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

12.04.23

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

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

Скачать файл

ВНИМАНИЕ: Файлы из Базы знаний - это исходный код разработки. Это примеры решения задач, шаблоны, заготовки, "строительные материалы" для учетной системы. Файлы ориентированы на специалистов 1С, которые могут разобраться в коде и оптимизировать программу для запуска в базе данных. Гарантии работоспособности нет. Возврата нет. Технической поддержки нет.

Наименование По подписке [?] Купить один файл
Ускорение многоразового обращения к серверной процедуре/функции с помощью многопоточного вызова фоновых заданий
.cfe 32,63Kb
0
0 Скачать (1 SM) Купить за 1 850 руб.

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

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

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

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

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

В качестве тестовой испытуемой функции не придумал ничего лучше, как запрашивать результат 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 оптимизация Технологический журнал Системный администратор Программист Бесплатно (free)

Обсудим поиск и разбор причин длительных серверных вызовов CALL, SCALL.

24.06.2024    5163    ivanov660    12    

56

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

Метод очень медленно работает, когда параметр приемник содержит намного меньше свойств, чем источник.

06.06.2024    9287    Evg-Lylyk    61    

44

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

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

13.03.2024    5110    spyke    28    

49

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

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

13.03.2024    7591    vasilev2015    20    

42

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

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

2 стартмани

15.02.2024    12459    241    ZAOSTG    82    

115

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

Принимать, хранить и анализировать показания счетчиков (метрики) в базе 1С? Почему бы нет? Но это решение быстро привело к проблемам с производительностью при попытках построить какую-то более-менее сложную аналитику. Переход на PostgresSQL только временно решил проблему, т.к. количество записей уже исчислялось десятками миллионов и что-то сложное вычислить на таких объемах за разумное время становилось все сложнее. Кое-что уже практически невозможно. А что будет с производительностью через пару лет - представить страшно. Надо что-то предпринимать! В этой статье поделюсь своим первым опытом применения СУБД Clickhouse от Яндекс. Как работает, что может, как на нее планирую (если планирую) переходить, сравнение скорости работы, оценка производительности через пару лет, пример работы из 1С. Все это приправлено текстами запросов, кодом, алгоритмами выполненных действий и преподнесено вам для ознакомления в этой статье.

1 стартмани

24.01.2024    5687    glassman    18    

40

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

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

09.01.2024    14098    doom2good    49    

71
Комментарии
Подписаться на ответы Инфостарт бот Сортировка: Древо развёрнутое
Свернуть все
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 пункте.
Оставьте свое сообщение