Оптимизация расчета себестоимости выпуска продукции (УПП 1.3, Партионный учет)

31.08.16

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

В результате выполненных доработок удалось уменьшить время проведения Регламентной операции "Рассчитать себестоимость (БУ, НУ)" в два и более раза, а в отдельных операциях (Распределение продукции и затрат по переделам) удалось достичь 7-ми кратного уменьшения времени проведения!

Скачать файл

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

Наименование По подписке [?] Купить один файл
ПроцедурыРасчетаСебестоимостиВыпуска.zip
.zip 175,16Kb ver:3
37
37 Скачать (1 SM) Купить за 1 850 руб.

До начала оптимизации дело обстояло так:

В отдельных месяцах проведение Регламентной операции "Рассчитать себестоимость (БУ, НУ)" удавалось осуществить только в выходные дни - проведение длилось более суток. При этом происходил лавинообразный рост служебной базы tempdb до астрономических 360 Гб.

Конфигурация:

  • Управление производственным предприятием, редакция 1.3 (1.3.79.2)
  • 1С:Предприятие 8.3 (8.3.8.1784)
  • Microsoft SQL Server 2012
  • Используется партионный учет
  • Расчет себестоимости ведется по переделам
  • Размер базы (.mdf) 38 Гб

Решение:

1. В борьбе с разрастанием служебной базы tempdb кардинально решить вопрос удалось следующим образом:

В процедурах:

  • ПроцедурыРасчетаСебестоимостиВыпуска.СформироватьТекстЗапросаЗаполнениеКорректировкиВстречногоВыпускаПродукции();
  • ПроцедурыРасчетаСебестоимостиВыпуска.СформироватьТекстЗапросаПоВыпускуПродукцииИЗатратамНаВыпуск();
  • ПроцедурыРасчетаСебестоимостиВыпуска.СформироватьТекстЗапросаПоЗатратамНаВыпуск();
  • ПроцедурыРасчетаСебестоимостиВыпуска.СформироватьТекстЗапросаБазаРаспределенияЗатратНаПродукцию();
  • РасчетСебестоимостиВыпускаРаспределениеПоПеределам.СформироватьТекстЗапросаВыпускБезПрямыхРасходов();
  • РасчетСебестоимостиВыпускаРаспределениеПоПеределам.СформироватьТекстЗапросаВыпускНаРаспределяемыеРасходы();
  • РасчетСебестоимостиВыпускаРаспределениеПоПеределам.СформироватьТекстЗапросаМатериальныеПроизводственныеРасходы();
  • РасчетСебестоимостиВыпускаРаспределениеПоПеределам.СформироватьТекстЗапросаНематериальныеПроизводственныеРасходы();
  • РасчетСебестоимостиВыпускаРаспределениеПоПеределам.СформироватьТекстЗапросаНематериальныеПроизводственныеРасходыНУ();

1.1. В текстах запросов конструкции языка связанные с обращением к реквизиту через точку заменяем на явное соединение с соответствующей таблицей. Особенно это актуально для справочника Номенклатура (реквизит ВестиУчетПоСериямВНЗП).

Пример: РасчетСебестоимостиВыпускаРаспределениеПоПеределам.СформироватьТекстЗапросаМатериальныеПроизводственныеРасходы()

//+    - добавленные строки

ВЫБРАТЬ РАЗЛИЧНЫЕ
	...
	//ВЫБОР КОГДА ЗатратыНаВыпуск.Продукция.ВестиУчетПоСериямВНЗП ТОГДА
	ВЫБОР КОГДА ЕСТЬNULL(ЗатратыНаВыпускПродукция.ВестиУчетПоСериямВНЗП, ЛОЖЬ) ТОГДА //+
	...
	//ВЫБОР КОГДА ЗатратыНаВыпуск.Затрата.ВестиУчетПоСериямВНЗП ТОГДА
	ВЫБОР КОГДА ЕСТЬNULL(ЗатратыНаВыпускЗатрата.ВестиУчетПоСериямВНЗП, ЛОЖЬ) ТОГДА //+
	...
ИЗ
	РегистрНакопления.ЗатратыНаВыпускПродукции%СуффиксУчета% КАК ЗатратыНаВыпуск
	...

	ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Номенклатура КАК ЗатратыНаВыпускПродукция //+
	ПО ЗатратыНаВыпускПродукция.Ссылка = ЗатратыНаВыпуск.Продукция //+

	ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Номенклатура КАК ЗатратыНаВыпускЗатрата //+
	ПО ЗатратыНаВыпускЗатрата.Ссылка = ЗатратыНаВыпуск.Затрата //+

1.2. В текстах запросов условия следующего вида (имеет смысл для вложенных запросов с объемистой выборкой):

ГДЕ
	ВыпускПродукции.Продукция В (
		ВЫБРАТЬ РАЗЛИЧНЫЕ
			БазаРаспределенияЗатрат.Продукция
		ИЗ
			РегистрСведений.БазаРаспределенияЗатрат%СуффиксУчета% КАК БазаРаспределенияЗатрат
		ГДЕ
			БазаРаспределенияЗатрат.Период МЕЖДУ &НачДата И &КонДата
			И БазаРаспределенияЗатрат.РаспределениеКосвенныхЗатрат = &РаспределениеКосвенныхЗатрат
			И БазаРаспределенияЗатрат.РасчетСебестоимостиВыпуска
		)

заменяем на внутреннее соединение следующего вида:

	ВНУТРЕННЕЕ СОЕДИНЕНИЕ (
		ВЫБРАТЬ РАЗЛИЧНЫЕ
			БазаРаспределенияЗатрат.Продукция
		ИЗ
			РегистрСведений.БазаРаспределенияЗатрат%СуффиксУчета% КАК БазаРаспределенияЗатрат
		ГДЕ
			БазаРаспределенияЗатрат.Период МЕЖДУ &НачДата И &КонДата
			И БазаРаспределенияЗатрат.РаспределениеКосвенныхЗатрат = &РаспределениеКосвенныхЗатрат
			И БазаРаспределенияЗатрат.РасчетСебестоимостиВыпуска
	) КАК БазаРаспределенияЗатрат
	ПО БазаРаспределенияЗатрат.Продукция = ВыпускПродукции.Продукция

В результате этих двух доработок (1.1 и 1.2) максимальный рост tempdb в итоге составил 1.8 Гб, так же существенно увеличилась скорость выполнения запросов.

2. Поиск из результата запроса заменяем на поиск строк из таблицы значений методом НайтиСтроки (в местах где происходит поиск в цикле с многотысячным количеством итераций)

Пример: ПроцедурыРасчетаСебестоимостиВыпуска.КорректировкаДвиженийПоВыпускуПродукции()

//+		- добавленные строки

//ВыборкаПоВыпуску = РезультатЗапросаПоВыпускуПродукции.Выбрать();
ТаблицаПоВыпуску = РезультатЗапросаПоВыпускуПродукции.Выгрузить(); //+
...

ОбходПоЗатратам = РезультатЗапросаПоЗатратамНаВыпускПродукции.Выбрать();
Пока ОбходПоЗатратам.Следующий() Цикл

	СтруктураПоискаВыпуск = ПолучитьСтруктуруПоискаСтрокВыпускаПродукции(
		СтруктураШапкиДокумента,
		ОбходПоЗатратам
		);
	...

	//ВыборкаПоВыпуску.Сбросить();
	//Пока ВыборкаПоВыпуску.НайтиСледующий(СтруктураПоискаВыпуск) Цикл // На этой строчке замер производительности показывал многочасовые ожидания
	МассивСтрокПоВыпуску = ТаблицаПоВыпуску.НайтиСтроки(СтруктураПоискаВыпуск); //+
	Для каждого ВыборкаПоВыпуску Из МассивСтрокПоВыпуску Цикл //+
		...

	КонецЦикла;
	...

КонецЦикла;

3. Перебор строк с нулевым переделом в отсортированной таблице заменяем на перебор элементов массива полученного из таблицы методом НайтиСтроки, то есть исключаем затратную операцию сортировки.

Пример: РасчетСебестоимостиВыпускаРаспределениеПоПеределам.СоздатьТабПеределов() - здесь удалось достичь 7-ми кратного выигрыша по времени !!!

//+    - добавленные строки

//МаксИндекс = ТабПеределов.Количество() - 1;
...

Пока ПроставленПередел Цикл
	...

	//ТабПеределов.Сортировать("НомерПередела Убыв");	// На этой строчке замер производительности показывал многочасовые ожидания (в нашей таблице до 6 млн. записей)
	//ТекСтрока = ТабПеределов.Найти(0, "НомерПередела");
	СтрокиСПустымиПеределами = ТабПеределов.НайтиСтроки(Новый Структура("НомерПередела", 0)); //+

	//Если ТекСтрока = Неопределено Тогда
	Если СтрокиСПустымиПеределами.Количество() = 0 Тогда //+
		Прервать;
	КонецЕсли;

	//Индекс = ТабПеределов.Индекс(ТекСтрока);
	...

	//ИндексСтроки = Индекс;
	//Пока ИндексСтроки <= МаксИндекс Цикл
	МаксИндекс = СтрокиСПустымиПеределами.ВГраница(); //+
	Для Сч=0 По МаксИндекс Цикл //+
		ИндексСтроки = МаксИндекс - Сч; //+

		//СтрокаТаблицы = ТабПеределов[ИндексСтроки];
		СтрокаТаблицы = СтрокиСПустымиПеределами[ИндексСтроки]; //+
		...

		СтрокаТаблицы.НомерПередела = 1;
		СтрокиСПустымиПеределами.Удалить(ИндексСтроки); //+ // Удаление текущей строки с проставленным переделом
		...

		//ИндексСтроки = ИндексСтроки + 1;

	КонецЦикла;
	...

КонецЦикла;

4. Так же был применён метод асинхронной записи регистров. По этому методу выигрыш по времени в наших условиях составил до 30%

Суть метода:

В документе Расчет себестоимости в оригинальном алгоритме реализована запись движений регистров порциями по 1000 строк по мере расчета.

	Если НаборЗаписейБазаРаспределенияЗатрат.Количество() = 1000 Тогда
		НаборЗаписейБазаРаспределенияЗатрат.Записать(Ложь);
	КонецЕсли;

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

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

Графически эти два варианта выглядят так:

1. Последовательные Расчет и Запись
Основной поток Расчет 1 Запись 1 Расчет 2 Запись 2 Расчет 3 Запись 3 Расчет 4 Запись 4 Расчет 5 Запись 5
2. Асинхронные Расчет и Запись
Основной поток Расчет 1 Расчет 2 Расчет 3 Расчет 4 Расчет 5 Ожидание
Фоновое задание 1 Запись 1
Фоновое задание 2 Запись 2
Фоновое задание 3 Запись 3
Фоновое задание 4 Запись 4
Фоновое задание 5 Запись 5

Замечания и ограничения применимости данного метода:

  • При стандартном проведении запись в регистры из фонового задания невозможна из-за конфликта блокировок (режим управления блокировкой данных - автоматический), метод можно реализовать только при так называемом "Проведении вне транзакции".
  • Как следствие из предыдущего, в коде необходимо предусмотреть признаки по которым при стандартном проведении должен выполняться последовательный алгоритм, а при проведении вне транзакции алгоритм с асинхронной записью.
  • Перед внедрением данного метода необходимо выполнить замер производительности. Может оказаться, что в Ваших условиях программно-аппаратной реализации, запись в информационную базу происходит достаточно быстро по сравнению с расчетом или наоборот. В этих случаях затраты времени на выгрузку записей набора в таблицу значений для передачи в фоновое задание и запуск фонового задания могут оказаться выше ожидаемого выигрыша.
  • Фоновые задания по каждому виду регистра необходимо выстраивать в стек, в котором каждое последующее задание должно ожидать завершение предыдущего.
  • В местах записи последней порции набора записей (до условия: Если НаборЗаписейБазаРаспределенияЗатрат.Модифицированность() Тогда) необходимо выполнить ожидание завершения фоновых заданий, запущенных в контексте по данному регистру.

Реализация метода:

Задачи.РегламентныеОперацииЗакрытияМесяца.ФормаЗадачи - на командную панель формы добавляем кнопку "Провести вне транзакции" и назначаем следующий обработчик:

Процедура КоманднаяПанельДокументыРОПровестиВнеТранзакции(Кнопка)
	
	Ответ = Вопрос("Внимание! Проведение документов вне транзакции можно выполнять только, 
	  |если одновременно не вводятся первичные документы в периоде, предшествующем проводимому документу.
  	  |Провести документы вне транзакции?", РежимДиалогаВопрос.ДаНет, 100, КодВозвратаДиалога.Нет);
	
	Если Ответ <> КодВозвратаДиалога.Да Тогда
		Возврат;
	КонецЕсли;	
	
	//Перед проведением упорядочим сформированные документы: сначала УУ, потом БУ, потом НУ
	//Для документа РасчетСебестоимости важно чтобы сначала провелся документ БУ, а лишь затем НУ
	СформированныеДокументы.Сортировать("ОтражатьВУправленческомУчете убыв, ОтражатьВБухгалтерскомУчете убыв, ОтражатьВНалоговомУчете убыв");
	
	МассивДокументы = ПолучитьВыбранныеДокументы(Истина);
	
	Для каждого Строка из МассивДокументы Цикл
		ДокументОбъект = Строка.Документ.ПолучитьОбъект();
		Если ДокументОбъект <> Неопределено И НЕ ДокументОбъект.ПометкаУдаления Тогда
			
			Попытка
				ДокументОбъект.Заблокировать();
			Исключение
				ВызватьИсключение "Не удалось заблокировать документ " + Строка.Документ + ", " + ОписаниеОшибки();
			КонецПопытки;
	
			Попытка
				
				Если ТипЗнч(Строка.Документ) = Тип("ДокументСсылка.РасчетСебестоимостиВыпуска") Тогда	
					
					Если ДокументОбъект.Проведен Тогда
						ДокументОбъект.Проведен = Ложь;
						ДокументОбъект.Записать(РежимЗаписиДокумента.Запись);
					КонецЕсли;
				
					// Также у непроведенного документа могут остаться движения, если предыдущее проведение вне транзакции завершилось внештатно (аварийно). Такие движения необходимо очистить.
					ЛУ_КлиентСервер.ОчиститьДвиженияДокумента(ДокументОбъект.Движения, ДокументОбъект.Ссылка);

					ДокументОбъект.мУдалятьДвижения = Ложь;
					Отказ = Ложь;
					
					глЗначениеПеременнойУстановить("НаборыДвиженийЗаписыватьАсинхронно", Истина, Истина); // Взводим флаг асинхронной записи движений

					ДокументОбъект.ОбработкаПроведения(Отказ, РежимПроведенияДокумента.Неоперативный);
					
					глЗначениеПеременнойУстановить("НаборыДвиженийЗаписыватьАсинхронно", Ложь, Истина); // Отключаем асинхронную запись
					
					Если Не Отказ Тогда
						
						Для Каждого ТекущееДвижение Из ДокументОбъект.Движения Цикл
							Если ТекущееДвижение.Модифицированность() Тогда
								ТекущееДвижение.Записать();
							КонецЕсли;
						КонецЦикла;	
						
						ДокументОбъект.Проведен = Истина;
						ДокументОбъект.Записать(РежимЗаписиДокумента.Запись);
						
					КонецЕсли;
					
				Иначе // Типовой функционал проведения
					
					ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);
					
				КонецЕсли;
				
			Исключение
				глЗначениеПеременнойУстановить("НаборыДвиженийЗаписыватьАсинхронно", Ложь, Истина);
				ВызватьИсключение "Не удалось провести документ " + Строка.Документ + ", " + ОписаниеОшибки();
			КонецПопытки;
			
			ДокументОбъект.Разблокировать();
			
			Строка.ДокументПроведен = ДокументОбъект.Проведен;
		
		КонецЕсли;
		
	КонецЦикла;
	
КонецПроцедуры

В местах записи очередной порции движений добавляем вызов функции: НаборЗаписейЗаписатьАсинхронно(НаборЗаписей)

Если НаборЗаписейЗатратыНаВыпуск.Количество() = 1000 Тогда
	Если НЕ ЛУ_КлиентСервер.НаборЗаписейЗаписатьАсинхронно(НаборЗаписейЗатратыНаВыпуск) Тогда //+
		НаборЗаписейЗатратыНаВыпуск.Записать(Ложь);
	КонецЕсли; //+
КонецЕсли;

НаборЗаписейЗаписатьАсинхронно - функция общего модуля ЛУ_КлиентСервер (Сервер, Клиент обычное приложение) со следующим листингом:

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

ФоновоеЗаданиеЗаписатьНаборЗаписей - функция общего модуля ЛУ_ВызовСервера (Сервер, Вызов сервера, Привилегированный) со следующим листингом:

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

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

ЛУ_КлиентСервер.ОжиданиеЗавершенияФоновыхЗаданий(СтруктураДвижений.ДвиженияЗатратыНаВыпуск); //+
	
Если СтруктураДвижений.ДвиженияЗатратыНаВыпуск.Модифицированность() Тогда
	СтруктураДвижений.ДвиженияЗатратыНаВыпуск.Записать(Ложь);
КонецЕсли;

ОжиданиеЗавершенияФоновыхЗаданий - функция общего модуля ЛУ_КлиентСервер (Сервер, Клиент обычное приложение) со следующим листингом:

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

Более подробные листинги смотри во вложении

Вложенный файл ПроцедурыРасчетаСебестоимостиВыпуска.zip содержит модули подвергшиеся оптимизации согласно описанным в статье методам и предназначены для конфигурации Управление производсвенным предприятем 1.3 (1.3.79.2). Возможна установка на более ранние или старшие релизы. Клиент - Серверный вариант. Обычные формы.

Модули предназначены для самостоятельного внедрения. Код всех модулей полностью открыт. Возможна доработка собственными силами. Дополнительное лицензирование не требуется.

Обновление модулей, связанные с изменением типовой конфигурации, не планируется. Так как данный функционал в типовой конфигурации более не развивается.

Состав файла:

1. ПроцедурыРасчетаСебестоимостиВыпуска.cf
Помечаем для объединения только следующие общие модули:
- ПроцедурыРасчетаБазыРаспределенияЗатрат
- ПроцедурыРасчетаСебестоимостиВыпуска
- РасчетСебестоимостиВыпускаРаспределениеПоПеределам
- ЛУ_КлиентСервер
- ЛУ_ВызовСервера

2. Задачи.РегламентныеОперацииЗакрытияМесяца.ФормаЗадачи.txt
- На форме задачи в командной панели необходимо добавить кнопку "Провести вне транзакции" и назначить обработчик из этого файла

УПП Оптимизация Расчет себестоимости Партионный учет tempdb асинхронная запись

См. также

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

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

06.06.2024    9260    Evg-Lylyk    61    

44

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

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

13.03.2024    5097    spyke    28    

49

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

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

13.03.2024    7573    vasilev2015    20    

42

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

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

2 стартмани

15.02.2024    12422    241    ZAOSTG    80    

115

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

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

1 стартмани

24.01.2024    5669    glassman    18    

40

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

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

09.01.2024    14018    doom2good    49    

71
Комментарии
Подписаться на ответы Инфостарт бот Сортировка: Древо развёрнутое
Свернуть все
1. DatiniFM 23.08.16 17:13 Сейчас в теме
Вопрос только один - А что программеры 1с в УПП тогда делают -усиленно штампуют неоптимальные алгоритмы?
2. linkov 278 23.08.16 17:43 Сейчас в теме
(1) DatiniFM,

Да нет, не штампуют, все в пределах методологии.
Только вот на наших объемах и программно - аппаратной реализации более оптимальными оказались представленные алгоритмы.
Хотя, по п.3 типовой алгоритм по любому косяковый.
3. tango 545 23.08.16 22:41 Сейчас в теме
+
хорошая работа
adhocprog; +1 Ответить
4. Mkonst 57 24.08.16 12:29 Сейчас в теме
некоторые вещи из публикации надо будет попробовать на своей упп...
5. adhocprog 1142 24.08.16 16:06 Сейчас в теме
Ценная статья )
Спасибо
6. lunjio 67 25.08.16 10:10 Сейчас в теме
Хорошая статья, спасибо, единственное, что при создании фоновых задач можно управлять стеком указывая ключ задания, который в данном случае будет являться именем регистра. 1С при запуске смотрит, нет ли задач с таким же ключем и только тогда запускает, а так же что это можно было оформить в конфигурацию в качестве модулей с такими же наименованиями, а не текстовыми файликами, а в целом мне очень актуально, больше всего занимает процесс записи у нас при расчете. Спасибо за то, что поделились.
7. linkov 278 25.08.16 10:54 Сейчас в теме
(6) lunjio,

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

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

Если вы внимательно просмотрите код, то увидите, что у меня все задания по одному регистру запускаются с именем этого регистра в наименовании, и уже внутри фонового задания происходит ожидание завершения предыдущих с таким же наименованием.
(Ключ задания - должен быть уникальным, Наименование задания - может быть любым)

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

Ок, во вложенный архив добавлен cf
13. lunjio 67 31.08.16 19:37 Сейчас в теме
(7)
Вынужден с вами не согласиться, лично для интереса проверил следующим образом - создал регламентное задание которое в качестве параметра принимает ссылку на некий справочник, далее в цикле с 10 тыс. итерациями в специально созданный регистр сведений записываю данную ссылку с измерением равным счетчику цикла, для тестов достаточно. Создаю подряд два регламентных задания, с одинаковым ключом, в оба передаю разные значения ссылок, записываю их подряд, выполнится должны почти сразу после записи, ставлю точку останова в процедуре выполнения фонового задания, в другом сеансе вижу, что одно задание выполняется, второе ещё не выполнялось и не запускалось, как только отпускаю точку останова, попадаю туда сразу второй раз, в консоли регламентных заданий вижу что теперь второе задание исполняется, первое выполнено.
А чтобы регламентные задания не висели впустую, в фоновом задании их можно просто удалять.
У себя ещё не внедрил, но деньги за это думаю просите обосновано, грамотно проделанная оптимизация, ещё раз спасибо за публикацию.
16. linkov 278 01.09.16 09:45 Сейчас в теме
(13) lunjio,

Вынужден с вами не согласиться, лично для интереса проверил следующим образом - создал регламентное задание которое в качестве параметра принимает ссылку на некий справочник, далее в цикле с 10 тыс. итерациями в специально созданный регистр сведений записываю данную ссылку с измерением равным счетчику цикла, для тестов достаточно. Создаю подряд два регламентных задания, с одинаковым ключом, в оба передаю разные значения ссылок, записываю их подряд, выполнится должны почти сразу после записи, ставлю точку останова в процедуре выполнения фонового задания, в другом сеансе вижу, что одно задание выполняется, второе ещё не выполнялось и не запускалось, как только отпускаю точку останова, попадаю туда сразу второй раз, в консоли регламентных заданий вижу что теперь второе задание исполняется, первое выполнено.
А чтобы регламентные задания не висели впустую, в фоновом задании их можно просто удалять.


Верно. Для Регламентного задания уникальность ключа не требуется. По Вашему предложению тоже можно организовать выполнение Фоновых заданий в стеке. Но все же получается сложновато - в этом случае для запуска Фоновых заданий дополнительно придется создавать, записывать в базу и удалять объекты Регламентных заданий. К тому же основное назначение Регламентных заданий - выполнение Фоновых задач по расписанию. Спасибо за подсказку, возможно, где нибудь пригодится.
18. lunjio 67 02.09.16 11:58 Сейчас в теме
(16)
Да согласен, в вашем варианте более оптимально, как-то сразу не подумал, притом что если организовывать через регламентные, встает проблема с ожиданием завершения всех текущих заданий, в голове рисуется что ожидание будет не очень красивым в плане кода и логики ,в важем случае все четко, НО внесу свои две копеек всё-таки в оптимизацию кода
Текст где в процедуре фонового задания мы ожидаем завершения предыдущих в стеке, нужно перенести в начало процедуры, не совсем оптимально по отношению к памяти, создаем набор записи, переносим в него таблицу, а потом ждём.
19. linkov 278 03.09.16 10:19 Сейчас в теме
(18) lunjio,

НО внесу свои две копеек всё-таки в оптимизацию кода
Текст где в процедуре фонового задания мы ожидаем завершения предыдущих в стеке, нужно перенести в начало процедуры, не совсем оптимально по отношению к памяти, создаем набор записи, переносим в него таблицу, а потом ждём.

Зато оптимально в плане скорости, после окончания ожидания уже всё готово, остается только записать набор записей. Спорный момент. Зависит от приоритетов в конкретной реализации.
8. anama_agro 26.08.16 16:30 Сейчас в теме
Большое спасибо Вам за идею! Хотелось бы задать пару вопросов: не совсем понимаю зачем мы предварительно выгружаем набор записей в таблицу значений ПараметрыЗадания.Добавить(НаборЗаписей.Выгрузить()) (на это же тратится дополнительное время)? Почему нельзя просто передать НаборЗаписей в фоновое задание и там выполнить метод НаборЗаписейЗадания.Записать(Замещение)? Так же не совсем понимаю почему для регистра накопления используется вызов промежуточного метода: ОбщегоНазначения.ВыполнитьДвижениеПоРегистру(НаборЗаписейЗадания) ? Разве строки НаборЗаписейЗадания.Загрузить(ТаблицаДвижений); будет недостаточно? Заранее благодарю за ответ!
9. linkov 278 26.08.16 17:24 Сейчас в теме
(8) anama_agro,

не совсем понимаю зачем мы предварительно выгружаем набор записей в таблицу значений ПараметрыЗадания.Добавить(НаборЗаписей.Выгрузить()) (на это же тратится дополнительное время)? Почему нельзя просто передать НаборЗаписей в фоновое задание и там выполнить метод НаборЗаписейЗадания.Записать(Замещение)?

Пробовал, надеялся, но ругается на мутабельность значения.

Так же не совсем понимаю почему для регистра накопления используется вызов промежуточного метода: ОбщегоНазначения.ВыполнитьДвижениеПоРегистру(НаборЗаписейЗадания) ? Разве строки НаборЗаписейЗадания.Загрузить(ТаблицаДвижений); будет недостаточно?

Данный участок кода позаимствован из типового. В этом методе при копировании ТаблицыЗначений в НаборЗаписей отсекаются пустые ссылки и пустые значения в случае, если реквизит составного типа. Что благотворно сказывается на самочувствии регистра накопления.
22. abadonna83 45 19.07.17 15:53 Сейчас в теме
Ругается.

{ОбщийМодуль.ЛУ_ВызовСервера.Модуль(9)}: Поле объекта не обнаружено (мТаблицаДвижений)
УчетЗатратРегл


И не понятно как поправить
10. sarun 33 31.08.16 13:01 Сейчас в теме
{ОбщийМодуль.ПроцедурыРасчетаБазыРаспределенияЗатрат.Модуль(2200)}: Ошибка при вызове метода контекста (Выполнить)
РезультатЗапроса = Запрос.Выполнить();
по причине:
{(103, 1)}: Синтаксическая ошибка
12. linkov 278 31.08.16 13:38 Сейчас в теме
(10) sarun,

{ОбщийМодуль.ПроцедурыРасчетаБазыРаспределенияЗатрат.Модуль(2200)}: Ошибка при вызове метода контекста (Выполнить)
РезультатЗапроса = Запрос.Выполнить();
по причине:
{(103, 1)}: Синтаксическая ошибка

Действительно, есть ошибочка, выложил исправленный cf
(у нас не задействован управленческий учет, поэтому ошибка не проявлялась)
11. sarun 33 31.08.16 13:02 Сейчас в теме
Добавил все процедуры. Закоментировал участки где идет асинхронная запись в регистры.
14. sarun 33 01.09.16 06:12 Сейчас в теме
при обычной (не асинхронной записи) в регистры выигрыша в скорости не заметил . УПП 1.3.81.2, по переделам, партионный учет, база 50 Гб.
17. linkov 278 01.09.16 10:54 Сейчас в теме
(14) sarun,

при обычной (не асинхронной записи) в регистры выигрыша в скорости не заметил. УПП 1.3.81.2, по переделам, партионный учет, база 50 Гб.

Эффект ускорения проведения возникает не при большом размере базы, а при большом количестве выпускаемой номенклатуры в месяц и большом количестве затрат.
У нас недавно был апгрейд железа. После чего время проведения стало следующим: без доработок - 8 часов, с доработками не асинхронно - 5 часов, асинхронно - 3,5 часа.
Ну и без доработок размер tempdb по прежнему зашкаливает.

Количество записей в регистрах нашего документа (один месяц):
РегистрНакопления.ВыпускПродукцииБухгалтерскийУчет: 46 136
РегистрНакопления.ВыпускПродукцииНалоговыйУчет: 46 136
РегистрНакопления.НезавершенноеПроизводствоБухгалтерскийУчет: 324 521
РегистрНакопления.НезавершенноеПроизводствоНалоговыйУчет: 324 445
РегистрНакопления.ЗатратыНаВыпускПродукцииБухгалтерскийУчет: 2 646 298
РегистрНакопления.ЗатратыНаВыпускПродукцииНалоговыйУчет: 2 646 297
20. echo77 1906 26.11.16 07:59 Сейчас в теме
(17) Сколько записей в журнале проводок(РегистрБухгалтерии.Хозрасчетный)?
(0) Кстати, процесс можно еще немного ускорить, если добавить правильный индекс в таблицу, в которой выполняется поиск
21. linkov 278 13.12.16 12:06 Сейчас в теме
(20)
Сколько записей в журнале проводок(РегистрБухгалтерии.Хозрасчетный)?

- 62 046 записей

Кстати, процесс можно еще немного ускорить, если добавить правильный индекс в таблицу, в которой выполняется поиск

Рецепт в студию!
23. abadonna83 45 17.10.17 10:57 Сейчас в теме
(21)Согласна надо рецепт.
15. sarun 33 01.09.16 06:15 Сейчас в теме
Оставьте свое сообщение