Первого числа каждого месяца бухгалтерия торговой компании с 14 магазинами запускала закрытие месяца и уходила обедать. Потом пить чай. Потом — домой. Закрытие заканчивалось к ночи. Восемь часов.
Нас позвали, когда закрытие перестало помещаться в рабочий день. Март закрылся за девять с половиной часов. Бухгалтер досиживала до десяти вечера.
Что такое закрытие месяца и почему оно тяжёлое
Закрытие месяца в 1С:ERP — это цепочка регламентных операций. Каждая зависит от предыдущей. Расчёт себестоимости, распределение затрат, формирование финансового результата, расчёт налогов. Двадцать-тридцать операций, строго по порядку.
Самые тяжёлые — расчёт себестоимости товаров и распределение постатейных затрат. В торговой компании с 14 магазинами, 8 000 наименований товаров и 200 000 документов за месяц — это миллионы строк в регистрах накопления. Запросы к этим данным — основная причина тормозов.
Платформа 1С выполняет закрытие как единую транзакцию. Если что-то упало на середине — откат всего. И бухгалтер запускает заново с нуля. Представьте: четыре часа работы, ошибка на расчёте себестоимости, откат, ещё четыре часа. Шестнадцатичасовой рабочий день.
Диагностика: где именно тормозит
Первое, что мы делаем — замеряем время каждой операции отдельно. Не «закрытие занимает 8 часов», а «расчёт себестоимости — 4 часа 20 минут, распределение затрат — 2 часа, формирование проводок — 50 минут, остальное — 50 минут».
Технологический журнал на время закрытия. Событие SDBL с фильтром по длительности. Все запросы дольше 5 секунд. Результат — лог на 200 МБ. Парсим, группируем по контексту, сортируем по суммарному времени.
Топ-3 запроса, которые забирали 80% времени:
Запрос расчёта партионного учёта. Соединение пяти регистров накопления с группировкой и фильтрацией. Время выполнения — от 3 до 15 минут за один вызов. Вызывался 14 раз (по количеству складов). Итого: 42-210 минут только на этот запрос.
Запрос распределения затрат по подразделениям. Виртуальная таблица оборотов регистра бухгалтерии с субконто. Фильтр в ON-условии LEFT JOIN — та самая проблема, про которую мы уже писали. Время: 20-40 минут.
Контроль остатков при формировании движений. Запрос проверки отрицательных остатков после каждого расчёта. Выполнялся сотни раз, по 10-30 секунд каждый. Суммарно — до часа.
Оптимизация расчёта себестоимости
Главный пожиратель времени — партионный учёт. Алгоритм ФИФО/средняя в 1С:ERP работает итерационно. Берёт приход, сопоставляет с расходом, вычисляет стоимость. Для каждой партии, для каждого склада.
Проблема была в индексах. Регистр накопления «Себестоимость товаров» имел стандартные индексы. Для расчёта нужен был составной индекс по полям Организация + Склад + Номенклатура + Период. Его не было. SQL Server строил план с Index Scan вместо Index Seek. Для таблицы с 12 миллионами записей разница — минуты.
Добавили индекс через расширение конфигурации (чтобы не ломать замок поставщика). Время расчёта себестоимости упало с 4 часов 20 минут до 45 минут.
Второй приём — ограничить расчёт только теми складами, где были движения за месяц. Из 14 магазинов активных за типичный месяц — 11-12. Два-три склада (резервный, транзитный) не имеют движений, но расчёт всё равно отрабатывал по ним полностью. Проверка «есть ли движения» — один лёгкий запрос. Экономия — 15-20 минут.
Оптимизация распределения затрат
Классическая ошибка с LEFT JOIN — фильтр по периоду в ON-условии. Оптимизатор SQL Server не мог «протолкнуть» его внутрь виртуальной таблицы оборотов. Читался весь регистр бухгалтерии за всё время — сотни тысяч строк вместо тысяч.
Решение — вынести выборку из регистра бухгалтерии в отдельный запрос пакета с WHERE-фильтром по периоду, проиндексировать временную таблицу, и уже к ней делать LEFT JOIN. Время распределения затрат: с 2 часов до 12 минут.
Контроль остатков
Контроль отрицательных остатков — механизм 1С:ERP, который после каждой записи проверяет, не ушёл ли остаток в минус. Правильная логика. Но при массовом расчёте, когда за одну операцию закрытия записываются тысячи движений — контроль вызывается тысячи раз.
В настройках учётной политики организации можно отключить контроль остатков для операций закрытия. Не навсегда — только для регламентных операций. Логика: если закрытие рассчитало отрицательный остаток — это не ошибка пользователя, это повод разобраться после. Контроль всё равно сработает при следующей попытке ручного проведения.
Отключение контроля при закрытии сэкономило 50 минут.
Параллельное выполнение
Некоторые операции закрытия не зависят друг от друга. Расчёт амортизации и расчёт себестоимости товаров — параллельны. Формирование проводок по разным разделам учёта — параллельно (если нет перекрёстных зависимостей).
1С:ERP поддерживает параллельное закрытие через настройку «Использовать фоновые задания для закрытия месяца». По умолчанию — выключено. Включили, настроили 4 потока (по числу ядер, доступных серверу 1С). Суммарное время сократилось ещё на 15-20%.
Важно: параллельное закрытие требует больше памяти. Четыре потока — четыре rphost-процесса, каждый потребляет 1-2 ГБ. Убедитесь, что на сервере хватает оперативной памяти — об управлении памятью и других инфраструктурных причинах тормозов 1С написано отдельно.
Результат
Расчёт себестоимости: 4 ч 20 мин → 45 мин (индекс + фильтр складов). Распределение затрат: 2 ч → 12 мин (исправление LEFT JOIN). Контроль остатков: 50 мин → 0 (отключение для регламентных операций). Параллелизм: -20% от оставшегося. Итого: 8 часов → 40 минут.
Бухгалтер запускает закрытие, идёт на кофе, возвращается — готово. Не в десять вечера, а в десять утра.
Три дня работы. Ни одной строки бизнес-логики не изменили. Индекс, исправление запроса, настройка платформы. Типичная оптимизация: не переписывать, а найти узкое место. Об ошибке с LEFT JOIN, которую мы нашли в запросе распределения затрат, подробно написано в статье про WHERE vs ON в LEFT JOIN. Сами индексы и регламент обслуживания — в статье о регламентном обслуживании SQL Server.


