Кейс по получению большой выборки данных в обработке порционно

01.06.23

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

В редких случаях требуется сложными запросами получить большую выборку данных и обработать её. Как переложить нагрузку с кластера 1С на СУБД.

Исходные данные:

  • лицензия 1С ПРОФ
  • платформа 8.3.22
  • оперативной памяти на сервере 1С 32 Гб

Описание проблемы:

Отраслевая отчёт-обработка, в которой с помощью сложных запросов с использованием множества временных таблиц строится результирующая (архитектуру реализации решения не обсуждаем, принимаем "как есть"). Результат нужно выгрузить в csv и отправить в надзорный орган.

Результат - таблица с более 800 тыс. строк и 30-тью колонками. Запрос на стороне СУБД исполняется достаточно быстро за период 2017-2023 гг, без нагрузки на сервер, план запроса - огромнейшая простыня на 5-ть экранов, визуально "по-диагонали" ничего криминального нет.

Выполнение отчёта генерирует исключение, в ТЖ “Недостаточно памяти для получения результата запроса к базе данных“, MemoryPeak > 4,5Гб для CALL.

Настройки параметров рабочего сервера "по умолчанию".

Согласно документации, настройка "Временно допустимый объем памяти процессов" при значении 0 в нашем случае равна 25 Гб (80% от ОЗУ). Настройка "Безопасный расход памяти за один вызов" при значении 0 в нашем случае равна 2.5 Гб (10% от "Временно допустимый объем памяти процессов").

Лицензия ПРОФ не позволяет изменять значение "Безопасный расход памяти за один вызов" (как раз "наш случай"). Исключительно ради эксперимента установил значение "Временно допустимый объем памяти процессов" (лицензия ПРОФ позволяет изменять данный параметр начиная с версии 8.3.19) в размер, 5-ти кратно превышающий размер ОЗУ 160 Гб - отчёт стал без проблем формироваться (расчётный "Безопасный расход памяти за один вызов" при этом получается 12,8 Гб). Но на продуктиве так делать, вероятно, нельзя.

Подходящий способ решения для нашей ситуации:

Очевидно, что нужно получать данные порциями (на ИТС даже есть статья, на это намекающая "Оптимизация использования оперативной памяти"). Но добавить ограничивающие выборку условия в нашем случае не представляется возможным (разумными трудозатратами) из-за чрезвычайно сложной логики работы.

И тут пришла идея - поместить результат выполнения запросов во временную таблицу и в цикле запрашивать данные порционно (с помощью менеджера временных таблиц). Но как их получать порционно? А для этого пронумеруем результирующие строки функцией АВТОНОМЕРЗАПИСИ(). Сказано = реализовано и протестировано!

Для примера я продемонстрирую простой виртуальной пример (не имеющий практической ценности). Исходный запрос:

	МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;
	Запрос = Новый Запрос;
	Запрос.МенеджерВременныхТаблиц = МенеджерВременныхТаблиц;
	Запрос.Текст = 
		"ВЫБРАТЬ ПЕРВЫЕ 500000
		|	скДСП.Представление КАК Представление,
		|	скДСП.ДоговорКонтрагента.Представление КАК ДоговорКонтрагентаПредставление,
		|	скДСП.Контрагент.Представление КАК КонтрагентПредставление,
		|	скДСП.Организация.Представление КАК ОрганизацияПредставление,
		|	АВТОНОМЕРЗАПИСИ() КАК Индекс
		|ПОМЕСТИТЬ врем
		|ИЗ
		|	Документ.скДСП КАК скДСП
		|
		|ИНДЕКСИРОВАТЬ ПО
		|	Индекс";
	
	РезультатЗапроса = Запрос.Выполнить();

Получение и обработка результата порциями:

	Запрос.Текст = 
		"ВЫБРАТЬ
		|	врем.Индекс КАК Индекс,
		|	врем.Представление КАК Представление,
		|	врем.ДоговорКонтрагентаПредставление КАК ДоговорКонтрагентаПредставление,
		|	врем.КонтрагентПредставление КАК КонтрагентПредставление,
		|	врем.ОрганизацияПредставление КАК ОрганизацияПредставление
		|ИЗ
		|	врем КАК врем
		|ГДЕ
		|	врем.Индекс МЕЖДУ &Индекс1 И &Индекс2";

	Для аа = 1 По 5 Цикл
		Запрос.УстановитьПараметр("Индекс1", 1 + 100000 * (аа-1));
		Запрос.УстановитьПараметр("Индекс2", 100000 * аа);
		РезультатЗапроса = Запрос.Выполнить();
		ВыборкаДетальныеЗаписи = РезультатЗапроса.Выбрать();
		
		инд = 0;
		Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
			Если инд = 0 Тогда
				Сообщить(ВыборкаДетальныеЗаписи.Индекс);
			КонецЕсли;
			а1 = ВыборкаДетальныеЗаписи.Представление;
			а2 = ВыборкаДетальныеЗаписи.ДоговорКонтрагентаПредставление;
			а3 = ВыборкаДетальныеЗаписи.КонтрагентПредставление;
			а4 = ВыборкаДетальныеЗаписи.ОрганизацияПредставление;
			инд = инд + 1;
		КонецЦикла;
		Сообщить("Получено: " + инд);
	КонецЦикла;

В цикле я изначально знаю, что записей фиксированное количество, для правильной обработки нужно получить количество строк в результате:

	Запрос.Текст = 
		"ВЫБРАТЬ
		|	КОЛИЧЕСТВО(*) КАК КолВо
		|ИЗ
		|	врем КАК врем";
	ВыборкаДетальныеЗаписи = Запрос.Выполнить().Выгрузить();
	ВсегоЗаписей = ВыборкаДетальныеЗаписи[0].КолВо;
	Сообщить("Всего записей: " + ВсегоЗаписей);

И обязательно обратить внимание на документацию для функции АВТОНОМЕРЗАПИСИ(): "Начальное значение счетчика зависит от используемой СУБД и, в общем случае, может быть любым. Не гарантируется, что начальное значение счетчика будет равно 1 для любой временной таблицы.". Но гарантируется "последовательно возрастающее значение". Дополним наш код получением начального значения счётчика:

	ИндексПервый = 0;
	Если ВсегоЗаписей > 0 Тогда
		Запрос.Текст = 
			"ВЫБРАТЬ ПЕРВЫЕ 1
			|	врем.Индекс КАК Индекс
			|ИЗ
			|	врем КАК врем
			|
			|УПОРЯДОЧИТЬ ПО
			|	Индекс";
		ВыборкаДетальныеЗаписи = Запрос.Выполнить().Выгрузить();
		ИндексПервый = ВыборкаДетальныеЗаписи[0].Индекс;
		Сообщить("Первый индекс: " + ИндексПервый);
	КонецЕсли;

Тестируем:

Всего записей: 500 000
Первый индекс: 1
1
Получено: 100 000
100 001
Получено: 100 000
200 001
Получено: 100 000
300 001
Получено: 100 000
400 001
Получено: 100 000

Дорабатываем наш отчёт по данной методике, тестируем. MemoryPeak при этом не поднимается > 1.5 Гб. Цель достигнута.

См. также

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

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

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

20.11.2023    5553    ivanov660    4    

61

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

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

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

15.11.2023    3570    a.doroshkevich    20    

64

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

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

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

11.10.2023    13477    skovpin_sa    14    

82

Как эффективно настроить autovacuum в Postgres для 1С

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

Кто не любит убирать мусор? Думаю, практически все, а вот в Postgres это обязательный ритуал для эффективной работы. Как эффективно настроить уборку за 1С в Postgres, можно прочитать в этой статье и еще раз задуматься о бесплатности Postgres.

05.08.2023    4187    1CUnlimited    5    

48

MS SQL Server: изучаем планы запросов

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

Многие знают, что для ускорения работы запроса нужно «изучить план». При этом сам план обычно обескураживает: куча разноцветных иконок и стрелочек; ничего не понятно, но очень интересно! Аналитик производительности Александр Денисов на конференции Infostart Event 2021 Moscow Premiere рассказал, как выполняется план запроса и что нужно сделать, чтобы с его помощью находить проблемы производительности.

20.06.2023    10709    Филин    37    

101

Простой способ проверки быстродействия

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

Простой (а точнее, мегапростой) способ проверки быстродействия, когда очень важно его, быстродействие, улучшить

10.04.2023    3491    vkrivov@yandex.ru    15    

36
Комментарии
В избранное Подписаться на ответы Сортировка: Древо развёрнутое
Свернуть все
1. ivanov660 4220 01.06.23 11:31 Сейчас в теме
Думаю, что проблема изначально в желании пользователя получить отчет размером в 800 тыс. строк. Я предполагаю, что он выгружается в эксель и смотрится там. С человеческой точки возможностей - нельзя объять необъятное.
Возможно стоит обсудить данную проблему с пользователем и сделать другой отчет или отчеты, который поможет решить задачу пользователя. А то из-за этого приходится так выкручиваться
METAL; info1i; корум; kser87; awk; +5 Ответить
2. AlexSTAL 99 01.06.23 13:31 Сейчас в теме
(1) Владимир, добрый день! Нет, это не желание пользователя изначально. Я в тексте специально указал, что это выгрузка без отображения на экране для передачи в надзорный орган
3. ivanov660 4220 01.06.23 16:38 Сейчас в теме
(2)
1. Пропустил, теперь увидел. Спасибо.
2. Тогда тут не объяснишь(
METAL; info1i; +2 Ответить
4. tormozit 7074 01.06.23 20:37 Сейчас в теме
Можно заметно сократить объем данных выборки, если не получать сразу представления ссылок, а выбрать сами ссылки (желательно простого типа) и получать представления ссылок при выводе каждой строки в файл выгрузки. Это конечно замедлит вывод данных. Но тут довольно экзотическая ситуация, поэтому в ней это может быть приемлемо. Еще можно не для всех типов ссылок это применить, а только для часто повторяющихся, тогда и на скорости вывода не так сильно скажется.
itoptimum; +1 Ответить
15. Cyberhawk 134 09.06.23 08:52 Сейчас в теме
(4)
не получать сразу представления ссылок, а выбрать сами ссылки (желательно простого типа)
А как такое сделать?
5. AlexSTAL 99 01.06.23 21:10 Сейчас в теме
(4) Сергей, спасибо, проведу эксперименты! Но это у меня в демо-примере представления получаются (не знаю зачем так сделал), а в реальном отчёте только ссылки...
6. TMV 14 01.06.23 22:10 Сейчас в теме
Что-то изменилось с этим?

АВТОНОМЕРЗАПИСИ

Описание:

Функция предназначена для формирования поля с уникальным, последовательно возрастающим значением во временной таблице. Функцию можно использовать в том случае, если необходимо создать ключевое поле во временной таблице. Значение, сформированное этой функцией, будет уникально в пределах временной таблицы, при формировании которой использовалась функция. Начальное значение счетчика зависит от используемой СУБД и, в общем случае, может быть любым. Не гарантируется, что начальное значение счетчика будет равно 1 для любой временной таблицы.
7. AlexSTAL 99 02.06.23 07:41 Сейчас в теме
(6)
Что-то изменилось с этим?

Так, что не так? Смысла комментария не могу понять.
Можно выделить из комментария жирный текст, скопировать в буфер, нажать Ctrl+F и поискать по текущей статье.
корум; mip128; +2 Ответить
8. ivanov660 4220 02.06.23 14:54 Сейчас в теме
(7)
Думаю вопрос в том, что может произойти так: начальное значение счетчика встанет, к примеру, в значение 100 000. Поэтому часть данных не будет считана. Т.к. от 0 до 100 000 будет пусто. А от 500 000 до 600 000 по условию счетчик не дойдет.
Чтобы от такого застраховаться, то можно сначала получить максимальное значение этого счетчика и от него уже считать интервалы.
P.S. Т.е. было бы хорошо, если коллега вместо намека, задал более прямой вопрос.
9. AlexSTAL 99 02.06.23 14:55 Сейчас в теме
(8) Так в моей статье именно так и написано, цитата: "Дополним наш код получением начального значения счётчика:"
10. ivanov660 4220 02.06.23 15:07 Сейчас в теме
(9) В самом алгоритме "Получение и обработка результата порциями" данного смещения сейчас не видно. С моей точки зрения, дополнение сейчас выглядит как демонстрация отдельной задачи, возможно стоило бы ее перенести выше и вставить значение индекса первого в код алгоритма.
Мне можете не отвечать, идею и проблему я понял. Спасибо.
AlexSTAL; +1 Ответить
11. kser87 2403 02.06.23 15:54 Сейчас в теме
Не хватает памяти, поэтому приняли решение поместить во временную таблицу. Что?
12. AlexSTAL 99 02.06.23 17:05 Сейчас в теме
(11) Мне кажется вы не читали статью...
mrChOP93; +1 Ответить
13. Jimbo 9 06.06.23 16:16 Сейчас в теме
Годнота, автору респект. Временная таблица хранится в tempdb СУБД - там не смотрели на сколько прирост ?
14. AlexSTAL 99 06.06.23 19:52 Сейчас в теме
(13) Благодарю! Не, на стороне СУБД не анализировал. Там ситуация управляемая - добавил места и решил проблему, в отличие от механизмов 1С
Оставьте свое сообщение