«База данных «тормозит». В логах — сотни выполняемых запросов. Где та самая иголка в стоге сена, что заставляет СУБД стонать под нагрузкой? PG_EXPECTO — это магнит, который её находит.»
«База данных «тормозит». В логах — сотни выполняемых запросов. Где та самая иголка в стоге сена, что заставляет СУБД стонать под нагрузкой? PG_EXPECTO — это магнит, который её находит.»

Расследование инцидентов производительности в PostgreSQL часто напоминает поиск иголки в стоге сена. Десятки тысяч запросов , и определить, какой именно из них стал «слабым звеном» системы, без специальных инструментов — крайне сложная задача.

В этой статье рассмотрим, как использование PG_EXPECTO позволяет кардинально ускорить этот процесс. Мы не будем гадать на основе снимков pg_stat_statements. Вместо этого мы научимся проактивно создавать «ловушки» на проблемные паттерны производительности. Когда инцидент происходит, PG_EXPECTO позволяет быстро найти проблемные SQL-запросы , предоставляя инженеру готовый список «подозреваемых» для дальнейшей оптимизации.

Новый инструмент с открытым исходным кодом для статистического анализа, нагрузочного тестирования и построения отчетов доступен в репозитории GitFlic и GitHub

kznalp/PG_EXPECTO: Комплекс статистического анализа производительности СУБД PostgreSQL

pg-expecto/pg_expecto: Комплекс pg_expecto для статистического анализа производительности и нагрузочного тестирования СУБД PostgreSQL


Практическая иллюстрация применения методик использования pg_expecto :

PG_EXPECTO 3.0: Когда мониторинг становится проактивным, а оптимизация — интеллектуальной. | Postgres DBA | Дзен

Использование pg_expecto для проактивного мониторинга производительности СУБД PostgreSQL | Postgres DBA | Дзен

Типовой шаблон расследования инцидентов PostgreSQL с помощью pg_expecto. Часть 1: Анализ на уровне СУБД | Postgres DBA | Дзен

Типовой шаблон расследования инцидентов PostgreSQL с помощью pg_expecto. Часть 2: Детальный разбор инфраструктуры сервера | Postgres DBA | Дзен

Теоретическое предисловие:

Статистический анализ производительности СУБД PostgreSQL | Postgres DBA | Дзен


Обнаружение инцидента производительности СУБД, с помощью мониторинга Zabbix

Дашборд Zabbix
Дашборд Zabbix

Дата и время возникновения инцидента : 2025-11-01 10:22

Предварительные действия: сформировать сводный отчет оценки производительности и инфраструктуры СУБД

summary_report.sh '2025-11-01 10:22'

Часть-1: Выявление аномалий инфраструктуры

1.1 Корреляция "Ожидания СУБД - vmstat"

PG_EXPECTO : Корреляция ожиданий СУБД и показателей vmstat | Postgres DBA | Дзен

Результат отчета:

  • Нагрузка на СУБД подтверждается наличием корреляции с метриками vmstat

1.2 Статистика vmstat+iostat по файловой подсистеме /data

PG_EXPECTO : Корреляция ожиданий vmstat и iostat для дискового устройства | Postgres DBA | Дзен

Результат отчета:

  • Имеются проблемы производительности на запись для дискового устройства используемого для файловой системы /data

1.3 Статистика vmstat+iostat по файловой подсистеме /wal

PG_EXPECTO : Корреляция ожиданий vmstat и iostat для дискового устройства | Postgres DBA | Дзен

Результат отчета:

  • Существенных аномалий нет, но возможно, имеются перспективы для оптимизации.

1.4 Чек-лист IO

PG_EXPECTO : Чек-лист IO | Postgres DBA | Дзен

Результат отчета:

  • Аномалий - не обнаружено.

1.5 Чек-лист CPU

PG_EXPECTO : Чек-лист CPU | Postgres DBA | Дзен

Результат отчета:

  • Аномалий - не обнаружено.

1.6 Чек-лист RAM

PG_EXPECTO : Чек-лист RAM | Postgres DBA | Дзен

Результат отчета:

  • Аномалий - не обнаружено.

1.7 Результат анализа инфраструктуры

Аномалии инфраструктуры, оказывающие влияние на производительность СУБД:

  1. Превышение времени отклика на запись для дискового устройства используемого для файловой системы /data

Часть-2: Корреляционный анализ производительности СУБД

PG_EXPECTO : Построение графиков производительности и ожиданий по результатам нагрузочного тестирования СУБД | Postgres DBA | Дзен

2.1 Операционная скорость и ожидания СУБД в период, предшествующий инциденту

График операционной скорости СУБД. Красная линия - линия регрессии.
График операционной скорости СУБД. Красная линия - линия регрессии.
График ожидания СУБД. Красная линия - линия регрессии.
График ожидания СУБД. Красная линия - линия регрессии.

2.2 Корреляционный анализ ожиданий СУБД

PG_EXPECTO : Показатели производительности и ожиданий СУБД в ходе нагрузочного тестирования | Postgres DBA | Дзен

Тип ожидания, имеющий наибольший коэффициент корреляции с ожиданиями СУБД - IPC

  • IPC: Серверный процесс ожидает взаимодействия с другим процессом. В wait_event обозначается конкретное место ожидания;

График ожиданий типа IPC
График ожиданий типа IPC

2.3 Диаграмма Парето по событиям ожидания СУБД - для типа ожидания IPC

PG_EXPECTO : Диаграмма Парето по событиям ожидания СУБД | Postgres DBA | Дзен

Гипотеза(спойлер)

Можно сразу предположить , что причина - отсутствие индексов.

2.4 Диаграмма Парето по ожиданиям SQL запросов для типа ожидания IPC

PG_EXPECTO : Диаграмма Парето по ожиданиям SQL запросов | Postgres DBA | Дзен

Часть-3: Формирование списка проблемных SQL запросов, для последующей оптимизации

Список queryid SQL-запросов для оптимизации:

  • -1701015661318396920

  • 3449463017331132112

  • -7715565454820708773

  • 1374759154717555017

  • -678327810318891437

  • 5459520954633506046

  • -3969322877824419761

  • 3985919093425059746

Часть 4: Получение рекомендации нейросети по снижению ожиданий типа IPC

Запрос нейросети

Файл, сформированный отчетом : net.1.wait_event.prompt.txt

Выдели общие части из текста и найти смысловые совпадения. Сформируй краткий итог по необходимым мероприятиям в виде сводной таблицы.

Входной файл для нейросети сформированный отчетом : net.1.wait_event.IPC.txt

Результат работы нейросети DeepSeek

Часть 5: Анализ проблемных SQL запросов нейросетью

Запрос нейросети

Файл сформированный отчетом : net.2.sql.prompt.txt

Выдели ключевые паттерны SQL запросов , с уточнением - сколько раз встречается паттерн. Сформируй итоговую таблицу - какие паттерны используются для каждого queryid. Выдели ключевые особенности SQL запроса, использующего наибольшее количество паттернов.

Входной файл для нейросети сформированный отчетом : net.2.sql.IPC.txt

Результат работы нейросети DeepSeek

Итог

В результате проведённого анализа инцидента производительности СУБД установлена причина снижения производительности - рост ожиданий типа IPC и сформирован список проблемных SQL запросов для последующей оптимизации.


P.S. Пример использования нейросети для оптимизации SQL запроса.

Проблемный запрос для оптимизации, queryid = 1374759154717555017

Текст запроса
SELECT 
 "Table1"."col1", 
 "Table1"."col2", 
 "Table2"."col1" AS "Table2.col1", 
 "Table2"."col3" AS "Table2.col3", 
 "Table2"."col4" AS "Table2.col4", 
 "Table3"."col1" AS "a.col1", 
 "Table3"."col4" AS "a.col4", 
 "Table3"."col5" AS "a.col5", 
 "Table3"."col6" AS "a.col6" 
 FROM "public"."Table4" AS "Table1" 
 INNER JOIN "public"."Table5" AS "Table2" ON "Table1"."col1" = "Table2"."col4" 
 INNER JOIN "public"."Table4_a" AS "Table3" ON "Table1"."col1" = "Table3"."col4" AND "Table3"."col5" = 'X1' AND "Table3"."col6" = 'X2'
Таблицы участвующие в запросе
                         Table "public.Table4"
 Column |      Type      | Collation | Nullable |       Default
--------+----------------+-----------+----------+---------------------
 col1   | uuid           |           | not null | generate1()
 col2   | type1          |           | not null |
Indexes:
    "Table4_pkey" PRIMARY KEY, btree (col1)
Referenced by:
    TABLE "Table5" CONSTRAINT "Table5_col4_fkey" FOREIGN KEY (col4) REFERENCES Table4(col1)
    TABLE "Table4_a" CONSTRAINT "Table4_a_col4_fkey" FOREIGN KEY (col4) REFERENCES Table4(col1)
    TABLE "Table4_meta" CONSTRAINT "Table4_meta_col4_fkey" FOREIGN KEY (col4) REFERENCES Table4(col1)
    TABLE "templates_Table4" CONSTRAINT "templates_Table4_col4_fkey" FOREIGN KEY (col4) REFERENCES Table4(col1)

                   Table "public.Table5"
  Column   | Type | Collation | Nullable |       Default
-----------+------+-----------+---------+---------------------
 col1      | uuid |           | not null | generate1()
 col3      | uuid |           | not null |
 col4      | uuid |           | not null |
Indexes:
    "Table5_pkey" PRIMARY KEY, btree (col1)
    "Table5_col3_col4_key" UNIQUE CONSTRAINT, btree (col3, col4)
Foreign-key constraints:
    "Table5_col3_fkey" FOREIGN KEY (col3) REFERENCES plans(col1)
    "Table5_col4_fkey" FOREIGN KEY (col4) REFERENCES Table4(col1)
Referenced by:
    TABLE "Table4_statuses" CONSTRAINT "Table4_s_p_col4_fkey" FOREIGN KEY (p_col4) REFERENCES Table5(col1)

                          Table "public.Table4_a"
  Column   |          Type           | Collation | Nullable |       Default
-----------+-------------------------+-----------+----------+---------------------
 col1      | uuid                    |           | not null | generate1()
 col4      | uuid                    |           | not null |
 col5      | type2                   |           | not null |
 col6      | text                    |           | not null |
Indexes:
    "Table4_a_pkey" PRIMARY KEY, btree (col1)
    "col4_key" UNIQUE CONSTRAINT, btree (col4, col5)
Foreign-key constraints:
    "Table4_a_col4_fkey" FOREIGN KEY (col4) REFERENCES Table4(col1)

План выполнения запроса
EXPLAIN ( ANALYZE , VERBOSE , COSTS , BUFFERS , TIMING , SUMMARY ) 
SELECT "Table1"."col1", "Table1"."col2", "Table2"."col1" AS "Table2.col1", "Table2"."col3" AS "Table2.col3", "Table2"."col4" AS "Table2.col4", "Table3"."col1" AS "a.col1", "Table3"."col4" AS "a.col4", "Table3"."key" AS "a.col5", "Table3"."col6" AS "a.col6" 
 FROM "public"."Table4" AS "Table1" 
 INNER JOIN "public"."Table5" AS "Table2" ON "Table1"."col1" = "Table2"."col4" 
 INNER JOIN "public"."Table4_a" AS "Table3" ON "Table1"."col1" = "Table3"."col4" AND "Table3"."col5" = 'X1' AND "Table3"."col6" = 'X2';

                                                                                            QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Gather  (cost=51291.67..61070.22 rows=11 width=138) (actual time=212.782..228.904 rows=0 loops=1)
   Output: "Table1".col1, "Table1".col2, "Table2".col1, "Table2".col3, "Table2".col4, a.col1, a.col4, a.col5, a.col6
   Workers Planned: 2
   Workers Launched: 2
   Buffers: shared hit=38808
   ->  Nested Loop  (cost=50291.67..60069.12 rows=5 width=138) (actual time=198.025..198.030 rows=0 loops=3)
         Output: "Table1".col1, "Table1".col2, "Table2".col1, "Table2".col3, "Table2".col4, a.col1, a.col4, a.col5, a.col6
         Inner Unique: true
         Buffers: shared hit=38808
         Worker 0:  actual time=191.589..191.593 rows=0 loops=1
           Buffers: shared hit=12830
         Worker 1:  actual time=191.283..191.288 rows=0 loops=1
           Buffers: shared hit=10816
         ->  Parallel Hash Join  (cost=50291.24..60066.84 rows=5 width=118) (actual time=198.023..198.027 rows=0 loops=3)
               Output: "Table2".col1, "Table2".col3, "Table2".col4, a.col1, a.col4, a.col5, a.col6
               Inner Unique: true
               Hash Cond: ("Table2".col4 = a.col4)
               Buffers: shared hit=38808
               Worker 0:  actual time=191.587..191.590 rows=0 loops=1
                 Buffers: shared hit=12830
               Worker 1:  actual time=191.281..191.285 rows=0 loops=1
                 Buffers: shared hit=10816
               ->  Parallel Seq Scan on public.Table5 "Table2"  (cost=0.00..9045.05 rows=278305 width=48) (never executed)
                     Output: "Table2".col1, "Table2".col3, "Table2".col4
               ->  Parallel Hash  (cost=50291.20..50291.20 rows=3 width=70) (actual time=197.528..197.529 rows=0 loops=3)
                     Output: a.col1, a.col4, a.col5, a.col6
                     Buckets: 1024  Batches: 1  Memory Usage: 0kB
                     Buffers: shared hit=38728
                     Worker 0:  actual time=191.259..191.260 rows=0 loops=1
                       Buffers: shared hit=12790
                     Worker 1:  actual time=190.969..190.970 rows=0 loops=1
                       Buffers: shared hit=10776
                     ->  Parallel Seq Scan on public.Table4_a a  (cost=0.00..50291.20 rows=3 width=70) (actual time=194.885..194.885 rows=0 loops=3)
                           Output: a.col1, a.col4, a.col5, a.col6
                           Filter: ((a.col5 = 'X1'::type2) AND (a.col6 = 'X2'::text))
                           Rows Removed by Filter: 1027564
                           Buffers: shared hit=38728
                           Worker 0:  actual time=187.238..187.239 rows=0 loops=1
                             Buffers: shared hit=12790
                           Worker 1:  actual time=187.176..187.176 rows=0 loops=1
                             Buffers: shared hit=10776
         ->  Index Scan using Table4_pkey on public.Table4 "Table1"  (cost=0.42..0.46 rows=1 width=20) (never executed)
               Output: "Table1".col1, "Table1".col2
               Index Cond: ("Table1".col1 = "Table2".col4)
 Query Identifier: 1374759154717555017
 Planning:
   Buffers: shared hit=217
 Planning Time: 2.928 ms
 Execution Time: 228.955 ms
(49 rows)

Запрос нейросети

Как можно оптимизировать SQL запрос, используя прилагаемые таблицы, участвующие в запросе и план выполнения запроса

Результат работы нейросети DeepSeek

Комментарии (0)