Ускорение многоразового обращения к серверной процедуре/функции с помощью многопоточного вызова фоновых заданий

12.04.23

База данных - HighLoad оптимизация

Много раз приходилось переделывать обработку данных в цикле, на вызов нескольких фоновых заданий. И всегда это были костыли по месту. Решил попробовать систематизировать случай многократного вызова некой процедуры или функции и написать универсальную библиотеку. Очень быстро вспомнил природу костылей, и в итоге все свелось к исследовательской работе. Чем и поделюсь в статье. Код библиотеки и тестов прилагается.

Скачать исходный код

Наименование Файл Версия Размер
Ускорение многоразового обращения к серверной процедуре/функции с помощью многопоточного вызова фоновых заданий
.cfe 32,63Kb
0
.cfe 32,63Kb Скачать

Итак, у нас есть код, который исполняется на сервере, где последовательно запускается много функций:

Для Каждого Элемент Из Коллекция Цикл
    Результат = ЦелеваяФункция(Элемент);
КонецЦикла;

А хочется его ускорить, переписав примерно так:

ПараметрыЗапуска = ПодготовитьКоллекциюКМногопоточномуЗапуску(Коллекция);
КоллекцияРезультатов = ЗапуститьМногопоточно("ЦелеваяФункция", ПараметрыЗапуска);

Тестовый стенд

В качестве тестовой испытуемой функции не придумал ничего лучше, как запрашивать результат http запроса. Это позволяет мне манипулировать задержкой ответа. Единственный существенный минус – это совпадение терминов: таймаут, длительность, процент отказа. Эти же термины применимы и к фоновым заданиям.

Установил сервер апач, прикрутил php. В корень выложил файл index.php:

<? //echo "Hello world<br>";
$crash_persent = (int)$_GET['crash_persent'];
if (rand(0,100) < $crash_persent){
	http_response_code(500);
	//echo "$crash_persent<br>";
}
echo $_GET['time'];
sleep($_GET['time']);
?>

Этот простой скрипт позволяет мне запрашивать по http, например следующие url:

http://localhost/index.php?time=10&crash_persent=20

Задержка ответа будет 10 секунд, а с вероятностью 20% будет возвращен 500 заголовок в ответе.

Дальше идет минисервер 8.3.21.1709 с пустой конфигурацией. В нем расширение с библиотекой. В библиотеке есть тестовая функция в модуле хе_Тестовые:

Функция ОтветGetЗапроса(ДлительностьПаузы, ПроцентОтказа, Таймаут, Пошалить = Ложь) Экспорт
 

Все параметры очевидны, возможно, кроме Пошалить. Он эмулирует выброс исключения или не закрытой транзакции.

Дальше была разработка тестовой обработки и непосредственно самой библиотеки. Но в итоге свести запуск удалось лишь к следующему:

РезультатВыполнения = хе_Многопоточка.Запустить(ПутьПроцедуры...
 

Сразу возникает законный вопрос, что за 100500 параметров у функции Запустить?!!! Хочется заменить две исходные строки на две другие две строчки. А получилось:

// Запускает указанную процедуру в несколько потоков через фоновые задания.
Функция Запустить(ПутьПроцедуры,
 
 

Дальше придется пойти в теорию. "В теорию" это громко сказано, т.к. думаю, лет эдак 40-50 назад при разработке операционных систем уже давно все придумали, но у нас low code и мы думаем проще.

Так вот, исходная задача была в ускорении работы за счет распараллеливания обработки. Но ведь ускорение произойдет в том случае, если при обработке у нас простаивают какие-то ресурсы. Например: ядра процессора, ожидание ответа от сервера, дисковая система базы данных и т.д.  На примере ядер - мы должны были бы добавлять параллельные потоки обработки до тех пор, пока у нас нагрузка процессора меньше 80%. Мы же должны задавать количество потоков заранее.

И основная проблема, это то что у нас нет возможности запросить из запущенного нами фонового задания (потока) следующие данные для обработки. Т.е. данные мы должны заранее разделить и передать частями в поток. Почему это вынесено в параметры? Отвечу примером - запуск 1000000 процедур которые выполняются 1 секунду каждая, и запуск 100 процедур, которые выполняются 10 минут каждая. Очевидно, что параметры распараллеливания будут разные.

Данную проблему можно решить передачей данных через базу данных. Но тут, из-за универсальности, мне никак не хотелось менять мета данные конфигурации. А так вот пример другой: нужно обработать документы - поменять им статус (реквизит статус). В этом случае я бы мог не беспокоиться о порционности данных. Я бы просто запустил несколько фоновых, а в каждом фоновом, процедура запрашивала бы очередной документ к обработке, блокировала бы его, перечитывала бы статус, и если он еще не обработан, то переводила бы в следующий статус. И так в цикле, пока есть документы к обработке. Да фоновые бы немного конкурировали между собой за документы, но скорость обработки очевидно бы что возросла.  

Опыты

Перейдем к опытам. Я буду мучать 200 заданий. Подбирать потоки и другие параметры. Итак один поток: 641 секунда.

 

 

Попробуем разбить на равные части - 5 порций по 40 заданий:

 

 

Вау, всего 131 секунда вместо 641! Из замечаний - видим, что потоки исполнялись разное время и в теории при другом характере целевой процедуры могла бы получиться следующая картина:

 

 

Т.е. что произошло: один поток отработал быстрее других и у нас наметился простой. Общее время работы увеличилось на 20 секунд.

Проблема на самом деле в следующем: допустим, мы запустили поток с 40 заданиями и, например, 3-е оказалось длительным, но оно будет держать все оставшиеся 37. Т.к. из менеджера запуска фоновых мы не знаем, что обработано, а что нет - ждем пока все обработается. И другие задания начинают простаивать.

Тогда пробуем следующее - будем разбивать на более мелкие порции, например по 10шт и запускать их последовательно:

 

 

Стало хуже. Но здесь вмешивается следующая проблема - это частота опроса завершения заданий. У меня она 10 секунд (параметр ЧастотаОпросаФоновых) и это сильно повлияло на результат. 

На самом деле, здесь таже картина что и в предыдущем тесте, только разбитая но более мелкие 4 части. Но у меня есть еще один параметр - это таймаут фоновых. Поток, после истечения таймаута не будет обрабатывать следующие задания из порции, а завершится и освободит необработанные задания. Зададим его для примера 20 секунд:

 

 

Вообще какая-то фигня получилась :) Что произошло: фоновые прерывались и задания освобождали, но в какой-то момент заданий стало не хватать для всех потоков.

В общем, к чему я это все виду? К тому, что вам все равно приходится по месту подбирать как бы будете распараллеливать обработку. Здесь еще много случаев можно рассмотреть, но я пожалуй остановлюсь.

Напоследок попробую увеличить количество потоков.

 

 

Очевидно, что нужно снижать время ожидания между опросами. Так, еще приходит идея запускать одно фоновое на каждое задание. Эм... ну можно, но из рабочих кейсов, когда у вас 100к-1м заданий, вам все равно придется бить на порции, т.к. сервер будет не рад запуску 1м фоновых.

Примечания.

  • Целевое использование функции в расширении, это снижение последовательной обработки обработки до 0-2ч исполнения . Если у вас обработка после распараллеливания занимает больше времени, то надо уходить в регзадания. Более длительное выполнение влечет за собой невозможность кластеру сбросить рпхост, в котором запущена обработка. Что рано или поздно приведет к падению из-за нехватки памяти. Для примера можно посмотреть реализацию регзаданий в БТС (организацию запуска регзаданий по областям) или регзадание Обновление доступа на уровне записей подсистемы управление доступом из БСП. 
  • Если вам нужно, ускорять обработку данных, за которой следит пользователь, то нужно менеджер управления фоновых переносить на клиент. А далее либо длительные операции из БСП, либо свое.  
  • Пока писал тестовую функцию, увидел что у HTTPСоединение добавился асинхронный метод ПолучитьАсинх (с 8.3.21). Соответственно, можно не колхозить с фоновыми, а... колхозить напрямую запуском асинхронного получения данных :))
  • Выгрузил расширение в файлы и выложил на https://github.com/jekins81/bsl_multithread

UPD

В БСП все-таки добавили тему многопоточности.

Многопоточное выполнение процедуры с помощью ДлительныеОперации

ДлительныеОперации.ВыполнитьПроцедуруВНесколькоПотоков(

Обновил свои базы и посмотрел, что же дает вендор. Как всегда лапша кода, но вроде докопался до сути. В функции 

Функция ВыполнитьМногопоточныйПроцесс(ПараметрыОперации) Экспорт

Есть строка: 

ОжидатьЗавершениеВсехПотоков(Результаты, ИдентификаторФормы, ПараметрыОперации.ПрерватьВыполнениеЕслиОшибка);

Получается, что все равно не гибкое многопоточное выполнение. Т.е. это вариант, что я рассматривал в первых тестах в статье. но все равно круто, что за это взялись. Как по мне, это нужно в платформу выносить. 

 

ускорение многопоточный запуск

См. также

Оптимизация нагрузки на ЦП сервера СУБД используя типовые индексы

HighLoad оптимизация Платформа 1С v8.3 Конфигурации 1cv8 Бесплатно (free)

Анализ простого плана запроса. Оптимизация нагрузки на ЦП сервера СУБД используя типовые индексы.

13.03.2024    3590    spyke    28    

47

Быстродействие типовой 1С

HighLoad оптимизация Платформа 1С v8.3 Бесплатно (free)

Оказывается, в типовых конфигурациях 1С есть, что улучшить!

13.03.2024    5550    vasilev2015    19    

38

Анализируем SQL сервер глазами 1С-ника

HighLoad оптимизация Инструменты администратора БД Платформа 1С v8.3 Конфигурации 1cv8 Абонемент ($m)

Обработка для простого и удобного анализа настроек, нагрузки и проблем с SQL сервером с упором на использование оного для 1С. Анализ текущих зааросов на sql, ожиданий, конвертация запроса в 1с и рекомендации где может тормозить

1 стартмани

15.02.2024    8315    169    ZAOSTG    74    

101

Удаление строк из таблицы значений различными способами с замером производительности

HighLoad оптимизация Платформа 1С v8.3 Конфигурации 1cv8 Абонемент ($m)

Встал вопрос: как быстро удалить строки из ТЗ? Рассмотрел пять вариантов реализации этой задачи. Сравнил их друг с другом на разных объёмах данных с разным процентом удаляемых строк. Также сравнил с выгрузкой с отбором по структуре.

09.01.2024    6602    doom2good    48    

65

Опыт оптимизации 1С на PostgreSQL

HighLoad оптимизация Бесплатно (free)

При переводе типовой конфигурации 1C ERP/УТ/КА на PostgreSQL придется вложить ресурсы в доработку и оптимизацию запросов. Расскажем, на что обратить внимание при потерях производительности и какие инструменты/подходы помогут расследовать проблемы после перехода.

20.11.2023    9438    ivanov660    6    

76

ТОП проблем/задач у владельцев КОРП лицензий 1С на основе опыта РКЛ

HighLoad оптимизация Бесплатно (free)

Казалось бы, КОРП-системы должны быть устойчивы, быстры и надёжны. Но, работая в рамках РКЛ, мы видим немного другую картину. Об основных болевых точках КОРП-систем и подходах к их решению пойдет речь в статье.

15.11.2023    5365    a.doroshkevich    20    

72

Начните уже использовать хранилище запросов

HighLoad оптимизация Запросы

Очень немногие из тех, кто занимается поддержкой MS SQL, работают с хранилищем запросов. А ведь хранилище запросов – это очень удобный, мощный и, главное, бесплатный инструмент, позволяющий быстро найти и локализовать проблему производительности и потребления ресурсов запросами. В статье расскажем о том, как использовать хранилище запросов в MS SQL и какие плюсы и минусы у него есть.

11.10.2023    16611    skovpin_sa    14    

101
Комментарии
Подписаться на ответы Инфостарт бот Сортировка: Древо развёрнутое
Свернуть все
1. siamagic 03.04.23 14:25 Сейчас в теме
1. https://winitpro.ru/index.php/2022/07/07/zapusk-prostogo-http-web-servera-na-powershell/

2. В бсп есть функционал который запускает либо любой код либо процедуру из обработки на клиенте - т.е. без расширений и механизма дополнительных обработок
2. Quantum81 6 03.04.23 22:27 Сейчас в теме
(1) 1 - это написание своего вебсервера? Так, в свое время, я начал свой путь в веб разработку - ничего не зная, начал писать свой вебсервер на шарпах.
2 - У меня другая была задача. С запуском с клиента все понятно. Можно Длительные операции из БСП использовать и то менеджер придется колхозить самому, т.к. задача Длительных операции, организовать запуск кода так, чтобы клиент не зависал, а продолжал работать. Т.е. задачи распараллеливать там не стояло.
Да и не так там много кода, чтобы использовать БСП. Я очень быстро ушел в свой код, т.к. менеджер то мне все равно приходится писать самому.
Но в данном случае у меня вообще другая цель была, хочетелось иметь возможность ускорять куски серверного кода минимальным рефакторингом.

Ну и в целом, противоречиво :). Готов запилить свои минивебсервер, а для мультипоточности использовать библиотеку, которая не совсем для этого создана.

Peace! :)
3. Quantum81 6 12.04.23 11:20 Сейчас в теме
(1) Признаю был не прав в части БСП! :)
Обновил статью.
4. siamagic 12.04.23 11:54 Сейчас в теме
(3) В общем случае в любой внешней обработке:

1. Получаешь её путь на клиенте, закидываешь в хранилище.
2. Далее вызываешь сервер читаешь из хранилища записываешь в файл на сервере.
3. Вызываешь этот файл с экспортным методом из модуля, через ДлительныеОперации.вызватьСКонтекстомКлиента (или как то так) - тут по идеи генеришь фоновые задания с возможностью указать имя.

4. На клиенте ловишь фоновые задания по именам, читаешь сообщения от них.
5. Quantum81 6 12.04.23 12:34 Сейчас в теме
(4) да, ок, это понятно. У меня просто цель была немного другая. Я про ваш случай написал в примечании во 2 пункте.
Оставьте свое сообщение