Хочу вернуться к старой проблеме с хранением журнала регистрации 1С в формате SQLite. История стара как мир, но мы нет-нет, а продолжаем с ней сталкиваться, поскольку очень часто большие информационные системы работают далеко не на самых свежих версиях платформы 1С, а администраторы системы не уследили за форматом хранения журнала регистрации (ЖР).
Формат журнала регистрации в виде файла *.lgd, представляющего собой отдельную БД SQLite, появился в платформе 8.3.5 еще в 2014 году и существовал до платформы 8.3.22 (2022 год). Точнее, до последнего времени существовало два варианта хранения журнала – обычный последовательный и SQLite, на выбор пользователя. А начиная с 8.3.22, в платформе была исключена возможность конвертации журнала в формат SQLite и его дальнейшая поддержка.

Соответственно, если у вас версия платформы в этом промежутке, то есть ненулевая вероятность, что ЖР хранится в формате SQLite. Чем это плохо и что с ним не так?
Наглядно посмотрим к чему может приводить ЖР в формате SQLite в многопользовательской нагруженной системе и выясним как администраторам и разработчикам понять, что проблема просадки производительности связана с чтением ЖР.
Вообще, вот так просто, по разным счетчикам производительности проблема не особо очевидна, и многие действительно не понимают в чем же дело. Как и любая другая проблема производительности внешне она выглядит совершенно типично – пользователи не могут работать и активно жалуются на зависания в поддержку. При этом специалисты чего-то такого страшного в мониторинге как на сервере приложений, так и на сервере СУБД могут и не увидеть. Ну немножко повышена очередь к диску, повышен уровень количества транзакций. Но опять же не критично. Процессор в норме, память в достатке.
Рассмотрим реальный пример. Серверы 1С и SQL расположены на одной машине, хотя на самом деле – это не важно. Поддержка регулярно получает валы заявок. Не понимает природу проблемы, перегружает сервер, срубает какие-то фоновые задания, но эффект кратковременный.
Для анализа будем использовать нашу палочку-выручалочку – мониторинг Perfexpert.
Посмотрим сначала на недельный срез по основным системным счетчикам. На самом нижнем выведен график чтения файла журнала регистрации *.lgd процессом rmngr.exe.

Если бы поддержка с самого начала знала о возможном негативном влиянии журнала регистрации, то обязательно увидела бы, эти синие пики, которые полностью приходятся на волны заявок с жалобами пользователей на сильные зависания системы. И сделали бы правильный вывод. Но такого графика у поддержки может и не быть (есть в штатном функционале Perfexpert) и нужно ориентироваться на другие, косвенные данные.
Приблизим последние два пика (желтая область на рисунке выше), как более затяжные и заметные и посмотрим, что же там:

После увеличения пиков оказалось не два, а три. Разберем только первый.
Начнем смотреть системные счетчики. Наша цель – быстро понять и обнаружить признаки, указывающие на возможные проблемы с журналом регистрации. Графики пронумерованы от 1 до 6, чтобы легче ориентироваться по тексту.
Берем пик на графике чтения ЖР (№6) и смотрим, как на всём протяжении чтения файла *.lgd менялись остальные счетчики:
Запросов/сек (график №3) – количество упало с нескольких тысяч до сотен и даже десятков запросов в секунду. Т.е. фактически до нуля. Это очень важный маркер, запомним его.
Можно сделать ошибочный вывод, что сервер SQL подзавис, не пропускает через себя запросы и является той самой причиной. Но это не так. Идем дальше.CPU (график №1). Поскольку в этом примере оба сервера (SQL и 1С) расположены на одной машине, то падение нагрузки не особо ярко бросается в глаза. Но оно есть, запросы со стороны сервера 1С практически перестают поступать на сервер SQL.
Транзакций /сек (график №5). На сервере SQL фактически копится очередь из выполненных транзакций, которые он не может передать обратно серверу 1С.
Длина очереди на диске (график №4). Файл *.lgd хранится на диске <С> , поэтому очередь смотрим на нем. Она совсем немного возросла, но пик совпадает с чтением ЖР (график №6). Поэтому нужно тоже обращать внимание на этот счетчик.
Теперь смотрим на вспомогательные панели мониторинга Perfexpert, где расположена информация о сессиях 1С,SQL и другая дополнительная информация. Нас интересует закладка "I/O дисковой подсистемы":

По нагрузке на диск мы видим, что больше всего операций чтения/записей выполняет процесс rmngr.exeс файлом 1cv8.lgd – журналом регистрации 1С.
А переключившись в этой же панели на закладку «Сессии 1С \ Упр. блокировки» мы видим в сессиях 1С пользователя «*** Руслан(DEV)», который как раз и работает в этот момент с журналом регистрации. Вероятно, один из разработчиков. Всё совпадает:

Подведём итоги по счетчикам. Каждый из них по отдельности (графики 1–5) ничего не говорит. И только лишь вкупе они указывают на признаки, говорящие о проблемах с журналом регистрации. Если вы можете построить свой счетчик с чтениями к файлу *.LGD, тогда будет намного легче идентифицировать проблему.
Ниже приведу еще парочку примеров со счетчиками из других систем, когда возникала проблема с журналом регистрации формата SQLite, и были те же признаки: падение кол-ва запросов в секунду на сервере СУБД, рост кол-ва транзакций, снижение нагрузки CPU. И каждый раз счетчик «Чтение журнала транзакций» подтверждает это:


Интересно знать почему так происходит?
Кому нет, можно не читать дальше и просто принять за аксиому, что для нагруженных и многопользовательских ИТ-систем журнал регистрации в формате SQLite – это зло и нужно оперативно перейти на обычный последовательный формат журнала. А кому интересно, предлагаю чуть-чуть поковырять SQLite и смоделировать ситуацию с чтением ЖР.
Что такое ЖР? Это лог изменений, которые делают пользователи. Чем больше система, тем больше изменений. Размер журнала может быть большим и очень большим – сотни и даже тысячи гигабайт.
А что такое чтение данных из файла базы данных *.LGD? Это запрос с различными отборами по таблицам. Чем шире условия фильтрации (типа «покажи мне действия Иванова за весь период с начала времен»), тем хуже последствия. Запросы на чтение (!) выполняются долго, а остальные пользователи не могут в этот момент ничего в журнал записать.
В формате SQLite ЖР состоит из 16 таблиц. Все таблицы в ЖР хорошо нормализованы. Основной является таблица EventLog, в которой два десятка колонок.

Для моделирования это избыточно и не удобно. Поэтому, чтобы показать суть проблемы, мы создадим в базе SQLite всего одну таблицу из трех колонок, заполним её и будем одновременно читать из нее и писать. Этого достаточно.
[ID] – инкрементальный индекс
[date] – дата, для колонки создадим индекс (но не сразу).
[text] – просто текстовая строка, единая для всех записей.

Заполним таблицу так: один миллион строк на каждый день августа 2025. Количество дней – 31, итого получим 31 миллион строк.
Скрипт на Python для заполнения таблицы
import sqlite3
import datetime
# import random
# import string
def create_and_populate_table(db_name='my_database.db'):
# Подключаемся к базе данных (или создаём новую)
conn = sqlite3.connect(db_name)
cursor = conn.cursor()
# Создаём таблицу
cursor.execute('''
CREATE TABLE IF NOT EXISTS records (
id INTEGER PRIMARY KEY AUTOINCREMENT,
date DATE NOT NULL,
text TEXT NOT NULL
);
''')
# # Создаём индекс
# cursor.execute('''
# CREATE INDEX idx_date ON records (date);
# ''')
conn.commit()
# Получаем текущую дату и определяем первый день месяца
today = datetime.date.today()
first_day = today.replace(day=1)
# Определяем последний день текущего месяца
if today.month == 12:
last_day = today.replace(year=today.year + 1, month=1, day=1) - datetime.timedelta(days=1)
else:
last_day = today.replace(month=today.month + 1, day=1) - datetime.timedelta(days=1)
# Cтроковая константа (можно изменить на нужную вам)
text_content = "Текстовая константа для всех записей"
# Подготовка пакетной вставки
batch_size = 10000 # Количество записей для вставки за один раз
total_records_per_day = 1000000 # Количество записей для заполнения одного календарного дня
batch_count = total_records_per_day // batch_size
# Заполняем таблицу данными за каждый день месяца
current_date = first_day
while current_date <= last_day:
print(f"Обрабатывается дата: {current_date}")
for _ in range(batch_count):
# Создаём список кортежей для массовой вставки
data = [(current_date, text_content) for _ in range(batch_size)]
# Выполняем массовую вставку
cursor.executemany('INSERT INTO records (date, text) VALUES (?, ?)', data)
# Коммитим изменения после обработки каждого дня
conn.commit()
current_date += datetime.timedelta(days=1)
# Закрываем соединение
conn.close()
print("Таблица успешно создана и заполнена")
if __name__ == "__main__":
create_and_populate_table()
Теперь открываем DBeaver и в двух сессиях начинаем выполнять запросы на чтение и вставку данных.
Чтение делаем в диапазоне 02.08.25 – 22.08.25. Этот запрос запускаем первым:

Элементарный запрос, возвращает кол-во строк за заданный интервал дат. На рисунке выше запрос выполнился за 8,308 сек. и индекса на колонке [date] не было. Специально, чтобы было долго и можно запустить вручную вторую сессию. С индексом этот же запрос выполняется за 1,1 сек.
Вставку делаем за пределами диапазона чтения, чтобы в надежде? не попасть в «возможное пространство блокировок», наложенное чтением. Например, 25.09.2025 – новая дата, которой нет в исходной таблице. Но общая длительность вставки всё равно почти совпадает с длительностью предыдущего запроса:

Таким образом, пока не выполнится запрос на чтение
SELECT count (*) FROM records WHERE "date" >= '2025-08-02' AND "date" <= '2025-08-22';
второй запрос на изменение ожидает на блокировке SHARED lock и ждет освобождения блокировки или таймаута.
INSERT INTO records (date, text) VALUES ('2025-08-25', 'Вставка из запроса')
Получается SQLite слабо приспособлена для высокоинтенсивной параллельной работы со стороны пользователей, т.к. ЖР очень часто меняется, а любое долгое чтение из журнала просто парализует работу всей системы, поскольку остальные пользователи в этот период не могут зафиксировать свои изменения в ЖР. Особенно ярко это будет проявляться на большом размере ЖР (сотни гигабайт), когда какой-то пользователь получает данные журнала с минимальными фильтрами, например, за большой период времени, и его запрос (SELECT) будет выполняться гарантированно долго.
Сама идея хранить лог пользовательских действий в базе данных, а не в текстовом файле прекрасна. Индексация, быстрый поиск, фильтрация. А файл – это всегда последовательная запись. И чтение из файла(ов) – это долго, т.к. поиск идет среди неиндексированных данных плюс парсинг и т.п. Но оказалось, что SQLite категорически не подходит для задачи интенсивной многопользовательской работы, т.к. при чтении накладывает избыточные блокировки.
Выводы
Весь этот текст написан для того, чтобы еще раз обратить внимание читателей на то, что если у вас релиз платформы 1С в промежутке 8.3.5 – 8.3.22, то обязательно проверьте формат хранения ЖР и лучше переведите его в последовательный, как и рекомендует вендор. В противном случае вы можете получать проблемы с производительностью системы, если кто-то из пользователей работает с журналом регистрации.
Задумка использовать базу данных для хранения журнала регистрации хорошая, но, к сожалению, SQLite как есть не подходит для этого. Вероятно, с базой SQLite можно поработать в части каких-то настроек, но как есть она не подходит для интенсивной многопользовательской работы. Возможно, стоило бы рассмотреть другие форматы хранения, например в виде отдельного инстанса PostgreSQL. Тут свои сложности, но навскидку видится перспективным.
Наш мониторинг Perfexpert работает с обычным последовательным форматом журнала регистрации, но он не тянет все данные, т.к. по большей части они не нужны и избыточны, а берет только те события, которые потенциально могут быть полезны при расследовании проблем производительности. То есть, отбирается информация по сеансам 1С, которая уже есть в мониторинге и как фильтр накладывается на ЖР. Так сохраняется баланс достаточности данных в системе мониторинга и размером базы мониторинга. Подробно об этом писали в прошлом году в статье «Эффективное использование журнала регистрации и технологического журнала 1С в решении вопросов производительности».
Ссылки на остальные части Записок оптимизатора 1С:
Записки оптимизатора 1С (ч.1). Странное поведение MS SQL Server 2019: длительные операции TRUNCATE
Записки оптимизатора 1С (ч.2). Полнотекстовый индекс или как быстро искать по подстроке
Записки оптимизатора 1С (ч.3). Распределенные взаимоблокировки в 1С системах
Записки оптимизатора 1С (ч.4). Параллелизм в 1С, настройки, ожидания CXPACKET
Записки оптимизатора 1С (ч.5). Ускорение RLS-запросов в 1С системах
Записки оптимизатора 1С (ч.6). Логические блокировки MS SQL Server в 1С: Предприятие
Записки оптимизатора 1С (ч.7). «Нелогичные» блокировки MS SQL для систем 1С предприятия
Записки оптимизатора 1С (ч.8). Нагрузка на диски сервера БД при работе с 1С. Пора ли делать апгрейд?
Записки оптимизатора 1С (ч.9). Влияние сетевых интерфейсов на производительность высоконагруженных ИТ-систем
Записки оптимизатора 1С (ч.10): Как понять, что процессор — основная боль на вашем сервере MS SQL Server?
Записки оптимизатора 1С (ч.11). Не всегда очевидные проблемы производительности на серверах 1С
Записки оптимизатора 1С (ч.12). СрезПоследних в 1C:Предприятие на PostgreSQL. Почему же так долго?
Записки оптимизатора 1С (ч.13). Что не так в журнале регистрации 1С в формате SQLitе?
Комментарии (4)
yukon39
28.08.2025 11:38У формата SQLite есть огромный плюс в том, что он легко перегоняется в Эластик, и дальше его можно крутить, вертеть как угодно.
Убирается как класс открытие и поиск чего-либо в ЖР средствами платформы.
Kwisatz
Как раз недавно вспоминал ЖР. Сколько раз я пытался воспользоваться им, столько раз он оказывался бесполезен. Как будто его специально делали так, чтобы найти было чтото слабореально. Надо просто разработчикам раз в день давать задание на поиск специфической вещи по ЖР и они доведут его до ума за неделю) Но лучше ненадо итак хорошо