Оглавление
- Необходимые знания для оценки производительности операции «Закрытия месяца» в 1С:ERP
- Проводим отладку для оценки скорости «Закрытия месяца»
- Принципы обработки этапов «Закрытия месяца» в 1С:ERP
- Практические случаи долгого «Закрытия месяца» в 1С:ERP
- Важное замечание по анализу данных
- Дополнительные примеры ситуаций с долгим «Закрытием месяца» в 1С:ERP
Необходимые знания для оценки производительности операции «Закрытия месяца» в 1С:ERP
Наша компания два года выпускает статьи в рамках проекта «От экспертов 1С‑Рарус». С одной стороны, эта статья знаменует собой двухлетний цикл экспертных статей, а с другой, мы впервые покажем, как применять экспертный подход по оценке производительности для самого большого и самого сложного типового решения «1С:ERP Управление предприятием 2». Оказывается, эта область требует знаний больше, чем имеет обычный эксперт по производительности. Правда говорить про эксперта «обычный» уже звучит как оксюморон, но тем не менее, чтобы разобраться в причинах долгого закрытия месяца нужно знать математику, методику и кругозор более широкий, чем вы можете подумать.
Статья предназначена для читателей, которые знают основы теории графов и раздел линейной алгебры, который посвящен решению систем алгебраических линейных уравнений (СЛАУ) с вещественными коэффициентами. Также потребуется знакомство с концептуальным устройством регистров ERP, в том числе с расширенной аналитикой учета. Перед прочтением статьи хорошо будет ознакомиться с материалом из ИТС https://its.1c.ru/db/v8319doc
#bookmark:dev:TI000002121 по решению систем линейных алгебраических уравнений.
Итак, первая статья цикла об оптимизации 1С:ERP представлена вашему вниманию.
Проводим отладку для оценки скорости «Закрытия месяца»
Параметры отладки
Начнем с параметров операций, помогающих в отладке процесса «Закрытия». Для этого на форме «Закрытие месяца» необходимо нажать на кнопку «Настройки» и в выпадающем списке выбрать команду «Настройка параметров операций закрытия месяца». В выпадающем списке «Операция» выберем «Общие параметры для операций». Данная операция доступна для роли «Использование Обработки Операции Закрытия Месяца», установленной, например, для предопределенного профиля «Бухгалтер».
Отметим, что режим отладки, как и практически любая другая отладка, имеет свою цену. Часть отладочных операций может существенно снизить скорость «Закрытия месяца» и даже приводить к неполноценному «Закрытию». Поэтому в штатном режиме режим отладки следует отключать.
Далее, при выборе операции «Распределение затрат и расчет себестоимости», появляется окно настройки параметров:
Опишем каждый из параметров более подробно.
Параметр «Перед расчётом очищать старые расчетные движения»
Заметим, что это не эквиваленция отмене результатов закрытия месяца. Да и, собственно, отменить результаты закрытия так просто не получится, ибо такой операции в ЕРП нет, а простое распроведение документов «Закрытие месяца» к полной отмене не приводит. Поэтому делайте копии базы в состоянии «до закрытия», это бывает полезно.
Стандартное поведение системы следующее:
- Перед началом расчета старые расчетные движения не очищаются, они остаются в ИБ до окончания расчета.
- Новые расчетные движения, формируемые механизмом, помещаются в таблицу значений при достижении определенного размера таблицы значений или при окончании очередного этапа расчета.
- Новые движения из таблицы значений перекладываются во временную таблицу.
- При окончании расчета сравниваются новые (из временной таблицы) и старые (из данных ИБ) движения.
- Перезаписываются движения только у тех документов, у которых есть отличия между старыми и новыми расчетными движениями.
Почему важна именно такая математика? Это прежде всего позволяет экономить время этапа записи сформированных движений.
Давайте осознаем написанный алгоритм на примере.
Пусть после первого закрытия сформировались такие движения в регистре себестоимость
Далее провели приобретение услуг «Доставки»:
В результате после перепроведения формируются движения:
Таким образом первые две записи не изменились и поэтому не были перезаписаны.
Минус такого подхода — уже сформированные движения могли сформироваться неправильно и могут влиять на сам расчет себестоимости, в первую очередь, за счет неправильно сформированных полей партионной идентификации (ключ учета):
- ВидЗапасов,
- Партия,
- АналитикаУчетаПартий,
- АналитикаФинансовогоУчета,
- ВидДеятельностиНДС,
- ДокументИсточник,
- РасчетНеЗавершен.
А также может значительно усложнять попытки расследования причин неправильного закрытия.
Предположим, в предыдущем примере ВидДеятельностиНДС в ВидеЗапасов стоял неправильно — «Продажа не облагается НДС».
Суммы изменены намеренно в приобретении, чтобы показать результат до «Закрытия»:
И после:
Как видим, даже после «Закрытия» ВидЗапасов стоит неправильный, при том, что во всех строках регистра стоит «Продажа облагается НДС».
Поэтому для целей отладки в сложных случаях рекомендуется все-таки очищать старые расчетные движения.
Параметр «При проведении документа не сохранять старые расчётные движения»
Стандартное поведение системы предполагает возможное перепроведение части документов для восстановления хронологической последовательности списания партий. При этом, по аналогии с первым пунктом, старые расчётные движения система старается сохранить.
По причине, описанной в п. 1, в сложных случаях рекомендуется очищать старые расчетные движения и в этом случае.
Для демонстрации изменена «Сумма» в регистре «Себестоимость», без перепроведения документа. Получилось, что «Сумма» и «СуммаНДС» совпадают:
После «Закрытия» получили:
Видно, что движения документа сохранились, а неправильная сумма транслировалась далее и в результате получилась неправильная себестоимость.
Параметр «Контролировать корректность разбиения на порции»
Стандартное поведение системы предполагает разбиение данных на порции для записи в параллельных фоновых заданиях. Настроить количество таких фоновых заданий можно здесь:
При этом может случиться ситуация, когда не все сформированные движения по одному документу попадут в одно фоновое задание. Это можно продемонстрировать строчками кода из процедуры РасчетСебестоимостиПрикладные
Алгоритмы.РазделитьВременную
ТаблицуНаПорции:
Видно, что разделение на порции происходит просто циклом по всей таблице, без какой-либо проверки на «Регистраторы».
В этом случае в лучшем варианте фоновое задание завершится с ошибкой превышения времени ожидания блокировки, в худшем — затрет движения другого задания по этому регистру и регистратору.
Проверка же добавляет следующий код:
Поэтому для целей отладки рекомендуется включать данный флаг, дополнительно проверяющий корректность такого разбиения.
Параметр «Игнорировать некорректные первичные движение документов»
Стандартное поведение системы предполагает перепроведение в случае нахождения ошибок в сформированных движениях документов. К сожалению, устранение данных ошибок может привести к образованию отрицательных остатков в регистрах товародвижения и себестоимости товаров, а следовательно, остановке расчета. Полный список проверяемых ошибок можно увидеть в модуле РасчетСебестоимостиПодготовка
Данных.ИсправитьПроблемы
ВДвиженияхИДокументах (актуально для версии 2.5.7). Пример части кода приведен ниже:
Но иногда хочется посмотреть, а что же дальше:
- Возникают ли ошибки на следующих этапах?
- Корректно ли распределяются затраты по базам?
- Корректно ли формируется себестоимость (с допуском на ошибки)?
Поэтому для целей отладки можно включать данный флаг, чтобы увидеть ответы на эти вопросы, даже если часть движений документов некорректна.
Параметр «Не выполнять оптимизацию данных при расчёте партий»
Стандартное поведение системы предполагает уменьшение объема данных для расчета себестоимости за счет удаления из графов расчета узлов, которые заведомо не могут быть рассчитаны (например, если количество 0, а сумма положительна). К сожалению, это довольно трудоемкий этап.
Поэтому для целей отладки можно пропускать данный пункт, для того чтобы сразу перейти к следующим шагам расчета.
Однако, ни в коем случае нельзя оставлять этот пункт взведенным в «боевом» режиме, т. к. в этом случае система скорее всего либо не завершит расчет, либо завершит его с ошибками.
Параметр «Не выполнять оптимизацию цепочек при расчёте партий»
Стандартное поведение системы предполагает оптимизацию нумерации узлов подграфа. Если не сильно вдаваться в математику, то нумерация вершин должна быть «правильной» (наличие дуги (vi,vj) означает, что i<j). При этом абсолютная правильность возможно только для ациклических графов.
Алгоритм, который в оптимизированном виде применяется в ERP представлен на следующем рисунке:
Топологическая сортировка вершин ориентированного графа без циклов
Эффективный алгоритм нумерации вершин:
- Производим обход графа с помощью рекурсивной процедуры обхода, начиная с произвольной вершины.
- Нумеруем каждую вершину при «прохождении ее назад» максимальным из номеров (то есть нумерация происходит в порядке убывания номеров)
- Повторяем шаги 1 и 2, пока не останется не пройденных вершин.
Отметим, что для сложных производств (и торговли с возвратной тарой) это не выполнимо, поэтому в алгоритме расчета есть ограничения на циклы.
За ограничение рекурсии в алгоритме отвечает параметр «Максимальная длина цепочки движения партии». На картинке приведено значение по умолчанию — 650. Много, согласитесь. Но ниже мы разберем пример, когда и такого уровня вложенности может не хватить.
Правильная нумерация важна для нахождения критического пути в графе, а это в свою очередь важно для правильного формирования матрицы для решения СЛАУ при расчете себестоимости.
К сожалению, это так же довольно трудоемкий этап, поэтому для целей отладки можно пропускать данный пункт.
Параметр «Не выполнять расчёт себестоимости»
Стандартное поведение системы предполагает этап расчета предварительной себестоимости. Заметим, что фактическая себестоимость не оказывает влияния на другие этапы (внутри одного периода), т. к. рассчитывается в последнюю очередь.
Этап расчета предварительной себестоимости по трудоемкости сопоставим с расчетом фактической себестоимости (на самом деле он использует тот же механизм).
Поэтому для целей отладки можно пропускать расчет предварительной себестоимости, чтобы перейти к следующим этапам.
Параметр «Не сдвигать период расчёта по окончании расчёта»
Стандартное поведение системы предполагает очистку регистра сведений «Задания к расчету себестоимости» по окончанию расчета в конкретном периоде. Если каждый раз выполнять отладку в одном конкретном периоде, приходится вручную возвращать задания к расчету (например, перепроведением любого документа в заданном периоде).
Поэтому для целей отладки полезно включать данный флаг, чтобы исключить бесполезную ручную работу.
Параметр «Исправлять пустой регистратор в сформированных движениях»
Стандартное поведение системы предполагает ошибку и остановку этапа записи движений в случае если регистратор в каком-то наборе записей сформированных движений оказался пустым (например, в качестве регистратора выбран документ, который не может иметь движений по данному регистру).
Поэтому для целей отладки рекомендуется включать данный флаг, чтобы запись движений была выполнена успешно. Отметим, что для «боевого» расчета этот флаг следует отключить и разобраться с причиной данной ошибки.
Параметр «Отключение выполнения этапа „Распределение затрат и расчет себестоимости“ в прошлых периодах»
Стандартное поведение системы предполагает ошибку и остановку расчета себестоимости, если расчет в предыдущих периодах выполнен не был.
Иногда (в очень редких случаях) требуется расчет только конкретного периода невзирая на некорректный расчет в предыдущих периодах. Задания к расчету себестоимости за прошлые периоды при этом не удаляются.
Поэтому для целей отладки можно включать данный флаг, для того чтобы «заглянуть в будущее» — возможно там всплывут ошибки, которые нужно поправить в «прошлом», для того чтобы не проводить документы уже в закрытых периодах и вновь не «Закрывать» множество периодов по цепочке.
Параметр «Отключение записи сформированных движений»
Стандартное поведение системы предполагает обязательную запись сформированных движений в регистры.
В некоторых случаях это может быть самый длительный этап расчета, который совершенно не нужен в момент отладки.
Включение этого флага приводит к кэшированию данных для записи в регистр сведений КэшДанныхПартионногоУчета без запуска фоновых заданий записи. За счет того, что запись в регистр сведений происходит быстрее (из-за отсутствия необходимости расчета итогов), время этой операции значительно уменьшается.
Непосредственно запись кэшированных движений можно выполнить потом, с помощью внешней обработки.
Поэтому для целей отладки рекомендуется включать данный флаг, чтобы во многих случаях значительно сократить время процедуры «Закрытия».
Параметр «Формировать промежуточный протокол расчёта после выполнения каждого этапа»
Стандартное поведение системы предполагает формирование «Протокола расчета» только при успешном завершении расчета себестоимости. Зачастую же при проблемах расчета себестоимости до успешного завершения процесс в принципе не доходит (например, очень часто просто «вываливается» процесс).
Поэтому для целей отладки крайне рекомендуется включать данный флаг, чтобы, в том числе, было проще понять, на каком шаге процесс вываливается и при этом получить большой объем очень полезной информации для расследования.
Чтение протокола расчета
Теперь, когда мы продрались сквозь параметры отладки расчета, давайте вернемся к протоколу расчета и попробуем понять зачем он нам, какую он для нас несет пользу.
Для этого прежде всего научимся его правильно читать. Если среди читателей статьи есть эксперты по производительности, то уместна будет аналогия с логом технологического журнала, где содержится масса полезной информации. Но она почти бессмысленна, если не научиться ее правильно читать. Хорошая новость в том, что для чтения протокола расчета никаких специальных инструментов и знания регулярных выражений не потребуется.
Опишем рекомендуемый алгоритм работы с данным отчетом:
1. Если в расчете были диагностированы ошибки их необходимо найти в тексте по символу «!».
Например, рассмотрим картинку:
В данном случае мы видим довольно детальное описание ошибки и даже документ, который привел к ошибке.
К сожалению, далеко не все описания ошибок так конкретны, при этом в любом случае для получения результата все ошибки в «Протоколе расчета» должны быть устранены.
2. Опускаемся в самый низ отчета и смотрим список «ТОП-10 самых длительных этапов».
Посмотрим на следующую картинку:
Картинка может на реальном проекте кардинально отличаться. Важно, что здесь показываются «узкие горлышки» расчета, на которые нужно обратить первоочередное внимание, если вам необходимо оптимизировать расчет.
3. Далее смотрим на первый (самый длительный) этап в предыдущем списке и ищем его описание выше в «Протоколе».
Какие важные блоки в описании шага мы можем использовать?
3.1. Сформированы движения по регистрам:
Очень важный блок, который показывает узкие места при формировании регистров. Здесь важно отметить:
- Чем больше движений формируется, тем медленнее идет процесс закрытия. Соответственно, если здесь мы увидим миллионы записей, то нужно понимать, что запись этих движений займет часы. Также эти движения могут использоваться в алгоритме ниже и влиять на их трудоемкость.
- Подозрение о правильности работы этапа может возникнуть, если объем входящей информации намного ниже, чем количество сформированных движений.
3.2. Сформированы временные таблицы:
Количество записей во временных таблицах важно, как в данном этапе, так и в этапах ниже. При этом подозрение о правильности работы этапа может возникнуть, если размер какой-либо глобальной ВТ (используемой на многих этапах) в данном этапе сильно растет в условиях, когда размер локальных ВТ (используемой только на этом этапе) значительно ниже.
Например, если данных в регистре ВтКэшРасчетныеОбороты
СебетоимостиТоваров на начало этапа было 100тыс, данных в Выборке 50тыс., то количество данных в регистре ВтКэшРасчетныеОбороты
СебетоимостиТоваров на конец этапа в 1 млн должно привести к анализу причин такого поведения на данном этапе. В основном, это проблемы на стыке Данные-Код, поэтому без включения отладки в конфигураторе обычно такие ошибки не разрешить.
3.3. Дополнительная информация об этапе:
В этом блоке содержится наиболее полезная для расследования причин медленной работы этапа информация.
Прежде всего требуется оценить количество входящих данных. Если данных изначально больше ожидаемого количества (не соотносится с количеством движений по регистрам или остатками), то возможные проблемы лежат где-то на предыдущих этапах и нужно вернуться к п 3.2.
Например, если данных по «Остаткам» на этом этапе 1 млн, при этом у вас количество различной номенклатуры вместе с партиями не превышает 100 тысяч (можно взять КонсольЗапросов или «Универсальный отчет» и проверить запросом к РегистрНакопления.ТоварыОрганизаций).
Если же количество данных подозрений не вызывает, то смотрим описание цепочек графа для расчета. Наибольшие подозрения вызывают:
- Большое количество дуг в графе, если оно не коррелирует с «Всего данных».
- Большое значение показателя «макс. дуг в одном подграфе», если оно сопоставимо с «количеством дуг в графе».
- Большое количество итераций поиска подграфов (более 3).
Далее смотрим на результаты обхода цепочек графа. Должно смущать:
- Большая глубина обхода (более 30).
- Ненулевое количество не рассчитанных записей (выдаются соответствующие ошибки).
Отметим, что каждый из вышеперечисленных блоков может появиться не во всех шагах и сильно зависит от данных клиента.
Добавим, что возможны случаи, когда проблемы при закрытии начинаются до того, как стартует запись протокола расчета, т. е. до начала этапа расчета себестоимости. Как расследовать такие случаи (на практическом примере) — мы опишем в следующей статье на эту тему.
Принципы обработки этапов «Закрытия месяца» в 1С:ERP
«Погружаемся» в код соответствующего блока. Точка входа в алгоритм расчета себестоимости находится (для актуального на текущий момент партионного учета версии 2.2) в общем модуле РасчетСебестоимости.РасчитатьВсе():
В принципе код различных этапов может сильно варьироваться, но почти всегда соблюдается следующий принцип обработки:
1. Подготовка данных:
- Получение данных (формирует временную таблицу Данные).
- Получение цепочек из исходных данных (формирует временные таблицы Источники и Приемники).
- Заполнение партий в цепочках (заполняет таблицу ПараметрыРасчета.Распределение
Партий.РасчетныеПартии). - Помещение данных в таблицу-приемник и очистка вспомогательных временных таблиц.
2. Распределение партий по прямым расходам:
- Выборка данных из заранее подготовленных ВТ в п. 1.
- Распределение по себестоимости:
- Подготавливаются таблицы УзлыКорректировкиСебестоимости и СвязиУзловКорректировкиСебестоимости.
- Подготавливаются таблица ДанныеДляНумерации для исходных данных.
- Происходит нумерация узлов графа (узлами графа выступают партии).
- Подготавливаются таблица СтоимостьПартий (к узлам графа добавляется исходная стоимость).
- Распределение партий (ДвиженияБезСтоимости).
- Подготавливаются таблица ДанныеДляНумерации для распределенных партий (ВТУзлыСПолямиРаспределения).
- Происходит нумерация узлов нового графа.
- Подготавливаются таблица ВТРезультатРасчета
Предварительный (к узлам графа добавляется исходная стоимость). - Решение системы линейных уравнений для расчета фактической стоимости.
- Загрузка решения во временную таблицу ТаблицаРешений.
- Подготавливаются таблица ВТЦеныУзлов.
- Сохранение движений.
- Сохранение движений и очистка вспомогательных временных таблиц.
Несколько более подробно (но для релиза ERP 2.4.10):
3. Распределение партий по дополнительным расходам происходит по алгоритму, аналогичному п. 1. Расчет фактической себестоимости для доп. расходов не производится.
4. Распределение партий по партиям прочих расходов происходит по алгоритму, аналогичному п. 1. Однако для постатейных затрат также происходит расчет фактической себестоимости. по алгоритму аналогичному для прямых расходов.
Практические случаи долгого «Закрытия месяца» в 1С:ERP
Экспоненциальный рост времени расчета себестоимости при закрытии месяца
Для расследования проделываем все, что было описано выше, как в части параметров отладки, так и, конечно, в части получения протокола расчета и его первичного прочтения.
Дальше погружаемся в код еще глубже и обращаем внимание на строку в протоколе расчета «РасчетСебестоимостиПрикладные
Алгоритмы.ПостроитьЦепочкиДвижений». Отметим, что модуль ПостроитьЦепочкиДвижений вызывается практически для всех этапов расчета себестоимости и является одним из ключевых узких мест расчета. Выше мы описали, как можно было добраться до этой строчки кода.
Для понимания причин сложности рассмотрим следующий запрос:
Проблемой данного запроса является «Внутреннее соединение» таблицы «Данные» с собой.
Далее по тексту модуля идет двойной цикл:
Как видим, шаблон &Условия формируется динамически в зависимости от «Приемника» и «Источника».
Пример таблицы типов приемника и типов источника приведен ниже:
Проблемы возникают в случае, если эти условия получаются слишком общими, например, для ТипаПриемника = «Возврат без документа источника» и ТипаИсточника = «Потребление» условие выглядит:
И в случае, если, к примеру, в системе есть партииобразующие документы с тысячами строк, ВнутреннееСоедниение может вернуть чрезвычайно много данных.
В одном из наших проектов в ВТЦепочки32 попало 75 млн данных, при том, что строк с ТипомПриемника = «Возврат без документа источника» было 3 тысячи записей, а строк с типом = «Потребление» было 34 тысячи записей. И т. к. направления деятельности и ВидДеятельностиНДС везде совпадали, мы получили почти полное произведение этих записей.
Поэтому в этом коде из-за теоретического максимума, равного произведению количества записей, возможность получить миллионы записей практически на любой связке данных очень велика.
Обращаем внимание, что это контринтуитивное поведение «Внутреннего соединения», от которого обычно не ждут взрывного роста записей, может сильно ввести в заблуждение — мы, к примеру, на эти грабли наступили.
Заметим, что в релизе 2.5.7 проблема остается актуальной.
Первичное решение, предложенное нами, сводилось к необходимости избавления от документов, приводящих к таким типам движений (Возврат между организациями, Возврат товаров между организациями и Отчет комиссионера (агента) о продажах), однако клиента такое решение не устроило, вследствие того, что перемещение маркированных товаров требуется контролировать онлайн из-за требований законодательства. Поэтому нам пришлось влезть в стандартный код модуля ПостроитьЦепочкиДвижений и сделать там такой «костыль»
Обратная сторона такого решения — необходимость копировать данный код в обновлениях до тех пор, пока данную ошибку не исправят.
Рост времени «Закрытия месяца» из-за раздельного учета НДС
Рассмотрим РасчетСебестоимостиНДС.
РаспределитьНДСПоСебестоимости.
Еще один пример потенциально проблемного запроса выглядит так:
Вновь мы видим «Внутреннее соединение». Проблема заключается в части кода, в которой этот запрос размещен (для экономии места запрос выше заменен на троеточие):
Запрос в цикле — это уже сама по себе проблема для производительности, но еще большую проблему может вызывать «Внутреннее соединение» по узкому условию «НомерУзлаИсточник = УзелПриемник».
Например, на одном из наших проектов, 1,4 млн записей превращались в 48 млн записей к 23й итерации:
Сам по себе этот цикл, как видим, выполнялся сравнительно быстро, зато следующий за этим шагом этап РассчитатьСистемы
ЛинейныхУравнений длился 1 час и 12 минут. Это важно для понимания того факта, что, естественно, существует прямая зависимость от количества входных данных и времени расчета СЛАУ.
Напомним, из курса вычислительной математики: трудоемкость точных методов решения СЛАУ в общем случае кубическая. Трудоемкость же итерационных методов в общем случае квадратическая, но сильно зависит от собственных чисел исходных матриц и получаемого из них числа обусловленности вплоть до невозможности сходимости метода.
В данном конкретном примере решение, предложенное нами, было продиктовано модулем, из которого вызывался данный код. Очевидно, что это расчет НДС и уже менее очевидно, но можно посмотреть по трассировке кода, что он связан с «Раздельным учетом товаров по налогообложению НДС». Настраивается данный параметр для каждой организации отдельно на вкладке «Учетная политика и налоги» в разделе «Настройка учета НДС»:
В нашем конкретном случае нам повезло, что Заказчик смог обойтись без установки данного флага. Это решило проблему. Однако отметим два момента:
- Еще более сложной и трудозатратной для расчёта, а значит и критически сложной для анализа является следующая настройка — «Раздельный учет постатейных производственных затрат по налогообложению НДС».
Если ваша Организация не может обойтись без этого флага, имеет широкую номенклатуру постатейных производственных затрат и различные виды деятельности по НДС, то готовьтесь, что этап распределения НДС по себестоимости будет занимать львиную долю времени расчета себестоимости. - Если снять данный флаг нельзя, то отметим куда же нужно копать далее. Важными таблицами для понимая являются две таблицы:
- ДанныеДляНумерации,
- СтоимостьПартийНДС.
Первая таблица является практически клоном таблицы ВТУзлыПартийНДС, в которой важно поле НомерУзла и содержит все партии НДС, которые не были распределены до этого момента, включая предыдущие месяцы «без ограничений по сроку». Ключевое здесь «без ограничений по сроку», т. е. чем больше ваша база и чем больше в ней осталось «хвостов» НДС, тем значительнее будет эта таблица и эта проблема не лечится «Обрезкой базы».
Вторая таблица является немного урезанной копией таблицы ПриходыТоваровНДС2_4_
ДляРешенияСЛУ. Эта таблица ограничена приходами товаров только за текущий месяц, но также у конкретных клиентов может быть весьма большой (считаем большими все таблицы, у которых больше 1 млн записей). Накладываясь, таблицы ДанныеДляНумерации и СтоимостьПартийНДС могут приводить к бесконечному количеству комбинаций, и если с СтоимостьПартийНДС бороться практически нельзя, т. к. это конкретные приходы, то остается минимизировать ДанныеДляНумерации. Как? Это уже другая история, которая зависит от конкретного случая.
Длительное «Закрытие месяца» из-за отнесения на себестоимость общехозяйственных расходов
Рассмотрим РасчетСебестоимостиПрикладные
Алгоритмы.РассчитатьПартии
ПоГруппамПодграфов.
Как же не упомянуть проблемы в рекурсии?
Процедура РассчитатьПартии
ПоГруппамПодграфов выполняет расчет по несвязанным между собой подграфам, из которой для обхода подграфов и расчета их узлов вызывается процедура РассчитатьПартиюРекурсивно.
Естественно, везде, где есть рекурсия, возникает проблема ограничения глубины рекурсии. Для этого используется уже описанный ранее флаг «Максимальная длина цепочки движения партии». Если такое происходит в протоколе расчета, мы увидим ошибку «При расчете цепочек движения партий сработало ограничение на максимально допустимую длину цепочки».
Можно попытаться решить эту проблему, увеличив значение по умолчанию 650 (помните, мы обещали показать случай, когда даже такого уровня вложенности может не хватить) на 1000, либо даже 2000, но это не решение задачи, а «заметание мусора под ковер». И, конечно, это может привести к росту времени выполнения процедуры расчета. Но, к сожалению, в большинстве случаев это не помогает, поэтому обычно необходимо погружаться в проблему глубже.
Для понимания процесса попробуем разобрать его с помощью примера:
На данной схеме представлен упрощенный пример расчёта графа себестоимости. Если упрощенно мы имеем три цеха:
- прачечная,
- котельная,
- ремонтный цех.
Все они оказывают работы друг другу (прачечная стирает спецодежду, котельная всех греет, ремонтный цех всех ремонтирует).
В итоге мы можем выделить 3 несвязных подграфа — показанные 3-мя разными цветами. Каждый из таких подграфов затем рассчитывает партии и, вследствие оказания работ друг другу, получается «неразрешимая» зацикленность.
Крайне рекомендуем сделать этот пример своими руками в 1С:ERP и посмотреть (в том числе в отладчике) что происходит.
В конкретном случае (у реального заказчика) эта проблема привела к такой ситуации:
Видно, что одна и та же продукция имеет разницу в себестоимости в 3 раза (списанные прямые расходы на партии при этом не отличаются).
Решение этой проблемы следует распутывать из самой цепочки, которая хранится в Данные.ЦепочкиДвижений.
Общий принцип решения — разрыв такой цепочки, что можно сделать, например, превратив номенклатурные затраты в постатейные, а те, в свою очередь, списывать на административно-хозяйственные затраты, а не на себестоимость.
Надо отметить, что данную рекомендацию следует рассматривать как системную. И прежде, чем вы захотите относить на себестоимость любую списанную на уборку помещений тряпочку, хорошенько подумайте. Это может привести не только к плохим с точки зрения производительности последствиям, но и не является правильным с методической точки зрения.
В качестве еще одной рекомендации может выступить увеличение параметра «Максимальная порция выборки данных при расчете партий», например, в 2 и более раза.
Важное замечание по анализу данных
Отдельно хочется отметить еще один очень важный момент отладки — получение промежуточных данных из временных таблиц. Как уже было отмечено выше, временные таблицы делятся на:
- Глобальные, использующиеся на многих этапах, часть из которых сильно видоизменяется от этапа к этапу.
- Локальные, использующиеся в рамках данного этапа и удаляющиеся при его завершении.
Для получения данных из ВТ самый простой способ заключается в использовании МенеджерВременныхТаблиц.
Для этого в режиме отладки на точке останова можно запустить монитор переменных и запустить примерно следующий код:
Запрос.МенеджерВременныхТаблиц.Таблицы["ИмяВременнойТаблицы"].ПолучитьДанные().Выгрузить()
Основная проблема отладки заключается в том, что в «Закрытии месяца» ВТ могут быть очень объемными, поэтому прежде, чем выполнить предыдущий код, можно проверить количество данных, если добавить к предыдущему запросу «Количество()».
Если данных в ВТ не очень много — примерно 40-50 тысяч строк (от 10 столбцов) полученную временную таблицу еще вполне возможно выгрузить стандартным способом в таблицу в конфигураторе и сохранить ее далее в Excel.
Если данных в ВТ больше 50 тысяч, но меньше 200 тысяч строк (от 10 столбцов), выгрузку таблицы в конфигураторе еще можно дождаться (ждать придется очень долго), но сохранить в Excel скорее всего уже не получится.
Если данных в ВТ больше 200 тысяч строк, то выгрузку таблицы в конфигураторе, скорее всего, не дождаться.
В этом случае нам может помочь следующий код:
Т. е. мы таблицу значений конвертируем в табличный документ и непосредственно пишем его в файл Excel. Данный подход гарантированно работает для любого количества строк, единственное нужно помнить, что у Excel есть ограничение в 2 млн строк на один лист в документе, поэтому особо большие таблицы придется выгружать в несколько итераций, либо написать свой модуль, к примеру, выгрузки непосредственно в базу данных.
Дополнительные примеры ситуаций с долгим «Закрытием месяца» в 1С:ERP
В качестве дополнения хотели бы привести кейсы, с которыми мы сталкивались, и которые могут стать проблемными, если вы используете эти механизмы:
- Возвраты от клиентов/организаций/..., не привязанные к партии. Т. е. не привязанные к реализации или заказу. Любой такой возврат пытается восстановить НДС и без партионной принадлежности делает это просто со всей имеющейся номенклатуры. А ее может быть много...
- Документы с отрицательным количеством. По той же причине, что в п. 1, с разницей в том, что для них никогда не подбираются партии, что автоматически приводит к проблемам.
- Статьи затрат, распределяемые на себестоимость, у которых аналитика не обязательна. Такую аналитику зачастую забывают поставить, что естественно приводит к распределению затрат на всю имеющуюся номенклатуру на складе/в подразделении.
- Возвратная тара, если она передается и принимается с какой-то ценностью. Мало того, что при возврате тары возникает п. 1, так еще и дополнительно тара может в периоде реализовываться и возвращаться несколько раз, с различной номенклатурой. Что в итоге может приводить к неразрешимым циклам в расчете.
- Выпуск продукции или работ, потребляемых непосредственно в этом подразделении, без изменения аналитики учета затрат. Это вызывает проблему «выпуска на самого себя» (или «змеи, пожирающей свой хвост»), что при закрытии периода может вызвать построение СЛУ, не имеющей решения.
- Использование опции «Распределение дополнительных расходов по выбывшим товарам» (НСИ и администрирование — Финансовый результат и контроллинг — Учет товаров). Выбывших товаров может быть много, ограничения на период нигде не устанавливается, выводы можете сделать сами.
Ну и, конечно, нужно отметить «сервисные» функции и особенности, которые следует учитывать при «Закрытии месяца». К ним можно отнести следующее:
- Должны быть рассчитаны итоги по всем регистрам на нужный месяц.
- Закрытие месяца — тяжелая процедура, которая нагружает систему саму по себе. Требовать от нее быстроты во время полноценной работы большого количества пользователей бесполезно. Поэтому «Закрытие месяца» обычно ставят либо на ночь, либо на выходные, когда активность пользователей минимальна.
- Изменение в документах/регистрах в момент «Закрытия месяца» в целевом периоде приводит к остановке процедуры закрытия, а если оно происходит довольно долго (> суток), то это весьма неприятно для всех участников процесса. При этом дату запрета установить на следующий период нельзя, т. к. это приведет к невозможности самого закрытия. Поэтому, если п. 2 не выполним, то необходимо решать проблему организационными методами (регламент «Закрытия», в котором предусмотрены наказания за вмешательство в процесс закрытия)
- «Закрытие месяца» — именно та процедура, в которой сильно помогает параллелизм запросов на уровне SQL. Поэтому если в «обычной» жизни у вас max degree of parallelism = 1(2/3), то для «Закрытия» рекомендуется ставить его в 0(4/5/...). Более подробно этот кейс будет рассмотрен в следующей статье по проблемам «Закрытия месяца».
Продолжение в статье: Расследование и оптимизация расчета операции «Закрытие месяца» в 1С:ERP. Часть II.
От экспертов «1С-Рарус»