
Когда система «падает», а времени на вдумчивый разбор логов нет, на помощь может прийти не еще один разработчик, а LLM. Нейросеть показала достойный результат в качестве ассистента при диагностике ошибок 1С.
Привет, Хабр! Меня зовут Евгений Филиппов, я эксперт департамента корпоративных решений IBS. Поговорим о том, как можно использовать ИИ для разбора логов 1С при возникновении некоторых характерных ошибок параллельной работы.
Идея этого подхода родилась в цейтноте. Ручной разбор логов требует свежей головы, а очередная проблема заказчика появилась, когда я был в отъезде, да еще и под конец рабочего дня. Полноценно разобрать ее вручную просто не было возможности. Поскольку поиск решения нельзя было откладывать, я провел подготовку: отфильтровал и обезличил логи и скормил выжимку нескольким LLM. Получившийся результат перепроверил, и он оказался вполне хорош.
Спойлер: LLM не сделают из джуна эксперта по 1С, но вполне помогут сэкономить этому эксперту время и силы.
Вместо предисловия
Мы в компании сопровождаем проекты, в которых есть базы 1С. Как и в любых ИТ-решениях, в них порой возникают проблемы. Единственный способ найти их реальный источник, а не подкрутить что-то по наитию — это разобрать логи, технологический журнал 1С, он же ТЖ, представленный, как правило, в виде текстового файла с запятой в качестве разделителя. В более современных версиях встречается лог в формате JSON, но это не так важно с точки зрения описываемого подхода.
Обращаясь за решением своей проблемы, заказчик пересылает собранный лог партнеру 1С, тот его изучает и находит причину возникновения ошибки. По итогам дает рекомендации по исправлению.
Далее я буду описывать ситуацию именно с точки зрения партнера 1С, а не внутренней работы с проблемами. Наш случай отягощен тем, что мы разбираем лог не в той же инфраструктуре, где он был собран. У большинства консалтинговых компаний и партнеров 1С, и у нас в том числе, по организационным и техническими причинам нет доступа к внутренним системам заказчика — мы не можем развернуть там собственные инструменты. Поэтому нам доступны всего три варианта:
разобрать ТЖ с помощью Центра управления производительностью — ЦУП, из состава 1С:КИП8. Он может анализировать чужие журналы, но для этого в нем должна быть актуальная структура метаданных, то есть нужна дополнительная информация из исследуемой базы;
использовать локальные средства автоматизации: либо самописные парсеры, работающие с текстами, либо некую СУБД, куда можно загрузить журнал и получить данные с помощью запросов. К сожалению, такие инструменты зачастую работают только в рамках собственной инфраструктуры;
работать полностью вручную, используя Notepad++ в Windows или консольные утилиты, типа grep в Linux/WSL.
ЦУП и каких-то собственных подходящих скриптов под рукой в поездке не оказалось, поэтому оставался только ручной разбор. ��то очень много рутины, которую я попробовал автоматизировать при помощи бесплатных ИИ — в моем примере Qwen и DeepSeek.
Разберем, как это происходит, на примере двух реальных задач:
анализа плохих запросов (длинные DBPOSTGRS);
разбора взаимной блокировки (TDEADLOCK).
Дисклеймер. ИИ не решает задачу полностью. Логи необходимо собрать вручную. Перед тем как передавать их нейросети, рекомендую отфильтровать из них интересующие события, поместив в отдельный файл, а потом обезличить данные. И только потом вопрос можно передавать на анализ ИИ. Ответ ни в коем случае нельзя напрямую пересылать заказчику. Его надо проанализировать и перепроверить.
Анализ плохих запросов
Рассмотрим использование LLM при разборе ситуации с «подвисающими» транзакциями.
Предварительные настройки
Чтобы анализировать долгие запросы, в logcfg.xml необходимо заранее включить их запись в лог. У наших заказчиков обычно в файл включена подобная секция:
<log location="C:\Logs\Long_DB" history="168">
<event>
<eq property="name" value="dbpostgrs"/>
<eq property="p:processName" value="имя_базы"/>
<ge property="durationus" value="5000000"/>
</event>
<property name="all"/>
</log>Так включается логирование запросов, которые выполняются более 5 секунд. Но будьте готовы к тому, что вам пришлют сотни мегабайт логов.
Можно настроить другой порог или уже после инцидента из готовых логов выделить нужное — допустим, запросы длиннее 100 секунд. Для этого выполняем поиск по всем подпапкам полученного ТЖ и выводим их в отдельный текстовый файл:
cat rphost*/*.log | awk -vORS= '{if(match($0, "^[0-9][0-9]\:[0-9][0-9]\.[0-9]+\-")) print "\n"$0; else print $0;}' | grep -P '\d{9},dbpostgrs' -i | sed -e "s/\xef\xbb\xbf/ /g" > analiz_longest_db.txtФильтрация файла логов
Как отметил выше, не обязательно анализировать все присланные сотни мегабайт. Я отфильтровал запросы длиннее 100 секунд. Чтобы использовать команды Linux для работы с логами, у меня стоит Windows Subsystem for Linux, так что это чисто техническая работа. На выходе — небольшой файл с самыми плохими запросами.
Готовый файл можно просмотреть глазами и выбрать интересующие события.
36:29.287000-22967951,DBPOSTGRS,5,level=DEBUG,process=rphost,p:processName= XXXX, ,OSThread=12248,t:clientID=55,t:applicationName=1CV8C,t:computerName= XXXX,t:connectID=6828,SessionID=485659,Usr= XXXX,AppID=1CV8C,DBMS=DBPOSTGRS,DataBase=XXXX,Trans=0,dbpid=680855,Sql="INSERT INTO pg_temp.tt184 (_Q_001_F_000, Q001_F_001RRef, Q001_F_002RRef) SELECTSUM(T2.Fld23454Turnover_),T2.Fld23443RRef,T2.Fld23451RRefFROM pg_temp.tt175 T1LEFT OUTER JOIN (SELECTT3._Fld23443RRef AS Fld23443RRef,T3._Fld23451RRef AS Fld23451RRef,SUM(T3._Fld23454) AS Fld23454Turnover_FROM AccumRg23441 T3WHERE ((T3.Fld1016 = CAST(0 AS NUMERIC))) AND (T3._Period >= '2025-01-01 00:00:00'::timestamp AND T3._Period <= '2025-08-15 00:00:00'::timestamp AND T3._Active = TRUE AND (T3._Fld23443RRef IN(SELECTT4._Q_001_F_000RRef AS Q_001_F_000RRefFROM pg_temp.tt175 T4) AND (NOT (((T3._Fld23447RRef IN ('\\263\\033\\255\\036ap\\264mB\\301\\306\\251\\007;0\\017'::bytea, '\\217MZ\\337e\\357(\\201@~\\211\\245Q\\015\\000\\200'::bytea)))))))GROUP BY T3._Fld23443RRef,T3._Fld23451RRefHAVING (SUM(T3._Fld23454)) <> CAST(0 AS NUMERIC)) T2ON (T1._Q_001_F_000RRef = T2.Fld23443RRef)GROUP BY T2.Fld23443RRef,T2.Fld23451RRef",RowsAffected=2138,Result=PGRES_COMMAND_OK,Context='Форма.Вызов : ВнешняяОбработка. XXXX.Форма.Форма1.Модуль.ЗаполнитьНаСервере
ВнешняяОбработка. XXXX.Форма.Форма1.Форма : 555 : СоответствиеДоходов = Справочники. НастройкиРасчетаНДФЛ.ПолучитьДоходыДоДатыСобытияБезДетализацииПоРегистраторуФизическиеЛица(Объект.ДатаКон, Объект.ТабЧасть.Выгрузить(, "ИД"));
Справочник. НастройкиРасчетаНДФЛ.МодульМенеджера : 777 : Выборка = Запрос.Выполнить().Выбрать();'
Здесь я уже вижу два плохих момента. Они выделены в тексте:
первое выделенное условие — проверка, что поле входит в некую таблицу. Этот поиск сработает быстро, только если нам повезет — если поможет индекс;
самое плохое условие — второе. Это поиск поля, которое не должно входить в таблицу. Для этого таблицу необходимо просканировать целиком — если она большая, выполняться запрос будет очень долго.
Текст лога надо обезличить — убрать имена базы, пользователей и тому подобное. После этого его можно скормить ИИ, чтобы выяснить, не пропустил ли я что-то еще.
Промпт и ответ
При отправке ИИ текст запроса надо сопроводить правильным про��птом: «что в тексте запроса может привести к его медленной работе».
Если ответ LLM вас не устроил, потому что, допустим, не содержит того, что вы нашли глазами, можно уточнить, спросив: «AND (NOT (((T3._Fld23447RRef IN не вошло в результат вашего разбора. Оно проблематично или нет?»
Если вы правы, ИИ даст уточнение, если нет, опровержение.
Рекомендую полностью не полагаться на результаты и использовать более одной ИИ-модели. Я перепроверяю одну LLM другой. Использую QWEN и DeepSeek, поскольку они решают задачи немного по-разному.
Фрагмент ответа QWEN
Вот фрагмент ответа нейросети, которая, на мой взгляд, лучше справилась с этой задачей. Текст от нейросети дополню своими комментариями.
1. Отсутствие индексов на используемых полях
Запрос обращается к таблице _AccumRg23441 (это таблица регистра накопления в 1С), фильтруя по:
_Period (период)
_Active
_Fld1016
_Fld23443RRef
_Fld23447RRef
Если на эти поля нет подходящих составных индексов, PostgreSQL будет выполнять полное сканирование таблицы (Seq Scan), что особенно критично при большом объеме данных.Рекомендация:
Создать составной индекс, например:
CREATE INDEX idx_AccumRg23441_optimized ON _AccumRg23441
(_Fld23443RRef, Period, Active, Fld1016, Fld23447RRef)
WHERE _Active = TRUE;
Или, если фильтр по _Fld1016 = 0 статичен:
CREATE INDEX idx_AccumRg23441_cond ON _AccumRg23441
(_Fld23443RRef, Period, Fld23447RRef)
WHERE Active = TRUE AND Fld1016 = 0;
Прежде чем советовать заказчику добавить индекс, надо убедиться, что в его случае он поможет. На сегодняшний день для баз 1С рекомендации «добавить индексы» редко бывают полезными, потому что платформа и так создает наиболее ходовые индексы. Просчеты архитектора или разработчика, когда требуется индекс, а его нет, случаются, но, судя по моей практике, это редкость. Позже я отмечал, что «добавить индексы» — это то, что LLM предлагают делать по умолчанию. Но на других задачах я с целью проверки пытался создавать все те индексы, что рекомендовали LLM, в том числе составные, и это никак не помогало. Тезис о том, что при не полностью подходящем индексе случится Seq Scan, — это сильное упрощение со стороны LLM.
2. Подзапрос с IN и временной таблицей (pg_temp.tt175)
Фрагмент:
T3._Fld23443RRef IN (
SELECT T4._Q_001_F_000RRef FROM pg_temp.tt175 T4)pg_temp.tt175 — это временная таблица, созданная в рамках сеанса.
Если в ней много строк и нет индекса на Q001_F_000RRef, то IN превращается в линейный поиск для каждой строки внешнего запроса → O(N×M).Рекомендация:
Убедиться, что в pg_temp.tt175 есть индекс на Q001_F_000RRef.
В 1С можно попробовать использовать JOIN вместо IN, если логика позволяет.3. Фильтрация по bytea (бинарным данным)
Условие:
NOT (T3._Fld23447RRef IN ('\\263\\033...'::bytea, ...))Сравнение с bytea (бинарные UUID/ссылки 1С) не использует индексы эффективно, особенно с NOT IN.
NOT IN с NULL-значениями может давать неожиданные результаты и мешать оптимизатору.Рекомендация:
Заменить NOT IN на NOT EXISTS или LEFT JOIN ... WHERE IS NULL.
Убедиться, что на _Fld23447RRef есть индекс.
На это я тоже обратил внимание. Попробовать заменить НЕ В () на ЛЕВОЕ СОЕДИНЕНИЕ ... ГДЕ ... ЕСТЬ NULL — это хорошая гипотеза для проверки.
4. Агрегация по большому объему данных
Запрос делает:
SELECT ... SUM(...) ... GROUP BY ...
по всей таблице за период с января по август 2025 ��ода (обратите внимание: будущая дата! — возможно, опечатка, но если данные реально есть, объем может быть огромным).
Если _AccumRg23441 содержит миллионы записей, агрегация без эффективной фильтрации — дорогостоящая операция.
Когда я наткнулся на этот пример, август 2025 года был уже в прошлом. Но оговорка про будущую дату для ИИ — нормальное явление.
5. LEFT JOIN с агрегированным подзапросом
Внешний LEFT JOIN выполняется после агрегации подзапроса T2. Это может быть не оптимально, если:
T1 (временная таблица tt175) мала,
а T2 (результат агрегации по _AccumRg23441) — велик.
Иногда лучше сначала соединить, потом агрегировать.
Честно скажу, при первом визуальном анализе это я пропустил. Можно рассмотреть как еще одну хорошую гипотезу.
При отправке запроса в LLM именно это и было для меня важно — получить более широкий взгляд и не упустить детали. В примере запрос один и не такой уж большой. Но даже в нем глазами удалось обнаружить не все. В большом запросе или при разборе целого пула запросов гарантированно будет пропущено что-то важное, а скорость «ручной» обработки этих данных может сильно падать.
Далее в рекомендациях от LLM идет предложение дать результат EXPLAIN (ANALYZE), но это в условиях командировки технически невыполнимо.
Разбор взаимной блокировки
Рассмотрим теперь разбор взаимной блокировки двух транзакций.
Для тех, кто не в курсе, что такое взаимная блокировка, объясню «на пальцах».
Предположим, в магазине есть две кассы: одна продает алкоголь, другая — сигареты. Напомню, что при продаже этих товаров кассы должны обращаться к соответствующим государственным базам.
Обе кассы открылись на обслуживание: к первой подошел покупатель с бутылкой вина, на второй клиент покупает сигареты. Но покупателю из первой кассы внезапно потребовались сигареты, а покупателю со второй — банка пива. Не прерывая работу с кем-то из клиентов, кассы не могут обслужить одновременные запросы. Это и называется взаимной блокировкой. В транзакционных системах подобные блокировки решаются отказом в обслуживании одному из клиентов, выбранному по определенному алгоритму или случайным образом.
Предварительные настройки
Как и в случае с предыдущей задачей, необходимо включить запись соответствующих событий (управляемых блокировок) в logcfg.xml.
<log location="C:\Logs\TLOCKS" history="168">
<event>
<eq property="name" value="tlock"/>
<eq property="p:processName" value="имя_базы"/>
</event>
<event>
<eq property="name" value="tdeadlock"/>
<eq property="p:processName" value="имя_базы"/>
</event>
<event>
<eq property="name" value="ttimeout"/>
<eq property="p:processName" value="имя_базы"/>
</event>
<property name="all"/>
</log>Фильтрация файла логов
Получаем логи, выбираем оттуда TDEADLOCK
cat /.log | grep 'tdeadlock' -i > tdeadlocks.txtВыбираем интересный нам TDEADLOCK, например
24:52.003002-0,TDEADLOCK,5,p:processName=ХХХХХ,t:connectID=2067058,DeadlockConnectionIntersections='2067058 2065308 InfoRg63347.DIMS Exclusive Fld10045=228 Fld63348="a76c0382-8823-11f0-9ef6-fa163e696ce9",2065308 2067058 InfoRg41351.DIMS Exclusive Fld2302=228 Fld41352=329:9b00fa163e037b5d11eeab93b4f4ecf8 Fld41353=1019:934cfa163ecb6e9b11f09f74351321e0',Context='
Здесь есть информация, кто с кем пересекся и кто был выбран «жертвой» для прекращения обслуживания.
В этой блокировке два t:connectID и два InfoRg. Пишем скрипт, выясняющий, что делал каждый из участников взаимоблокировки до этого. tid — правая часть от t:connectID.
cat rphost*/*.log | awk -vORS= '{if(match($0, "^[0-9][0-9]\:[0-9][0-9]\.[0-9]+\-")) print "\n"$0; else print $0;}' | grep -P '(tid=2067058|tid=2065308).+(InfoRg63347|InfoRg41351)' -i | sed -e "s/\xef\xbb\xbf/ /g" > analiz.txtПо файлу analiz.txt можно вручную построить граф взаимоблокировки. Из него будет видно, что причина — захват ресурсов в разном порядке. Но в экспериментальных целях отдаем файл ИИ-модели с промптом «найди причину tdeadlock».
Отдаем, естественно, обезличенный файл — analiz2.txt. Он может быть относительно большим, поэтому, чтобы не обезличивать вручную, вырезаем имя базы и другие чувствительные поля, используя sed:
sed 's/p:processName=ХХХХХ//g' analiz.txt > analiz2.txtФрагмент ответа QWEN
Причина TDEADLOCK (взаимоблокировки) в приведенном логе — это циклическое ожидание блокировок между двумя соединениями:
соединение 2067058 (фоновая задача выгрузки данных через очередь заданий),
соединение 2065308 (пользовательская операция записи документа).
Анализ цепочки блокировок
Из строки с событием TDEADLOCK:TDEADLOCK,5,,t:connectID=2067058,
DeadlockConnectionIntersections=
'2067058 2065308 InfoRg63347.DIMS Exclusive Fld10045=228 Fld63348="a76c0382-8823-11f0-9ef6-fa163e696ce9",
2065308 2067058 InfoRg41351.DIMS Exclusive Fld2302=228 Fld41352=329:9b00fa163e037b5d11eeab93b4f4ecf8 Fld41353=1019:934cfa163ecb6e9b11f09f74351321e0'Это означает:
2067058 удерживает эксклюзивную блокировку на InfoRg63347.DIMS (по ключу Fld10045=228, Fld63348="a76c0382-...")
→ и ожидает блокировку на InfoRg41351.DIMS (удерживаемую 2065308)
2065308 удерживает эксклюзивную блокировку на InfoRg41351.DIMS (по ключу Fld2302=228, Fld41353=1019:...)
→ и ожидает блокировку на InfoRg63347.DIMS (удерживаемую 2067058)
Таким образом, каждое соединение держит ресурс, нужный другому, и ждет освобождения — классическая взаимная блокировка.Контексты операций
Соединение 2067058:
Выполняет фоновую задачу "СводныеПриложения.ЗаданиеВыгрузка()" → записывает состояние выгрузки → пытается получить эксклюзивную блокировку на InfoRg63347.DIMS → ждет InfoRg41351.DIMS.Соединение 2065308:
Пользователь сохраняет документ "ТребованиеНакладная" → в обработчике ПередЗаписьюДокумента вызывается логика, связанная с выгрузкой → читает/блокирует InfoRg63347.DIMS → ждет InfoRg41351.DIMS.Причина возникновения
Нарушение порядка захвата блокировок:
Обе операции (фоновая и пользовательская) работают с двумя регистрами (InfoRg41351 и InfoRg63347), но захватывают блокировки в разном порядке:
Фоновая задача: сначала InfoRg63347, потом InfoRg41351
Пользовательская операция: сначала InfoRg41351, потом InfoRg63347
Это и приводит к deadlock.
ИИ-модель правильно определила генезис взаимной блокировки — захват ресурсов в разном порядке, и подробно его разобрала. В данной задаче для меня было важно именно это.
Рекомендации по устранению:
унифицировать порядок захвата блокировок во всех местах, где используются эти два регистра. Например, всегда сначала блокировать InfoRg41351, потом InfoRg63347;
Абсолютно правильная, можно сказать, классическая рекомендация, которая полностью устранит взаимную блокировку.
минимизировать время удержания блокировок;
не читать/записывать другие объекты внутри критической секции;
Это решение уменьшит вероятность взаимной блокировки. Правда, при большой нагрузке мы все равно будем на нее натыкаться.
избегать вызова внешних методов (особенно РазрешеноВыгружатьОбъектМетаданных) под блокировкой;
использовать неблокирующие проверки там, где это возможно (например, ПопыткаЗаблокировать() с таймаутом);
Здесь у ИИ пошли галлюцинации в терминологии.
логировать начало и конец блокировок для упрощения диагностики в будущем.
Логировать начало и конец транзакций полезно для диагностики, но накладно. Годится для поимки конфликта на коротком интервале, но не для дежурного мониторинга.
Заключение
На двух классических и сложных задачах — поиске причин медленных запросов и диагностике взаимоблокировок — анализ технологического журнала 1С с привлечением публичных LLM-моделей показал свою эффективность.
Вначале я упоминал, что использовал две LLM, но привел ответы только одной. На мой взгляд, с данными проблемами она справилась лучше. Но я не ставил себе задачу сравнить ИИ-модели. Эксперименты показали, что наилучшая тактика — использование именно множественных моделей для взаимной проверки их рекомендаций.
Как я и говорил в начале, использование LLM не сделает из джуна эксперта, способного решать любые кейсы. ИИ действует как мощный ассистент — обрабатывает рутину и генерирует гипотезы, которые эксперт затем проверяет и верифицирует. Его ключевые преимущества:
скорость. ИИ-ассистент за секунды обрабатывает объемы данных, на ручной разбор которых могли бы уйти десятки минут;
глубина: модель замечает неочевидные для человеческого глаза паттерны и генерирует множество гипотез, расширяя круг поиска эксперта;
доступность: для использования метода не требуется развертывание дополнительной инфраструктуры у заказчика или доступа к его продуктивной среде, что часто является критическим ограничением.
Но и недостатки присутствуют:
ложные и бесполезные рекомендации, как в первом примере с индексами;
дополнительная работа по обезличиванию данных, передаваемых LLM;
необходимость глубокой проверки рекомендаций.
Хочется закончить предостережением. За рекомендации, переданные заказчику, отвечаете вы, а не ИИ. Если просто скопировать все пункты, у заказчика появятся вполне логичные замечания, что предложенные варианты не работают. Их необходимо перепроверять самому, и делать это внимательно.
