Ускорение перепроведения документов

Описание незначительной доработки типовой конфигурации, которая привела к ускорению перепроведения документов на 40%.
Дорабатывалась УПП 1.3, но это должно без каких-либо изменений работать и в других конфигурациях с подобным подходом к проведению документов (КА, УТ 10)

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

Список регистров для очистки процедура получала от функции ПроведениеДокументов.МассивРегистровНужноОчистить, поэтому оставалось сделать так, чтобы эта процедура возвращала только те регистры, в которых есть движения.

Для этого в конфигурацию я добавил:

  • регистр сведений ГМ_НепустыеДвижения (по старой традиции этой конфигурации, истоков которой никто не помнит, все нетиповые объекты должны содержать префикс «ГМ_«) с измерениями Документ (ведущее, основной отбор) типа ДокументСсылка и НомерРегистра (основной отбор) типа Число(3,0,Неотрицательное).
  • параметр сеанса ГМ_РегистрыПоНомеру типа ФиксированныйМассив
  • параметр сеанса ГМ_НомераРегистровПоИмени типа ФиксированноеСоответствие
  • справочник ГМ_НомераРегистров с кодом типа Число длиной 3 (должно совпадать с длиной измерения НомерРегистра)

Затем подправил процедуру ПроведениеДокументов.МассивРегистровНужноОчистить:


Функция МассивРегистровНужноОчистить(ДокументОбъект) Экспорт

МассивРегистров = Новый Массив();
//Коровин 09.09.2014
лствИмена=ПараметрыСеанса.ГМ_РегистрыПоНомеру;
лНабор=РегистрыСведений.ГМ_НепустыеДвижения.СоздатьНаборЗаписей();
лНабор.Отбор.Документ.Установить(ДокументОбъект.Ссылка);
лНабор.Прочитать();
Для каждого ТекЗапись Из лНабор Цикл
МассивРегистров.Добавить("РегистрНакопления."+лствИмена[ТекЗапись.НомерРегистра-1]);
КонецЦикла;
лфлНетИнформацииОДвижениях=МассивРегистров.Количество()=0;//Мы, вероятно, в том периоде, где регистр ГМ_НепустыеДвижения пуст
//Коровин 09.09.2014 - конец

Для Каждого Движение ИЗ ДокументОбъект.Метаданные().Движения Цикл
//Коровин 09.09.2014
//МассивРегистров.Добавить(Движение.ПолноеИмя());
Если лфлНетИнформацииОДвижениях ИЛИ Найти(Движение.ПолноеИмя(),"РегистрНакопления.")=0 Тогда
МассивРегистров.Добавить(Движение.ПолноеИмя());
КонецЕсли;
//Коровин 09.09.2014 - конец
КонецЦикла;

Возврат МассивРегистров;

КонецФункции

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

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

лмИменаРегистров.Добавить(ТекРегистр.Имя);
лствНомераПоИмени.Вставить(ТекРегистр.Имя,лЭлемент.Код);
КонецЕсли;
КонецЦикла;
ПараметрыСеанса.ГМ_РегистрыПоНомеру=Новый ФиксированныйМассив(лмИменаРегистров);
ПараметрыСеанса.ГМ_НомераРегистровПоИмени=Новый ФиксированноеСоответствие(лствНомераПоИмени);
КонецПроцедуры

 И, наконец, добавил подписку на событие ПриЗаписи всех регистров накопления:

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


В общем, работает всё так: при запуске 1С процедура ПодготовитьНомераРегистров заполняет параметры сеанса именами и номерами регистров накопления, создавая при необходимости нужные элементы в справочнике ГМ_НомераРегистров. При каждой записи непустого набора в какой-либо регистр накопления подписка на событие ПриЗаписи добавляет для этого регистра и регистратора набора запись в регистр ГМ_НепустыеДвижения. При записи пустого набора с установленным флагом Замещение такая запись, наоборот, удаляется.

При очистке движений документа изменённая процедура ПроведениеДокументов.МассивРегистровНужноОчистить читает из регистра ГМ_НепустыеДвижения все записи по этому документу и возвращает массив регистров, для которых записи есть. Если нет ни одной записи, то работает старый алгоритм — на тот случай, если перепроводимый документ был проведён до того, как в конфигурацию были внесены эти изменения. Это также означает, что из регистра ГМ_НепустыеДвижения можно смело вычищать записи по документам из закрытых периодов, которые трогать не предполагается, так как в случае необходимости перепроведение этих документов пройдёт нормально без всяких дополнительных действий.

Upd

В комментариях мне указали на то, что переименование регистра приведёт к проблемам. Тут я вижу три способа с этим разобраться:

1. Непосредственно перед сохранением изменений конфигурации, включающих в себя переименование регистра, переименовать этот регистр в справочнике ГМ_НомераРегистров. Самый простой и быстрый способ, который, однако, требует помнить о такой необходимости, потому что если сохранить изменения в конфигурацию базы данных, воспользоваться этим способом уже не выйдет.

2. Очистить регистр ГМ_НепустыеДвижения. Способ на случай, когда не воспользовались первым. Справочник ГМ_НомераРегистров тоже можно очистить (я бы очистил), но необязательно.

3. Доработать алгоритм так, чтобы в справочнике ГМ_НомераРегистров также хранилось имя таблицы базы данных соответствующего регистра. При переименовании регистра имя таблицы не поменяется и это можно будет использовать в процедуре ПодготовитьНомераРегистров.

Я эту проблему решать не стал, потому что не помню, чтобы хоть раз менял имя регистра (разве что с ИмяРегистра на УдалитьИмяРегистра, но к этому моменту в регистре, как правило, всё равно не было записей), но эта проблема на самом деле не единственная, есть и более вероятная и неприятная: некоторые обновления релиза включают в себя какую-нибудь обработку записей регистров. При этом могут быть перезаписаны наборы записей в том периоде, для которого в регистре ГМ_НепустыеДвижения нет записей и тогда при перепроведении тех документов этого периода, которые делали движения по перезаписанному регистру, движения по всем остальным регистрам задвоятся. Неприятность этой второй проблемы в том, что вы не узнаете о ней по матюкам программы, как непременно узнаете о первой. Зато если вы про неё в курсе, решить её можно способом №2 из решений первой проблемы.

В качестве профилактической меры можно после каждого обновления релиза запускать обработку, которая бы удаляла записи из регистра ГМ_НепустыеДвижения до определённой даты. Такую обработку по-любому стоит иметь для того, чтобы удалять заведомо старые записи, ибо маленькие регистры быстрее работают.

32 Comments

  1. cargobird

    Интересная идея, попробую…

    Reply
  2. tormozit

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

    Reply
  3. cargobird

    (2) tormozit, на своей практике переименование регистров не встречал ни разу (у вас, видимо, опыта побольше). Копипастом по конфигурации размножаются самые причудливые названия, и чтобы принципиально потребовалось переименование — такого не было…

    Reply
  4. Балабас

    где статистика на сколько увеличилась производительность в результате описанных изменений?

    Reply
  5. tormozit

    (3) cargobird, конечно, у меня опыта побольше =) Думаете мало добавляют свои регистры и потом меняют им наименование, когда поймут, что промахнулись с именем? Тем более в 8.3.6 сейчас переименование стало заметно менее затратным из-за нового помощника.

    Reply
  6. AlX0id

    Чота закрадываются сомнения насчет того, что подобный подход не ведет к постоянным блокировкам на регистре ГМ_НепустыеДвижения.. Каждый документ при записи каждого регистра в него лезет писать свои сочинения.. Или я чот недопонял?

    Reply
  7. deadman66

    Статистика замеров где? И, да, с блокировками не возникает проблем?

    Reply
  8. vasyak319

    (4) Балабас, (7) deadman66, в первой же фразе анонса. Понятно, что это не ровно 40.00000%, но точные цифры тут не имеют смысла — даже на одном и том же сервере, выделенном специально для тестирования и замера (а иначе как вообще было оценить прирост) время заметно плавает от перепроведения к перепроведению, а приводить его одно и то же состояние перед каждым тестом (делаем образ диска — перезагружаем — тестируем — восстанавливаем образ диска — перезагружаем — тестируем…) мне нафиг не упёрлось. А даже если бы упёрлось, это не помогло бы статье, так как это были бы цифры для моей базы и моего тестового сервера.

    На рабочем сервере теперь возможно за ночь перепровести два месяца вместо одного — по мне так вполне достаточная характеристика доработки.

    Reply
  9. vasyak319

    (6) AlX0id, (7) deadman66, были такие опасения и думал, что придётся накладывать управляемые блокировки (тут-то всё будет хорошо на 100%, ибо диапазоны разных документов не пересекаются), но то ли 1С сама их хорошо накладывает, то ли одно из двух (не было времени выяснять, отчего же всё хорошо), но проблем не возникло.

    Reply
  10. AlX0id

    (9)

    Внимательно посмотрел структуру регистра — понял, что недопонял )

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

    Reply
  11. Fox-trot

    так речь про файловый вариант?

    Reply
  12. CrazyCD

    После проведения года попробую вкрячить на КОРП подобное.

    А так ловите плюсик авансом =)))

    Reply
  13. Йожкин Кот

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

    Reply
  14. vasyak319

    (13) Йожкин Кот, а я долгое время думал, ОбработкаПроведения всегда на сервере выполняется. Пока как-то утром не пришел и не увидел, что проведение (Операции — Проведение документов…) встало и ждёт, что я в окне предупреждения ОК нажму. Так я узнал две вещи: что один из тех парней, что дорабатывали эту конфигу, идиот (в ОбработкуПроведения Предупреждение воткнул!) и что перепроведение выполняется на клиенте, потому что иначе тот документ вылетел бы с ошибкой компиляции модуля.

    Какое-то время попереписывался с Хотлайном на тему «Когда такое стало возможно», но получился, как это часто бывает, разговор с глухим.

    Reply
  15. vasyak319

    (10) Fox-trot, нет, но мне интересно, из чего вы сделали такой вывод.

    Reply
  16. Evg-Lylyk

    Не понял зачем так

    Я переписал функцию МассивРегистровНужноОчистить

    Функция МассивРегистровНужноОчистить(ДокументОбъект) Экспорт

    МассивРегистров = Новый Массив();

    Запрос = Новый Запрос;

    ДвиженияДокумента = ДокументОбъект.Метаданные().Движения;

    Для Каждого Движение ИЗ ДвиженияДокумента Цикл

    ИмяРегистра = Движение.ПолноеИмя();

    Если ЗначениеЗаполнено(Запрос.Текст) Тогда

    Запрос.Текст = Запрос.Текст + » ОБЪЕДИНИТЬ ВСЕ «;

    КонецЕсли;

    Запрос.Текст = Запрос.Текст + «ВЫБРАТЬ ПЕРВЫЕ 1″»» + ИмяРегистра + «»» ИЗ » + ИмяРегистра + » ГДЕ » + ИмяРегистра + «.Регистратор = &Регистратор»;

    КонецЦикла;

    Запрос.УстановитьПараметр(«Регистратор», ДокументОбъект.Ссылка);

    Результат = Запрос.Выполнить();

    Возврат Результат.Выгрузить().ВыгрузитьКолонку(«Поле1»);

    КонецФункции

    вроде все теперь очищает только те регистры в которых были записи…

    проверял на документе ПлатежкаИсх (5 раз проводил) ускорилось очень значительно

    теперь проверяю и думаю на что это может повлиять эта доработка

    Reply
  17. Fox-trot

    (15) всю жись с 1с работал тока с трехзвенкой и когда-то сделал вывод, что количество информации = размер базы не влияет на скорость выполнения проги. такие дела

    Reply
  18. vasyak319

    (16) Evg-Lylyk, у меня запрос к одной маленькой таблице (вся эта заморочка с нумерацией регистров как раз для того, чтобы она была настолько маленькой, насколько возможно), а у вас к множеству очень больших. Это тоже должно ускорить перепроведение (что вы и ощутили), так как все СУБД оптимизируют на чтение, а не на запись, но тут вопрос, чего вам нужно больше — написать покороче и постабильнее (у вашего способа нет проблем моего) или выжать из возможности максимум.

    Хотя я допускаю, что ответ вопрос «какой метод быстрее» не такой однозначный и зависит от обстоятельств (мозги сервера, размер базы, характер заполненности регистров и т.п.).

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

    Reply
  19. vasyak319

    (17) Fox-trot, ну, во-первых, конечно, влияет (другое дело, что вы могли этого не заметить), а во-вторых, всё равно непонятно — в статье про количество данных ни слова.

    Reply
  20. Evg-Lylyk

    (18) Понятно. Думаю вашей статьи достаточно.

    Reply
  21. ZOMI
    Reply
  22. vasyak319

    (21) ZOMI, напрашивается встречный вопросик: Вы смотрели, что на самом деле делает эта процедура при взведённом параметре ВыборочноОчищатьРегистры и в каких вообще случаях он взведён, или вы просто увидели, что есть такой параметр и решили, что вот он — золотой ключик?

    Reply
  23. AlX0id

    (21) ZOMI,

    а это выборочное очищение где-то отдельно включается или само по себе работает?

    Reply
  24. vasyak319

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

    Reply
  25. Йожкин Кот

    (14) Проведение, которое инициировано из формы или формы списка док-та для толстого клиента всегда выполняется на клиенте. Для тонкого клиента — всегда на сервере, в не зависимости от точки вызова.

    Reply
  26. tormozit

    (25) Йожкин Кот, не совсем верно. Проведение стандартными командами вызывается

    — для управляемой формы всегда на сервере

    — для обычной формы всегда на клиенте

    Reply
  27. capitan

    Программисты 1С они такие … программисты.

    А те, кто не прогуливал в институте Основы программирования, знают, что каждую задачу можно (и нужно) описать формально, потом блок-схема, потом код.

    Оставив за бортом код мы увидим следующее:

    1. Автор подозревает, возможно небезосновательно, что документы в УПП являются регистраторами для регистров, в которых проводки не делают. Тут камень в огород 1С ибо в каждой книге по программированию написано — не добавляйте лишних регистров/регистраторов.

    И тут вот хорошо бы увидеть отчет, который пройдет по конфигурации, посчитает и выведет в виде — Документ — Количество регистров где он является регистратором — Количество регистров, где реально есть проводки.

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

    Тогда можно уже аргументированно теребить 1С и/или править документ убирая регистры, где он в регистраторах не нужен.

    2. Что сделал автор (надеюсь инфостарт не читают его юзеры и начальник) — он ускорил перепроведение документов, замедлив, в зависимости от количества строк табличной части документа конечно, проведение документа. Не говоря уже о взаимных блокировках регистра сведений при проведении, до которых автору конечно дела нет — он то ночью один работает. За что конечно юзеры, говорят простое человеческое спасибо.

    По хорошему нужно делать регламентное задание, заполняющее регистр сведений и всю кухню с перепроведением завязывать на принак — массовое перепроведение ли это вообще. А так — оставлять возможно стандартный механизм от 1С.

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

    Reply
  28. AlX0id

    (24)

    Ну вот сильно не хочется по коду лазить, если честно ) Тем более, когда не знаешь, как он должен работать. Я просто интересуюсь, может быть где-то есть описание этого использования механизма на уровне пользователя/программиста/администратора — мало ли удастся облегчить людям жизнь, не утруждая свою ))

    Reply
  29. vasyak319

    (27) capitan, бред. Расписал бы подробнее, да вот опровергать буйные фантазии мне ещё лет 10 назад скучно стало.

    Reply
  30. vasyak319

    (28) AlX0id, там немного, потому и предложил взглянуть. Если вкратце, то этот параметр, конечно, никакими настройками не управляется. Где можно (а можно в большинстве реально используемых документов, в частности — ПТиУ и РТиУ) он уже взведён. Влияние оказывает не на все, а где-то на десяток второстепенных регистров.

    В общем, что имел в виду ZOMI своей цитатой процедуры (спасибо, что не модуль целиком), а также ссылкой на загадочные «механизмы типовой», по-моему даже он сейчас не в курсе.

    Reply
  31. vasyak319

    (25) Йожкин Кот, теперь-то я в курсе, но ещё в 8.1 всё проводилось на сервере и такой мрачный прикол с Предупреждением в ОбработкеПроведения даже не компилировался. Там даже Сообщить сначала не компилировалось, это потом сделали очередь сообщений, которую сервер отдаёт клиенту вместе с возвратом управления.

    Reply
  32. capitan

    (29) а ты посмотри на сколько дольше стал проводиться один документ. И поймешь чему за 10 лет научился.

    Reply

Leave a Comment

Ваш адрес email не будет опубликован. Обязательные поля помечены *