Эта статья — не академическое исследование внутреннего устройства СУБД, а описание универсального инженерного подхода к контролю качества кода и нагрузки на базу. Мы покажем, как с помощью бесплатного инструмента pgBadger организовать регулярный аудит запросов и почему этот метод критически важен для любой OLTP‑системы — он помогает купировать риски падений во время пиковых нагрузок. Методика проиллюстрирована на реальном примере из практики.

Статья будет полезна системным администраторам и разработчикам в командах, где нет выделенного DBA или 1С эксперта по технологическим вопросам, но есть желание навести порядок в работе баз данных своими силами, включая проекты на платформе 1С:Предприятие.

Суть проблемы: Смерть от тысячи порезов

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

Анализ через pgBadger выявил категорию запросов, которые суммарно генерировали аномальную нагрузку. В метрике Total DB Time (агрегированное время работы всех потоков СУБД) на них набралось почти двое суток.

На Рис. 1 представлен отчет pgBadger в разделе Time consuming queries. Обратите внимание на колонку Total duration (общая длительность выполнения) — именно здесь скрыты наши «тихие убийцы». Суммарное время выполнения некоторых запросов достигает нескольких часов, что является критически высоким показателем.

Рис. 1. Отчет pgBadger: самые ресурсоемкие запросы
Рис. 1. Отчет pgBadger: самые ресурсоемкие запросы

Коварство ситуации в том, что пользователи не замечали проблем. Отдельные вызовы длились от 2 секунд до 1 минуты 40 секунд, что для тяжелых операций в интерфейсе 1С часто воспринимается как норма. Как выяснилось позже, основным источником нагрузки было фоновое задание. Оно методично «выжигало» ресурсы, оставаясь в тени.

На Рис. 2. наглядно демонстрируется одно из ключевых преимуществ pgBadger над технологическим журналом 1С: статистика с разбивкой по часам и дням. Как видно из таблицы ниже и графика выше, нагрузка носит регулярный характер, а не является разовым всплеском или ручным запуском отчета. Это помогло нам понять, что проблема кроется в регламентном задании, а не в действиях пользователя.

Рис. 2. Статистика pgBadger с разбивкой нагрузки по дням и часам
Рис. 2. Статистика pgBadger с разбивкой нагрузки по дням и часам

Такие запросы — идеальные «тихие убийцы». Из‑за постоянных Sequential Scan они вымывают полезные данные из кэша (shared buffers), заставляя СУБД постоянно обращаться к диску. В итоге мы платили «налог на железо»: быстрые NVMe и лишние ядра просто маскировали неоптимальный код, не давая системе упасть, но работая на износ.

Стек и инструменты

  • СУБД: PostgreSQL 14 (сборка от 1С) на Linux.

  • Приложение: «1С:Предприятие 8.3» (но сам подход применим к любой высоконагруженной системе).

  • pgBadger — быстрый генератор отчетов, который парсит логи Postgres и превращает их в наглядные графики и таблицы.

  • Инструменты миграций: Конфигуратор 1С (в других стеках это могут быть Liquibase, Flyway или нативный SQL).

Настройка логирования: Бережный старт

Чтобы pgBadger смог построить отчет, нужно настроить postgresql.conf.

Важно: Мы установили порог log_min_duration_statement на 3 секунды. Это осознанная цифра для старта: она позволяет не захлебнуться в гигабайтах логов и не просадить диск на высоконагруженной базе. Наша стратегия — сначала разгрести «самую жесть», а затем постепенно снижать планку до 500 или 200 мс.

Ключевые параметры:

# Формат обязательно для pgBadger
log_line_prefix = '%m [%p]: user=%u,db=%d,app=%a,client=%h ' 
# Сообщения строго на английском
lc_messages = 'C'
log_checkpoints = on
log_lock_waits = on
# Контроль временных файлов
log_temp_files = 0
# Стартовый порог в миллисекундах
log_min_duration_statement = 3000

Перед применением настроек всегда проверяйте синтаксис командойpostgres ‑t или используйте SELECT pg_reload_conf(); там, где это возможно, чтобы избежать фатального падения при перезагрузке.

Параметр pg_stat_statements.track для 1С можно оставить в значении top, так как платформа шлет прямой SQL. В случае использования хранимых процедур (Java/Python) лучше использовать all.

Построение системы обратной связи

Оптимизация — это не разовый подвиг, а непрерывный процесс. Чтобы методика работала, мы внедрили реглам��нт: раз в неделю берем топ-3 самых тяжелых запроса из отчета pgBadger и отправляем их в работу.

Почему только три? Это осознанное ограничение. Больший объем правок сложно качественно пропустить через цикл тестирования и вовремя доставить на продакшен. Наша цель — чтобы результат зафиксированных изменений гарантированно отразился в отчете на следующей неделе. Такой итерационный подход позволяет планомерно снижать нагрузку, не перегружая команду разработки и тестирования.

Кейс «Троянский конь»:

Однажды в топ вылез запрос, который мы уже оптимизировали.

Оказалось, что после очередного обновления БСП (Библиотека стандартных подсистем) случайно активировался функционал «Разделение данных». Платформа провела реструктуризацию почти всех таблиц, добавив реквизит ОбластьДанныхОсновныеДанные. Это замедлило многие запросы и раздуло таблицы.

Рис. 3. Тот самый реквизит-диверсант
Рис. 3. Реквизит ОбластьДанныхОсновныеДанные
Рис. 3. Реквизит ОбластьДанныхОсновныеДанные

Наш «дешевый» инструмент блестяще подсветил проблему, которую на уровне кода можно было искать очень долго. Вместо того чтобы препарировать архитектуру вендора и тратить недели на выяснение причин замедления базы из‑за разделения данных, мы выбрали кратчайший путь. Поскольку этот функционал был нам не нужен, его отключение стало самым логичным и эффективным решением, которое мгновенно вернуло производительность в норму.

Анализ плана: Когда Seq Scan — это диагноз

В отчете pgBadger мы в первую очередь идем в раздел Time consuming queries (N). Здесь собраны запросы, которые «съели» больше всего серверного времени по совокупности всех вызовов.

На Рис. 4 показан интерфейс раздела «Time consuming queries» в отчете pgBadger, где мы фокусируемся на анализе самых ресурсоемких запросов.

Рис. 4. Изображение с интерфейсом pgBadger, раздел Time consuming queries
Рис. 4. Изображение с интерфейсом pgBadger, раздел Time consuming queries

Прежде чем бросаться добавлять индексы, мы всегда задаем вопрос аналитикам: «А нужен ли нам вообще этот функционал?». Пару раз мы находили процедуры, которые давно должны были быть выведены из эксплуатации. Помните: Самая эффективная оптимизация — это удаление кода, который больше не нужен.

Если функционал признан полезным, переходим к техническому анализу.

Рассмотрим пример запроса к таблице _Reference13432 (выяснить имя таблицы в 1С помогает функция ПолучитьСтруктуруХраненияБазыДанных()или готовые обработки‑обертки над ней). Для наших целей вполне достаточно типовой обработки от вендора. Как видно на Рис. 5, за техническим именем таблицы СУБД Reference13432 скрывается вполне понятный объект конфигурации 1С — «Справочник.СМЭВ Пакет».

Рис. 5. Структура хранения базы данных 1С
Рис. 5. Структура хранения базы данных: соответствие имени объекта 1С и имени таблицы СУБД.
Рис. 5. Структура хранения базы данных: соответствие имени объекта 1С и имени таблицы СУБД.

В других OLTP-системах этот шаг обычно упрощен: вы смотрите на название таблицы в логе и сразу находите модель в коде своего приложения (например, через поиск по ORM-модели или названию таблицы в схеме БД). Принцип тот же — сопоставить техническое имя с бизнес-логикой.

Типичный «подозреваемый» из нашего отчета выглядел так:

SELECT
    T1._IDRRef,
    T1._Fld15010 
FROM 
    _Reference13432 T1
WHERE 
    T1._Fld15010 < '2026-01-03 14:09:22'::timestamp
    AND T1._Marked = FALSE
    AND T1._Fld18482 = FALSE
ORDER BY 
    T1._Fld15010
LIMIT 100;

Чтобы получить детализированный план выполнения запроса в pgAdmin и понять, почему этот запрос попал в топ медленных, необходимо использовать кнопку «Explain Settings» и выбрать нужные параметры.

Как показано на Рис. 6, мы включили ANALYZE (для получения реальных цифр исполнения), TIMING, BUFFERS и SUMMARY. Это даст нам точную картину происходящего.

Рис. 6. Выбор параметров для выполнения запроса EXPLAIN ANALYZE в pgAdmin
Рис. 6. Выбор параметров для выполнения запроса EXPLAIN ANALYZE в pgAdmin

Теперь, когда мы запустили запрос с нужными параметрами, давайте посмотрим на результат.

На первый взгляд — ничего криминального. Простая выборка с фильтром по дате и парой флагов. Но в графическом плане (EXPLAIN) мы видим классический Sequential Scan (последовательное чтение всей таблицы).

Наглядный графический план выполнения запроса (Рис. 7) демонстрирует "диагноз" — красный Sequential Scan по всей таблице.

Рис. 7. Графический план выполнения запроса с Sequential Scan
Рис. 7. Графический план выполнения запроса с Sequential Scan

Бывает ли Sequential Scan нормой? Да — на таблицах в сотню строк или когда вы вычитываете 80% данных. Но когда в таблице 400к+ записей, а запрос забирает всего 100, это превращается в катастрофу. База раз за разом «пролистывает» сотни тысяч строк, чтобы найти нужную сотню, тратя на это те самые 17 секунд и вымывая кэш.

Если погрузиться в детали текстового вывода EXPLAIN ANALYZE (Рис. 8), можно увидеть, сколько строк (rows) планировщик ожидал получить, и сколько времени (cost) заняло фактическое чтение.

Рис. 8. Детализированный текстовый план выполнения запроса
Рис. 8. Детализированный текстовый план выполнения запроса

Чтобы убедиться в наших подозрениях об отсутствии нужного индекса, мы выполнили запрос к системному каталогу pg_indexes.

На Рис. 9 показан результат выборки: в таблице reference13432 существует несколько уникальных индексов по другим полям (codedescriptionidrref), но индекса по полю даты Fld15010 или флагу Fld18482 действительно нет. Это подтверждает, что планировщику СУБД не на что опереться для быстрого поиска, и он вынужден сканировать всю таблицу.

Рис. 9. Вывод системного запроса, подтверждающий отсутствие индекса по дате
Рис. 9. Вывод системного запроса, подтверждающий отсутствие индекса по дате

Исправляется это классически — созданием индекса по полям фильтрации (в нашем случае по дате). После фикса этот запрос просто испарился из топа pgBadger на следующей неделе.

Почему разработчики сразу не добавили индекс?

В ��изни всё прозаично, и дело не всегда в квалификации. Часто один человек создает структуру данных (метаданные 1С), а другой пишет запрос к ним спустя полгода. В реальности такое разделение ролей часто приводит к «слепым зонам», когда разработчик, пишущий код, просто не знает о наличии нужного индекса (или его отсутствии).

Опытный специалист, конечно, проверит наличие индексов перед тем, как писать тяжелый запрос. Но в условиях авралов и сроков об этом часто забывают.

Именно поэтому мы не ищем виноватых, а исправляем проблему на этапе эксплуатации, используя системный аудит. Наша задача — не допустить, чтобы Sequential Scan (последовательное чтение) привел к неэффективному использованию диска и памяти, особенно когда в таблице 400к+ записей, а требуется всего сотня.

Ускорение: Только для диагностики!

Чтобы проверить гипотезу, мы создали индекс напрямую через SQL.

Внимание! Согласно официальному FAQ по лицензированию 1С, любое прямое изменение структуры данных СУБД в обход программного интерфейса платформы является нарушением лицензионного соглашения.

Пруф: Вопрос № 65 официального FAQ по лицензированию 1С
«...Нельзя обращаться к данным информационной базы напрямую, минуя уровень объектов работы с данными „1С:Предприятия“, например при помощи средств СУБД... Это ограничение распространяется на любые действия с данными, в том числе на изменение их структуры, а так же на чтение...»

Мы используем этот метод исключительно как лабораторию для быстрого поиска решения на копии базы или в рамках короткого диагностического окна.

-- Создание индекса для проверки гипотезы
CREATE INDEX _reference13432_habra_test 
    ON _reference13432 (_fld15010);

После добавления индекса по полю даты план мгновенно упростился, а сортировка стала «бесплатной». Время выполнения упало с 17 секунд до нескольких миллисекунд.

На Рис. 10 (графический план) и Рис. 11 (текстовый план) наглядно продемонстрирован результат нашей работы. Как видно на изображении, вместо неэффективного Sequential Scan теперь используется быстрый Index Scan с использованием нашего нового индекса reference13432habra_test. Это подтверждается низкими значениями в колонках Timings и малым количеством обработанных строк (Rows).

Рис. 10. Графический план выполнения запроса ПОСЛЕ добавления индекса
Рис. 10. Графический план выполнения запроса ПОСЛЕ добавления индекса
Рис. 11. Детализированный текстовый план выполнения запроса ПОСЛЕ добавления индекса
Рис. 11. Детализированный текстовый план выполнения запроса ПОСЛЕ добавления индекса

После подтверждения гипотезы фиксируем изменения легально: через свойство «Индексировать» в Конфигураторе 1С или через штатные механизмы миграций (Flyway/Liquibase), если вы работаете в нативном стеке.

На Рис. 12 показано, как установить свойство «Индексировать» для поля «Дата» в объекте «Справочник.СМЭВ Пакет» через интерфейс Конфигуратора 1С. Это гарантирует, что платформа корректно создаст и будет поддерживать необходимый индекс.

Рис. 12. Изображение с интерфейсом Конфигуратора 1С
Рис. 12. Установка свойства «Индексировать» для реквизита в Конфигураторе 1С
Рис. 12. Установка свойства «Индексировать» для реквизита в Конфигураторе 1С

Селективность: Математика против интуиции

Чтобы не гадать, поможет ли нам индексация, мы проверили селективность — коэффициент избирательности поля. В общем смысле он показывает, насколько эффективно индекс будет «отсекать» лишние строки.

В нашем случае мы проверяли гипотезу: стоит ли пытаться внедрить частичный индекс (с условием WHERE _Marked = FALSE)?

Важное уточнение: Платформа «1С:Предприятие» не поддерживает создание частичных индексов (Filtered Indexes) в своих метаданных. Однако на этапе технического анализа нам было важно понять: если даже максимально «зауженный» индекс на уровне СУБД не дает выигрыша, то стандартный индекс тем более не спасет ситуацию.

Сначала мы определили общее количество записей в таблице (Рис. 13), а затем — количество записей, которые удовлетворяют условиям: Marked = FALSE и Fld18482 = FALSE (Рис. 14).

Рис. 13. Результат выполнения запроса COUNT(1)
Рис. 13. Результат выполнения запроса COUNT(1)
Рис. 14. Результат выполнения запроса COUNT(1) с условиями, показывающий количество записей для расчета селективности
Рис. 14. Результат выполнения запроса COUNT(1) с условиями, показывающий количество записей для расчета селективности

В нашем случае расчет выглядел так:

\frac{428\,496 \text{ (живых)}}{429\,167 \text{ (всего)}} = 0.99

То есть условие «отсекает» всего несколько записей из почти полумиллиона. Правило здесь простое: если селективность поля выше 0.2 (20%), индекс по нему, скорее всего, не принесет пользы. СУБД поймет, что ей всё равно придется прочитать почти всю таблицу, и выберет Sequential Scan.

В данной ситуации единственный смысл имел индекс по дате (_Fld15010), который реально сужал выборку до искомых 100 строк.

Почему pgBadger, а не Техжурнал (ТЖ) 1С?

Технологический журнал 1С (ТЖ) — мощный «микроскоп» для детального аудита, но у pgBadger есть свои уникальные преимущества, которые делают его идеальным для системного контроля:

  • Объем данных: Логи ТЖ за полгода могут легко занять терабайты дискового пространства. Отчеты pgBadger генерируются в компактный HTML и весят мегабайты, их легко хранить как историю изменений производительности.

  • Наглядность и скорость анализа: Вы получаете графики распределения нагрузки по часам «из коробки». Это позволяет за 5 минут отличить разовый «кривой» отчет пользователя от регулярно выполняющегося тяжелого регламентного задания и быстро определить виновника.

  • Универсальность: Подход к анализу pg_log одинаков для 1С, Django, Spring или любого другого приложения на PostgreSQL. Методика работает для всего стека, что делает её универсальным инструментом инженера.

Парадокс мониторинга: Почему Zabbix молчит?

Справедливости ради: одиночный замер EXPLAIN ANALYZE может обмануть из‑за прогретого кэша. Но в этом и прелесть системного подхода: нам не нужен pgbench. Если индекс работает, на следующей неделе этот запрос просто исчезнет из топа pgBadger. Реальный профит на живых данных — лучший бенчмарк.

Почему же Zabbix может показывать всё те же 15% CPU после оптимизации?

  • Масштаб DB Time: На 64-ядерном сервере снижение нагрузки с 5% до 1% выглядит как шум или статистическая погрешность. Высвободившиеся ресурсы мгновенно утилизируются другими задачами.

  • Ожидания (I/O Wait): Если запросы ждали диск, процессор «отдыхал». Вы разгрузили дисковую подсистему и повысили отзывчивость базы, но график CPU Load этого просто не заметит.

Где искать результат? Смотрите на специфические метрики БД: Rows Fetched/Returned (этот показатель должен упасть) и Active Connections (количество одновременно активных сессий сократится, так как запросы перестанут «стоять в очереди»).

Заключение

Эта методика не претендует на полноту освещения всех аспектов PostgreSQL, но она дает главное — непрерывный контроль. Когда вы строите систему регулярного аудита, вы перестаете гадать и начинаете видеть реальную картину происходящего.

Для повышения стабильности вашего проекта не обязательно использовать сверхсложные инструменты. Метод, описанный здесь, помогает не только в повседневной работе, но и адресно снижает риск возникновения критических проблем во время пиковых нагрузок или отчетных периодов. То, что в обычный день работает «медленно», во время аврала может полностью положить сервис. Наш системный подход позволяет купировать эти риски заранее.

Где искать результат?

В нашем случае общие графики мониторинга остались практически неизменными, и это нормально для мощного железа.

Настоящий профит мы увидели именно в отчетах pgBadger — время «тихих убийц» сократилось до миллисекунд, а аномальная нагрузка исчезла из топов.

Буду рад обсудить в комментариях ваши подходы к анализу производительности СУБД.


P. S. При подготовке структуры статьи и редактировании некоторых формулировок использовались инструменты ИИ. Однако весь цикл анализа, диагностики, тестирования гипотез и разработки методологии — полностью моя работа и личный опыт.

UPD: В комментариях справедливо поправили, что Sequential Scan в PostgreSQL использует кольцевой буфер (Buffer Access Strategy). Это предотвращает вымывание shared_buffers, но не отменяет нагрузку на IO и CPU при полном сканировании таблиц.