При работе с крупными или активно развивающимися нетиповыми конфигурациями 1С на обычных формах разработчики часто сталкиваются с проблемой неэффективности стандартного журнала регистрации (ЖР). Основные претензии к нему — крайне низкая скорость фильтрации данных (на получение простых отчетов может уходить от нескольких минут до получаса) и отсутствие детальной истории изменений объектов (непонятно, какие именно реквизиты и значения были изменены). В этой статье мы подробно разберем, стоит ли писать свою систему логирования с нуля, с какими критическими проблемами вы столкнетесь при разработке и какие современные альтернативные варианты существуют в экосистеме 1С.
Прежде чем принимать решение о написании собственного велосипеда, давайте разберем, почему стандартный ЖР начинает тормозить. Начиная с версии платформы 1С 8.3.5, по умолчанию используется новый формат журнала регистрации на базе СУБД SQLite (файлы с расширением .lgd). Однако встроенный драйвер платформы работает с этой базой данных весьма специфически:
VACUUM для SQLite. Это означает, что даже после удаления старых записей из ЖР размер файла на диске не уменьшается, а структура индексов дефрагментируется. При достижении файлом размера более 1–2 ГБ скорость чтения падает в геометрической прогрессии.Альтернативный шаг: Первое, что мы рекомендуем сделать для ускорения работы — попробовать вернуть старый последовательный текстовый формат журнала регистрации (файлы с расширением .lgp). Сделать это можно через параметры запуска конфигуратора или утилиты администрирования. Текстовый формат делит логи по периодам и записывает данные последовательно без использования механизмов СУБД, что в ряде высоконагруженных систем значительно повышает скорость записи и решает проблему взаимных блокировок при фиксации логов.
Если вы всё же решили писать собственный журнал регистрации внутри базы 1С (например, используя РегистрСведений и ПодпискиНаСобытия на уровне объектов), приготовьтесь к решению сложнейших архитектурных проблем:
Стандартный журнал регистрации платформы работает независимо от транзакций базы данных. Если пользователь проводит документ, платформа делает запись в ЖР. Если в процессе проведения происходит ошибка и транзакция SQL откатывается, документ остается непроведенным, но запись в ЖР о попытке проведения сохраняется. Это позволяет расследовать причины сбоев.
Если же вы записываете свой лог в обычный РегистрСведений внутри транзакции записи объекта (например, в событии ПриЗаписи), то при возникновении ошибки транзакция SQL откатит и изменения объекта, и саму запись в вашем регистре логов. Вы просто не узнаете, что пользователь пытался совершить действие и на каком этапе произошел сбой. Решение этой проблемы через вызов фоновых заданий или внешних источников данных существенно усложнит архитектуру конфигурации.
Каждая запись в созданный вами регистр логов — это полноценная транзакционная операция в SQL-базе, требующая наложения блокировок и обновления индексов таблицы. При одновременной работе десятков или сотен пользователей, записывающих различные документы, постоянные обращения к одной и той же таблице логов неизбежно приведут к эскалации блокировок, взаимным блокировкам (Deadlocks) и полному зависанию системы. Написание лога в файл на диске выполняется на порядок быстрее и безопаснее с точки зрения транзакционной целостности базы данных.
Давайте проведем простой математический расчет. Предположим, средний размер XML-представления измененных реквизитов одного документа составляет 100 Кб. Если в день в базе проводится 1000 документов, и каждый документ перезаписывается в среднем 3 раза в процессе согласования, то только за один день ваш регистр логов вырастет на:
100 Кб * 1000 * 3 = 300 МБ в день!
За год база вырастет более чем на 100 ГБ только за счет таблиц логирования. Это приведет к усложнению резервного копирования, росту стоимости дискового пространства и замедлению обслуживания всей СУБД.
Прежде чем писать код, давайте проанализируем штатные возможности современной платформы 1С 8.3. В платформу встроен мощный оптимизированный механизм «История данных». Его ключевые особенности:
Однако учитывайте минус: версия создается при любой программной или интерактивной записи объекта, даже если фактически данные в реквизитах не поменялись. Это может приводить к избыточному накоплению записей при частых групповых обработках.
Если у вас есть возможность интегрировать элементы БСП (Библиотеки стандартных подсистем), обязательно рассмотрите подсистему «Версионирование объектов». Она сохраняет полные версии объектов в XML-формате в регистре сведений ВерсииОбъектов. Несмотря на то, что это решение изначально ориентировано на управляемые формы, его бэкэнд-логику (обработки и общие модули) можно успешно вызывать из обычных форм вашей нетиповой конфигурации. Главный минус аналогичен — потенциально быстрое раздувание базы данных и дополнительная нагрузка на СУБД при массовом перепроведении документов.
В крупных HighLoad-системах ведение логов внутри основной транзакционной базы считается критической ошибкой проектирования. Современный подход заключается в выгрузке логов во внешние аналитические системы:
LIKE составляет доли секунды.ibcmd eventlog, позволяющий транслировать новые события журнала регистрации в форматы XML или JSON в режиме реального времени. Это полностью избавляет от необходимости писать самописные парсеры файлов логов.Если вам критически важно иметь простую историю изменений по узкому списку реквизитов ключевых справочников и вы не хотите использовать тяжелые внешние системы, давайте разберем, как правильно реализовать это с минимальной нагрузкой на транзакции.
Создадим подписку на событие ПередЗаписью для выбранных объектов метаданных. В качестве обработчика напишем процедуру, которая будет сравнивать текущие данные объекта с данными в базе данных и записывать только измененные реквизиты.
Пример программного кода для сравнения и фиксации изменений:
Процедура ПередЗаписьюОбъектаДляЛогирования(Источник, Отказ) Экспорт
// Исключаем работу механизма при загрузке данных (например, через обмен)
Если Источник.ОбменДанными.Загрузка Тогда
Возврат;
КонецЕсли;
// Если объект новый, фиксируем только факт создания
Если Источник.ЭтоНовый() Тогда
ЗаписатьСобытиеВЛог(Источник, "Создание", "Объект создан впервые");
Возврат;
КонецЕсли;
// Получаем старую версию объекта напрямую из базы данных для сравнения реквизитов
СсылкаНаОбъект = Источник.Ссылка;
СтарыйОбъект = СсылкаНаОбъект.ПолучитьОбъект();
Если СтарыйОбъект = Неопределено Тогда
Возврат;
КонецЕсли;
Изменения = Новый ТекстовыйДокумент;
МетаданныеОбъекта = Источник.Метаданные();
// Проверяем изменение стандартных реквизитов и реквизитов объекта
Для Каждого Реквизит Из МетаданныеОбъекта.Реквизиты Цикл
ИмяРеквизита = Реквизит.Имя;
Если Источник[ИмяРеквизита] <> СтарыйОбъект[ИмяРеквизита] Тогда
Изменения.ДобавитьСтроку(СтрШаблон("Изменен реквизит '%1'. Старое значение: '%2', Новое значение: '%3'",
ИмяРеквизита, СокрЛП(СтарыйОбъект[ИмяРеквизита]), СокрЛП(Источник[ИмяРеквизита])));
КонецЕсли;
КонецЦикла;
// Если зафиксированы изменения, записываем их в оптимизированный регистр сведений
Если Изменения.КоличествоСтрок() > 0 Тогда
ЗаписатьСобытиеВЛог(Источник, "Изменение", Изменения.ПолучитьТекст());
КонецЕсли;
КонецПроцедуры
Процедура ЗаписатьСобытиеВЛог(ОбъектИсточник, ТипСобытия, ОписаниеСобытия)
// Для снижения блокировок запись лога рекомендуется выносить во внешние сессии
// или использовать максимально быстрый и неиндексируемый по текстовым полям регистр сведений
НаборЗаписей = РегистрыСведений.МойЖурналРегистрации.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Период.Установить(ТекущаяДатаСеанса());
НаборЗаписей.Отбор.Объект.Установить(ОбъектИсточник.Ссылка);
НоваяЗапись = НаборЗаписей.Добавить();
НоваяЗапись.Период = ТекущаяДатаСеанса();
НоваяЗапись.Объект = ОбъектИсточник.Ссылка;
НоваяЗапись.Пользователь = ПараметрыСеанса.ТекущийПользователь;
НоваяЗапись.ТипСобытия = ТипСобытия;
НоваяЗапись.Описание = ОписаниеСобытия;
Попытка
НаборЗаписей.Записать(Истина);
Исключение
// Логируем ошибку записи лога в стандартный ЖР, чтобы не прерывать транзакцию пользователя
ЗаписьЖурналаРегистрации("Ошибка записи собственного лога", УровеньЖурналаРегистрации.Предупреждение,,, ОписаниеОшибки());
КонецПопытки;
КонецПроцедуры
Разработка собственного полноценного журнала регистрации «с нуля» внутри базы данных 1С сопряжена с серьезными рисками снижения производительности всей системы. Прежде чем приступать к написанию кода, мы рекомендуем выполнить следующие шаги:
.lgp (текстовый) для ускорения чтения и записи логов.