Как 1С всю оперативную память съел...

17.06.21

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

В данной статье рассматривается обход (средствами встроенного языка) ошибки: "Превышен максимальный расход памяти сервера за один вызов" при рассылке расчетных листков в ЗУП 3.1 250+ сотрудникам.

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

Наименование Файл Версия Размер
РассылкаРасчеток
.cfe 48,85Kb
6
.cfe 1.0 48,85Kb 6 Скачать

С чего все началось. После перехода на версию платформы 8.3.18.1208 х64 и релиз ЗУП 3.1.17.135 перестали отправляться расчетные листки сотрудникам одним скопом (получателей 250+). А именно, начали ловить ошибку "Превышен максимальный расход памяти сервера за один вызов" (когда сеанс "отъедает" всю доступную серверу 1С оперативную память). В нашем случае, при отправке расчеток, формировалось фоновое задание, которое и расходовало всю доступную оперативную память. Обновление версии ЗУП до 3.1.17.138  и "танцы" с настройками сервера 1С не дали результата, отправился искать отладчиком "узкое" место встроенного языка, где происходит увеличение памяти.

На скрине представлен фрагмент типового кода конфигурации  ЗУП 3.1.17.138 , логика приведенного цикла в формировании и сохранении результата отчета "Анализ начислений и удержаний" (расчетный листок) для каждого сотрудника.

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

 Решение.  При рассылке отчетов типовым алгоритмом предусмотрено: формирование одного фонового задания, которое обеспечивает формирование и отправку результатов отчетов всем получателям. Так как памяти не хватает обрабатывать всех получателей за одно фоновое задание. Появилась идея формировать для каждого получателя отдельное фоновое задание (контролируя статус завершения фонового задания) и при этом минимально изменяя типовой алгоритм, в надежде, что когда нибудь это исправят... Результатом идеи и успешного решения поставленной задачи стало написанное расширение конфигурации. Логику и фрагменты которого распишу ниже.

Логика:

1. В имеющемся общем модуле перехватим типовую процедуру по формированию фонового задания для всего массива получателей и передадим в параметр создания фонового задания первый элемент массива получателей, затем сохраним исходный массив получателей и УИД задания во временном хранилище и хранилище общих настроек соответственно.

2. Далее подключим обработчик ожидания, который будет повторять создание фонового задания, пока массив получателей не будет пустым.

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

 
 Пример кода и описание пункта 1. 
&ИзменениеИКонтроль("ВыбратьПолучателяЗавершение")
Процедура Расш5_ВыбратьПолучателяЗавершение(ВыбранныйЭлемент, ДополнительныеПараметры) Экспорт
	Если ВыбранныйЭлемент = Неопределено Тогда
		Результат = Неопределено;
	Иначе
		Если ДополнительныеПараметры.ВозвращатьСоответствие Тогда
			Если ВыбранныйЭлемент.Значение = Неопределено Тогда
				Результат = ДополнительныеПараметры.Получатели;
			Иначе
				Результат = Новый Соответствие;
				Результат.Вставить(ВыбранныйЭлемент.Значение, ДополнительныеПараметры.Получатели[ВыбранныйЭлемент.Значение]);
			КонецЕсли;
		Иначе
			Результат = Новый Структура("Получатель, ПочтовыйАдрес", ВыбранныйЭлемент.Значение, ДополнительныеПараметры.Получатели[ВыбранныйЭлемент.Значение]);
		КонецЕсли;
	КонецЕсли;
	#Удаление
	ВыполнитьОбработкуОповещения(ДополнительныеПараметры.ОбработчикРезультата, Результат);
	#КонецУдаления
	#Вставка
	Если Строка(ДополнительныеПараметры.ОбработчикРезультата.ДополнительныеПараметры.МассивРассылок[0]) = "расчетный листок" Тогда
		//Формируем массив соответствий получателей (по одному)
		МассивСоответствийПолучателей = Новый Массив;
		Для каждого Получатель из Результат Цикл
			КопияПолучателей = Новый Соответствие;
			КопияПолучателей.Вставить(Получатель.Ключ,Получатель.Значение);
			МассивСоответствийПолучателей.Добавить(КопияПолучателей);    
		КонецЦикла;
		//Сохраним наш массив получателей во временное хранилище, и сохраним адрес к этому значению в хранилище общих настроек
		АдресМассиваПолучателейВременноеХранилище = ПоместитьВоВременноеХранилище(МассивСоответствийПолучателей, Новый УникальныйИдентификатор);
		Расш5_ПроверкаФоновогоЗадания.СохранитьАдресПользователей(АдресМассиваПолучателейВременноеХранилище);
		
		ДополнительныеПараметры.Получатели = МассивСоответствийПолучателей[0];
		Результат = МассивСоответствийПолучателей[0];
		ВыполнитьОбработкуОповещения(ДополнительныеПараметры.ОбработчикРезультата, Результат);
		//Сохраним Параметры, а перед сохранением удалим из параметров саму форму 
		Расш5_ПроверкаФоновогоЗадания.СохранитьУидФормы(ДополнительныеПараметры.ОбработчикРезультата.ДополнительныеПараметры.Форма.УникальныйИдентификатор);
		ДополнительныеПараметры.Удалить("ОбработчикРезультата");
		АдресПараметровВременноеХранилище = ПоместитьВоВременноеХранилище(ДополнительныеПараметры, Новый УникальныйИдентификатор);
		Расш5_ПроверкаФоновогоЗадания.СохранитьПараметры(АдресПараметровВременноеХранилище);	
		ОжиданиеОбработчика();
	Иначе
		ВыполнитьОбработкуОповещения(ДополнительныеПараметры.ОбработчикРезультата, Результат);
	КонецЕсли;
	#КонецВставки
КонецПроцедуры

 В данном фрагменте типового общего модуля "РассылкаОтчетовКлиент" перехватим процедуру "ВыбратьПолучателяЗавершение". В которой "ВыполнитьОбработкуОповещения(ДополнительныеПараметры.ОбработчикРезультата, Результат)" дает условный старт формирования фонового задания, посему перед этой строкой мы сохраняем наш массив получателей во временное хранилище, а так же необходимые дополнительные параметры. (помещаем во временное хранилище обязательно с параметром "Новый УникальныйИдентификатор", чтобы наш массив получателей жил на протяжении всего сеанса пользователя).

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

После выполнения "ВыполнитьОбработкуОповещения(ДополнительныеПараметры.ОбработчикРезультата, Результат)" и еще ряда процедур, исполнение кода переходит в процедуру "ВыполнитьСейчасВФоне", которую мы так же перехватим в расширении:

&ИзменениеИКонтроль("ВыполнитьСейчасВФоне")
Процедура Расш5_ВыполнитьСейчасВФоне(Получатели, Параметры) Экспорт
	ПредварительныеНастройки = Неопределено;
	Если Параметры.ЭтоФормаЭлемента Тогда
		Если Параметры.Форма.Объект.ИспользоватьЭлектроннуюПочту Тогда
			Если Получатели = Неопределено Тогда
				Возврат;
			КонецЕсли;
			ПредварительныеНастройки = Новый Структура("Получатели", Получатели);
		КонецЕсли;
		ТекстСостояния = НСтр("ru = 'Выполняется рассылка отчетов.'");
	Иначе
		ТекстСостояния = НСтр("ru = 'Выполняются рассылки отчетов.'");
	КонецЕсли;
	
	ПараметрыМетода = Новый Структура;
	ПараметрыМетода.Вставить("МассивРассылок", Параметры.МассивРассылок);
	ПараметрыМетода.Вставить("ПредварительныеНастройки", ПредварительныеНастройки);
	#Удаление
	Задание = РассылкаОтчетовВызовСервера.ЗапуститьФоновоеЗадание(ПараметрыМетода, Параметры.Форма.УникальныйИдентификатор);
	
	НастройкиОжидания = ДлительныеОперацииКлиент.ПараметрыОжидания(Параметры.Форма);
	НастройкиОжидания.ВыводитьОкноОжидания = Истина;
	НастройкиОжидания.ТекстСообщения = ТекстСостояния;
	
	Обработчик = Новый ОписаниеОповещения("ВыполнитьСейчасВФонеЗавершение", ЭтотОбъект, Параметры);
	ДлительныеОперацииКлиент.ОжидатьЗавершение(Задание, Обработчик, НастройкиОжидания);																																																									  
	#КонецУдаления
	#Вставка
	Если Строка(Параметры.МассивРассылок[0]) = "расчетный листок" Тогда
		Задание = РассылкаОтчетовВызовСервера.ЗапуститьФоновоеЗадание(ПараметрыМетода, Параметры.Форма.УникальныйИдентификатор);
		//Сохраним УИД задания для дальнейшего контроля статуса
		Расш5_ПроверкаФоновогоЗадания.СохранитьУидЗадания(Задание.ИдентификаторЗадания);
		НастройкиОжидания = ДлительныеОперацииКлиент.ПараметрыОжидания(Параметры.Форма);
		НастройкиОжидания.ВыводитьОкноОжидания = Истина;
		НастройкиОжидания.ТекстСообщения = ТекстСостояния;
		Обработчик = Новый ОписаниеОповещения("ВыполнитьСейчасВФонеЗавершение", ЭтотОбъект, Параметры);
		ДлительныеОперацииКлиент.ОжидатьЗавершение(Задание, Обработчик, НастройкиОжидания);
	Иначе	
		Задание = РассылкаОтчетовВызовСервера.ЗапуститьФоновоеЗадание(ПараметрыМетода, Параметры.Форма.УникальныйИдентификатор);
		НастройкиОжидания = ДлительныеОперацииКлиент.ПараметрыОжидания(Параметры.Форма);
		НастройкиОжидания.ВыводитьОкноОжидания = Истина;
		НастройкиОжидания.ТекстСообщения = ТекстСостояния;
		Обработчик = Новый ОписаниеОповещения("ВыполнитьСейчасВФонеЗавершение", ЭтотОбъект, Параметры);
		ДлительныеОперацииКлиент.ОжидатьЗавершение(Задание, Обработчик, НастройкиОжидания);	
	КонецЕсли;
	#КонецВставки
КонецПроцедуры

В данном фрагменте проверяем, что текущая рассылка у нас по расчетным листкам, затем после формирования фонового задания сохраняем его УИД в хранилище общих настроек.

Описанный выше код прописываем в заимствованном в расширении типовом общем модуле "РассылкаОтчетовКлиент". 

 
 Пример кода и описание пункта 2.

 

Процедура ОжиданиеОбработчика() Экспорт
	
	//Подключим свой обработчик ожидания, который будет ждать статус завершения фонового задания
	ПодключитьОбработчикОжидания("ОбработчикСтатусаВыполненияЗадания", 1, Ложь);	
	
КонецПроцедуры	

Данный фрагмент кода используется, чтобы организовать повторение действий по созданию фонового задания для каждого получателя отдельно.

 

 
 Пример кода и описание пункта 3.

 

Процедура ОбработчикСтатусаВыполненияЗадания() Экспорт
	СостояниеЗадания =  Расш5_ПроверкаФоновогоЗадания.ПолучитьПрогрессВыполнения();
	Если СостояниеЗадания = "Задание выполнено"  Тогда
		//Если заданее запущенное ранее заврешено, то удаляем 1 получателя из общего массива получателей и запускаем новое
		АдресМассива = Расш5_ПроверкаФоновогоЗадания.ПолучитьАдресПользователей();
		МассивСоответствийПолучателей = ПолучитьИзВременногоХранилища(АдресМассива);
		МассивСоответствийПолучателей.Удалить(0);
		Если МассивСоответствийПолучателей.Количество() = 0 Тогда
			ОтключитьОбработчикОжидания("ОбработчикСтатусаВыполненияЗадания");
			Сообщить("Выполнение рассылки успешно завершено!");
			ПоказатьОповещениеПользователя("Выполнение рассылки успешно завершено!");
		Иначе	
			АдресПараметры = Расш5_ПроверкаФоновогоЗадания.ПолучитьПараметры();
			ДополнительныеПараметрыОбщие = ПолучитьИзВременногоХранилища(АдресПараметры);
			ДополнительныеПараметрыОбщие.Получатели = МассивСоответствийПолучателей[0];
			Результат = МассивСоответствийПолучателей[0];
			ИмяПроцедуры = "ВыполнитьСейчасВФоне";
			Модуль = РассылкаОтчетовКлиент;
			МассивРассылок = Новый Массив;
			МассивРассылок.Добавить(Расш5_ПроверкаФоновогоЗадания.ПолучитьРассылку());
			ДополнительныеПараметры = Новый Структура;
			УИДФормы = Расш5_ПроверкаФоновогоЗадания.ПолучитьУидФормы();
			ФормаРассылки = ПолучитьФорму("Справочник.РассылкиОтчетов.Форма.ФормаЭлемента",,,Новый УникальныйИдентификатор(УИДФормы));
			ДополнительныеПараметры.Вставить("Форма", ФормаРассылки);
			ДополнительныеПараметры.Вставить("ЭтоФормаЭлемента", Истина);
			ДополнительныеПараметры.Вставить("МассивРассылок", МассивРассылок);
			ОбработчикРезультата = Новый ОписаниеОповещения(ИмяПроцедуры, Модуль, ДополнительныеПараметры);
			ДополнительныеПараметрыОбщие.Вставить("ОбработчикРезультата", ОбработчикРезультата);
			ВыполнитьОбработкуОповещения(ДополнительныеПараметрыОбщие.ОбработчикРезультата, Результат);
			//Сохраним наш массив получателей во временное хранилище
			ПоместитьВоВременноеХранилище(МассивСоответствийПолучателей, АдресМассива);
		КонецЕсли;
	ИначеЕсли  СостояниеЗадания = "Задание отменено пользователем"  Тогда
		//Если фоновое задание было отменено, то мы создаем новое задание для того же пользователя
		АдресМассива = Расш5_ПроверкаФоновогоЗадания.ПолучитьАдресПользователей();
		МассивСоответствийПолучателей = ПолучитьИзВременногоХранилища(АдресМассива);
		АдресПараметры = Расш5_ПроверкаФоновогоЗадания.ПолучитьПараметры();
		ДополнительныеПараметрыОбщие = ПолучитьИзВременногоХранилища(АдресПараметры);
		ДополнительныеПараметрыОбщие.Получатели = МассивСоответствийПолучателей[0];
		Результат = МассивСоответствийПолучателей[0];
		ИмяПроцедуры = "ВыполнитьСейчасВФоне";
		Модуль = РассылкаОтчетовКлиент;
		МассивРассылок = Новый Массив;
		МассивРассылок.Добавить(Расш5_ПроверкаФоновогоЗадания.ПолучитьРассылку());
		ДополнительныеПараметры = Новый Структура;
		УИДФормы = Расш5_ПроверкаФоновогоЗадания.ПолучитьУидФормы();
		ФормаРассылки = ПолучитьФорму("Справочник.РассылкиОтчетов.Форма.ФормаЭлемента",,,Новый УникальныйИдентификатор(УИДФормы));
		ДополнительныеПараметры.Вставить("Форма", ФормаРассылки);
		ДополнительныеПараметры.Вставить("ЭтоФормаЭлемента", Истина);
		ДополнительныеПараметры.Вставить("МассивРассылок", МассивРассылок);
		ОбработчикРезультата = Новый ОписаниеОповещения(ИмяПроцедуры, Модуль, ДополнительныеПараметры);
		ДополнительныеПараметрыОбщие.Вставить("ОбработчикРезультата", ОбработчикРезультата);
		ВыполнитьОбработкуОповещения(ДополнительныеПараметрыОбщие.ОбработчикРезультата, Результат);
		//Сохраним наш массив получателей во временное хранилище
		ПоместитьВоВременноеХранилище(МассивСоответствийПолучателей, АдресМассива);
	КонецЕсли;
	
КонецПроцедуры

Проверим завершено ли созданное нами ранее фоновое задание по одному получателю "Расш5_ПроверкаФоновогоЗадания.ПолучитьПрогрессВыполнения()". Если статус "Задание выполнено", тогда необходимо удалить из исходного массива получателей первый элемент (порядок элементов массива не меняется при "перегонке" его из хранилища в хранилище). Затем, если в массиве получателей еще есть элементы, создадим новое фоновое задание по аналогии фрагмента кода из пункта 1 логики, описанного выше.

Т.к. через примерно 40-50 итераций созданное фоновое задание иногда завершалось со статусом "Задание отменено пользователем"(не понятно почему), то заново формируем новое фоновое задание по тому же получателю.

Процедуры "ОжиданиеОбработчика()" и "ОбработчикСтатусаВыполненияЗадания()" помещаем в созаднный нами общий модуль "Расш5_ПодключениеОжидания" в расширении.

 

 
 Вспомогательные процедуры и функции созданного нами общего модуля "Расш5_ПроверкаФоновогоЗадания", которые обеспечивают получение и сохранение значений в хранилища (временное и общих настроек).

 

#Область сохранения
Процедура СохранитьАдресПользователей(АдресПользователей) Экспорт
	
	ХранилищеОбщихНастроек.Сохранить("MMP", "AdrUser", АдресПользователей, , );
	
КонецПроцедуры

Процедура СохранитьУидЗадания(УидЗадания) Экспорт
	
	ХранилищеОбщихНастроек.Сохранить("MMP", "AdrUid", УидЗадания, , );
	
КонецПроцедуры

Процедура СохранитьУидФормы(УидФормы) Экспорт
	
	ХранилищеОбщихНастроек.Сохранить("MMP", "UidForm", УидФормы, , );
	
КонецПроцедуры

Процедура СохранитьПараметрыМетода(ПараметрыМетода) Экспорт
	
	ХранилищеОбщихНастроек.Сохранить("MMP", "ParamMetod", ПараметрыМетода, , );
	
КонецПроцедуры

Процедура СохранитьПараметры(Параметры) Экспорт 
	
	ХранилищеОбщихНастроек.Сохранить("MMP", "Param", Параметры, , );
	
КонецПроцедуры
#КонецОбласти


#Область получения
Функция ПолучитьАдресПользователей() Экспорт
	
	Возврат	 ХранилищеОбщихНастроек.Загрузить("MMP", "AdrUser", ,);
	
КонецФункции

Функция ПолучитьУидЗадания() Экспорт
	
	Возврат  ХранилищеОбщихНастроек.Загрузить("MMP", "AdrUid", ,);
	
КонецФункции

Функция ПолучитьПараметрыМетода() Экспорт
	
	Возврат	 ХранилищеОбщихНастроек.Загрузить("MMP", "ParamMetod", ,);
	
КонецФункции

Функция ПолучитьПараметры() Экспорт
	
	Возврат	 ХранилищеОбщихНастроек.Загрузить("MMP", "Param", ,);
	
КонецФункции

Функция ПолучитьУидФормы() Экспорт
	
	Возврат	 ХранилищеОбщихНастроек.Загрузить("MMP", "UidForm", ,);
	
КонецФункции


Функция ПолучитьРассылку() Экспорт
	
	Возврат Справочники.РассылкиОтчетов.НайтиПоНаименованию("расчетный листок",Истина);	
	
КонецФункции

Функция ПолучитьПрогрессВыполнения() Экспорт
	
	УникальныйИдентификатор = ХранилищеОбщихНастроек.Загрузить("MMP", "AdrUid", ,);
	Отбор = Новый Структура;
	Отбор.Вставить("УникальныйИдентификатор", УникальныйИдентификатор);
	НайденныеЗадания = ФоновыеЗадания.ПолучитьФоновыеЗадания(Отбор);
	Возврат Строка(НайденныеЗадания[0].Состояние);
		
КонецФункции
#КонецОбласти

Поместим в общий модуль "Расш5_ПроверкаФоновогоЗадания".

 

P.s. Расширение тестировалось на версии ЗУП 3.1.17.138. Надеюсь данный материал будет кому то полезен.

Расширение конфигурации ЗУП 3.1 расход память рассылка отчеты фоновые задания

См. также

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

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

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

13.03.2024    3644    spyke    28    

47

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

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

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

13.03.2024    5611    vasilev2015    19    

38

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

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

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

1 стартмани

15.02.2024    8471    170    ZAOSTG    74    

102

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

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

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

09.01.2024    6791    doom2good    49    

65

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

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

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

20.11.2023    9581    ivanov660    6    

76

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

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

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

15.11.2023    5429    a.doroshkevich    20    

72

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

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

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

11.10.2023    16697    skovpin_sa    14    

101
Оставьте свое сообщение