Сообщество - Postgres DBA

Postgres DBA

155 постов 27 подписчиков

Популярные теги в сообществе:

6

PG_EXPECTO: Прицельный залп по проблемам производительности PostgreSQL

Взято с основного технического канала Postgres DBA (возможны правки в исходной статье).

Определи цель, заряди параметры, устрани проблему

Определи цель, заряди параметры, устрани проблему

Предисловие:

Когда производительность СУБД падает под нагрузкой, важно не стрелять наугад. Методика PG_EXPECTO предлагает сначала выявить ключевые цели — проблемные запросы и типы ожиданий — и только затем наносить точный удар, используя «большие пушки» оптимизации.

1. Анализ ожиданий СУБД на основании данных о диаграмме Парето

Проанализируй данные по типам ожидания в ходе инцидента производительности СУБД PostgreSQL

1. Анализ ожиданий типа IPC (Inter-Process Communication)

BgWorkerShutdown (40,62%) — наиболее проблемное событие:

  • Указывает на ожидание завершения фоновых процессов

  • Возможные причины:

  • Медленное завершение параллельных worker-процессов

  • Проблемы с освобождением ресурсов

  • Возможные deadlock-ситуации при завершении

ParallelFinish (25,4%) и ExecuteGather (21,14%):

  • Связаны с параллельным выполнением запросов

  • Высокие значения могут указывать на:

  • Слишком агрессивные настройки параллелизма

  • Конкуренцию за ресурсы между параллельными worker-ами

  • Неоптимальные планы выполнения параллельных запросов

2. Анализ ожиданий типа LWLock (Lightweight Locks)

BufferMapping (49,46%) — основная проблема в блокировках:

  • Указывает на конкуренцию за доступ к буферному кешу

  • Возможные причины:

  • Недостаточный размер shared_buffers

  • Высокая конкурентная нагрузка на горячие данные

  • Отсутствие или неэффективность индексов

SerializableXactHash (31,72%):

  • Связано с транзакциями уровня изоляции SERIALIZABLE

  • Может указывать на:

  • Высокую конкуренцию за структуры данных serializable транзакций

  • Необходимость оптимизации транзакций

Рекомендации по устранению проблем:

Для IPC-ожиданий:

1.Проверить настройки параллелизма:

max_parallel_workers_per_gather

max_parallel_workers

max_worker_processes

2.Анализировать логи на предмет ошибок воркеров

3.Рассмотреть уменьшение параллелизма для сложных запросов

Для LWLock-ожиданий:

  1. Оптимизировать работу с буферным кешем:

  2. Увеличить shared_buffers (если позволяет RAM)

  3. Проверить эффективность индексов

  4. Рассмотреть использование pg_prewarm для критичных данных

  5. Для Serializable транзакций:

  6. Проверить необходимость использования SERIALIZABLE изоляции

  7. Оптимизировать длительность транзакций

  8. Рассмотреть альтернативные уровни изоляции

Общие рекомендации:

  1. Сбор дополнительной информации:

  2. Получить полный список ожиданий за период инцидента

  3. Проверить мониторинг нагрузки на CPU, IO, память

  4. Возможные immediate-действия:

  5. Перераспределить нагрузку по времени

  6. Временно снизить параллелизм

  7. Добавить индексы на часто используемые поля в условиях WHERE/JOIN

Вывод:

Основные проблемы связаны с параллельным выполнением запросов и конкуренцией за буферный кеш. Рекомендуется поэтапная оптимизация настроек параллелизма и работы с памятью.

2. Анализ событий ожиданий по отдельным SQL-запросам на основании данных о диаграмме Парето

Проанализируй данные по событиям ожидания для SQL запросов по queryid в ходе инцидента производительности СУБД PostgreSQL

Ключевые выводы по корреляции типов ожиданий и запросов

1. Запросы с наибольшим влиянием на IPC-ожидания:

Топ-1 проблемный запрос (17,6% IPC ожиданий):

  • queryid: -1701015661318396920 3018 вызовов, 85k строк

  • Ожидания: BgWorkerShutdown, ParallelFinish, BufferIO, ExecuteGather

  • Анализ: Часто выполняемый параллельный запрос с проблемами завершения воркеров

Топ-2 (14% IPC ожиданий):

  • queryid: -678327810318891437 5183 вызовов, 5183 строк

  • Особенность: Каждый вызов возвращает ровно 1 строку

  • Высокая частота выполнения усиливает проблемы параллелизма

2. Запросы с наибольшим влиянием на LWLock-ожидания:

Топ-1 проблемный запрос (24,73% LWLock ожиданий):

  • queryid: -8182552260313222074 42,7k вызовов, 8,5M строк

  • Ожидания: BufferMapping

  • Анализ: Крайне часто выполняемый запрос с высокой конкуренцией за буферный кеш

Критический запрос, присутствующий в обеих категориях:

  • queryid: 5459520954633506046:

  • IPC: 5,34% (параллельное выполнение)

  • LWLock: 11,83% (ParallelHashJoin)

  • Особенность: 287 вызовов, но 217k строк на вызов (~759 строк/вызов)

  • Вероятно: Сложный аналитический запрос с хеш-соединениями

Детальный анализ проблемных паттернов:

A. Проблемы параллельного выполнения:

  1. Запросы с ParallelFinish и ExecuteGather:

  2. Все топ-8 IPC запросов имеют эти ожидания

  3. Рекомендация: Уменьшить max_parallel_workers_per_gather для этих queryid

  4. Конкретные кандидаты на оптимизацию:

  5. queryid: 1374759154717555017 2053 вызова, 0 строк

  6. Возможно, DML-операция с неправильным планом

  7. Имеет HashBuildHashInner - проблема с хеш-таблицами

B. Проблемы с блокировками буферов:

  1. Высокочастотные запросы:

  2. queryid: -8182552260313222074 42k вызовов

  3. Решение: Кэширование на уровне приложения, batch-обработка

  4. Serializable транзакции в db5:

  5. 4 из топ-8 LWLock запросов используют SerializableXactHash

  6. Все из базы db5

  7. Рекомендация: Пересмотреть уровень изоляции

Приоритетные действия:

1. Немедленные меры:

  • Для queryid: -8182552260313222074 (42k вызовов):

  • -- Добавить индексы на используемые предикаты

  • -- Внедрить кэширование на 5-10 минут

  • Для queryid: -1701015661318396920 (частый параллельный запрос):

  • -- Принудительно отключить параллельное выполнение

  • SET max_parallel_workers_per_gather = 0;

2. Среднесрочная оптимизация:

Группа 1: Частые параллельные запросы

  • queryid: -678327810318891437 (5k вызовов)

  • queryid: 3449463017331132112 (3k вызовов)

  • Действие: Оптимизация планов, уменьшение частоты выполнения

Группа 2: Запросы с хеш-соединениями

  • queryid: 5459520954633506046

  • queryid: 1374759154717555017

  • Действие: Увеличить work_mem, добавить индексы для соединений

3. Долгосрочные изменения:

Для базы db5 (Serializable транзакции):

  • Аудит необходимости SERIALIZABLE изоляции

  • Возможный переход на REPEATABLE READ

Настройка PostgreSQL:

# Увеличить для уменьшения BufferMapping

shared_buffers

# Оптимизация параллельных запросов

max_parallel_workers_per_gather = 2 (вместо 4-8)

parallel_setup_cost = 1000

parallel_tuple_cost = 0.1

# Увеличение для хеш-соединений

work_mem = 32MB (или больше)

Рекомендуемый план действий:

  1. Неделя 1: Оптимизация двух самых проблемных запросов

  2. queryid: -8182552260313222074 (кэширование)

  3. queryid: -1701015661318396920 (отключение параллелизма)

  4. Неделя 2: Анализ и оптимизация запросов с хеш-соединениями

  5. Добавление индексов

  6. Пересмотр логики запросов

  7. Неделя 3: Работа с часто выполняемыми запросами

  8. Батчинг

  9. Оптимизация бизнес-логики

  10. Неделя 4: Пересмотр уровня изоляции в db5

  11. Тестирование производительности при READ COMMITTED/REPEATABLE READ

Важно:

Все изменения должны тестироваться на staging-окружении с нагрузочным тестированием.

3.Сводный отчет по ожиданиям и проблемным SQL запросам

Сформируй сводный отчет по типам ожиданий и событиям ожиданий для проблемных SQL запросов в ходе инцидента производительности СУБД PostgreSQL

Контекст

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

Ключевые наблюдения

  1. Наиболее частые события ожидания:

  2. IPC: BgWorkerShutdown (40,62%)

  3. LWLock: BufferMapping (49,46%)

  4. Наиболее проблемные запросы:

  5. IPC: -1701015661318396920 (17,6% ожиданий)

  6. LWLock: -8182552260313222074 (24,73% ожиданий)

  7. Распределение по БД:

  8. db1 и db5 имеют наибольшее количество проблемных запросов

  9. role1 является общей ролью для всех проблемных запросов

  10. Паттерны ожиданий:

  11. Запросы с высоким числом CALLS часто имеют ожидания типа LWLock

  12. Запросы с большим количеством возвращаемых ROWS склонны к ожиданиям IPC

Рекомендации для дальнейшего анализа

  1. Сфокусироваться на оптимизации запросов с наибольшим процентом ожиданий

  2. Исследовать причины частых событий BgWorkerShutdown и BufferMapping

  3. Проанализировать работу параллельных операций (ParallelFinish, ExecuteGather)

  4. Проверить настройки, связанные с управлением блокировками и параллельным выполнением

Показать полностью 3
4

PG_EXPECTO: Принцип «только большие пушки» для СУБД

В силу ограниченности платформы Пикабу - статья сокращена. Полностью - на основном канале Дзен.

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

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

PG_EXPECTO — это не просто инструмент, а целостный комплекс, построенный на двух новых принципах.

1. Комплексный статистический анализ (CSSA)

Система больше не разделяет метрики СУБД и инфраструктуры. Она создаёт единый корреляционный снимок всей системы:

  • Вертикальная корреляция: Вместо отдельных событий система видит цепочки: например, как медленный запрос вызывает рост очереди дисковых операций (iowait).

  • Проактивность: Комплекс отслеживает не сбои, а тренды. Он может предупредить: «Обнаружена устойчивая тенденция к деградации из-за X», позволяя устранить проблему до того, как она затронет пользователей.

2. Семантический анализ и нейросеть-ассистент

Это и есть та самая «турбина». Вместо сырых графиков PG_EXPECTO использует нейросеть, которая интерпретирует данные в контексте.

Практический пример

Инцидент производительности СУБД в панели Zabbix

Регрессионный и корреляционный анализ производительности и ожиданий СУБД

Операционная скорость и ожидания СУБД

График изменения операционной скорости СУБД перед инцидентом

График изменения операционной скорости СУБД перед инцидентом

Исходные данные для семантического анализа нейросетью

1. Производительность и ожидания СУБД

2. Метрика vmstat

3.Метрика iostat для дискового устройства, файловой системы /data

3.Метрика iostat для дискового устройства, файловой системы /wal

1.Анализ производительности и ожиданий СУБД

Проведи анализ производительности и ожиданий СУБД PostgreSQL за заданный период

На основе предоставленных данных можно провести следующий анализ производительности и ожиданий СУБД PostgreSQL за период с 08:40 до 09:40:

📈 1. Регрессионный анализ

· Операционная скорость (SPEED):

o Коэффициент детерминации R2=0.88R2=0.88 → модель хорошо описывает данные.

o Угол наклона = -43.1 → скорость со временем снижается.

· Ожидания СУБД (WAITINGS):

o R2=0.75R2=0.75 → модель умеренно точна.

o Угол наклона = +40.87 → количество ожиданий растёт.

🔗 2. Корреляционный анализ

· SPEED vs WAITINGS: −0.763→ сильная отрицательная корреляция. Чем больше ожиданий, тем ниже скорость.

· WAITINGS vs IPC: 0.9039 → очень сильная положительная корреляция. Рост межпроцессного взаимодействия связан с ростом ожиданий.

· WAITINGS vs LWLOCK: 0.9587 → почти прямая зависимость. Ожидания сильно связаны с лёгкими блокировками (lightweight locks).

📉 3. Динамика изменения показателей

· SPEED:

o Начало: ~130 572

o Конец: ~90 948

o Снижение на ~30% за час.

· WAITINGS:

o Начало: 345

o Конец: 435

o Рост на ~26%.

· IPC и LWLOCK также растут, особенно заметно после 09:30.

⚠️ 4. Потенциальные проблемы

1. Рост LWLOCK → возможна конкуренция за лёгкие блокировки (например, в памяти).

2. Рост IPC → увеличивается нагрузка на межпроцессное взаимодействие, возможно из-за параллельных запросов.

3. Падение SPEED при росте WAITINGS → система тратит больше времени на ожидания (возможно, из-за блокировок или нехватки ресурсов).

2.Анализ vmstat

Проведи анализ метрик vmstat за заданный период

На основе данных vmstat за период с 08:40 до 09:40 проведён анализ производительности системы, на которой работает PostgreSQL. Вот ключевые выводы:

📊 1. Общие тенденции

· Время простоя CPU (idle) снизилось с 90% до 85%.

· Пользовательская нагрузка CPU (us) выросла с 9% до 13%.

· Системная нагрузка CPU (sy) стабильна (2%).

· Ожиданий ввода-вывода (wa) не наблюдается (0%).

🧠 2. Анализ памяти

Показатель Начало Конец Изменение Комментарий

swpd 710МБ 749МБ +39 МБ Незначительный рост свопа, система не испытывает нехватки RAM.

free 7185МБ 3221МБ -3964 МБ Сильное снижение свободной памяти, что говорит о её активном использовании.

cache 53521МБ 55937МБ +2416МБ Кэш памяти увеличивается, что нормально при активной работе с диском.

Вывод: Память активно используется, но свопинг минимален — система работает в рамках доступной памяти.

💽 3. Ввод-вывод (I/O)

Показатель Начало Конец Пиковое значение Тренд

bi (блоков ввода/с) 572 2409 2409 Резкий рост после 09:25.

bo (блоков вывода/с) 850 511 850 Снижение, затем стабилизация.

Вывод:

· После 09:25 резко выросла активность чтения с диска (bi).

· Это совпадает с падением SPEED в данных PostgreSQL.

· Возможно, это связано с увеличением количества запросов к данным, не помещающихся в кэш.

🔄 4. Системные показатели

Показатель Начало Конец Изменение

in (прерываний/с) 12833 14503 +13%

cs (переключений контекста/с) 6835 7033 +3%

Вывод: Умеренный рост прерываний и переключений контекста, что соответствует увеличению нагрузки.

📉 5. Связь с данными PostgreSQL

Сравнивая с предыдущим анализом cluster.txt, наблюдаем:

1. Рост ожиданий (WAITINGS) в PostgreSQL коррелирует с:

o Ростом bi (чтение с диска).

o Увеличением us (пользовательская нагрузка CPU).

2. Падение SPEED совпадает с:

o Увеличением bi (дисковые операции).

o Снижением idle CPU.

⚠️ 6. Потенциальные узкие места

1. Дисковый ввод-вывод: резкий рост bi после 09:25 может быть причиной замедления.

2. Использование памяти: свободная память уменьшается, но свопинг незначителен — пока критично.

3. Нагрузка на CPU: idle снижается, но не до критических значений.

✅ 7. Рекомендации

1. Оптимизация запросов:

o Увеличить shared_buffers, чтобы больше данных кэшировалось в памяти.

o Проверить наличие медленных запросов, выполняющих полное сканирование таблиц.

2. Мониторинг диска:

o Проверить скорость дисковых операций (например, с помощью iostat).

o Рассмотреть использование SSD или увеличение скорости дискового массива.

3. Настройка PostgreSQL:

o Увеличить effective_cache_size.

o Настроить work_mem для уменьшения операций на диске.

4. Системные настройки:

o Убедиться, что достаточно оперативной памяти для кэширования.

o Рассмотреть увеличение количества CPU, если нагрузка продолжит расти.

📈 8. Визуализация (рекомендуется)

· График SPEED и bi на одной оси времени → проверить корреляцию.

· График WAITINGS и us (пользовательская нагрузка CPU).

· График free memory и cache → отследить использование памяти.

График SPEED и bi на одной оси времени → проверить корреляцию.

Для наглядности значения bi увеличены в 10 раз

Для наглядности значения bi увеличены в 10 раз

Отрицательная корреляция: Рост значений bi сопровождается снижением операционной скорости.

График WAITINGS и us (пользовательская нагрузка CPU).

Для наглядности значения us увеличены в 10 раз

Для наглядности значения us увеличены в 10 раз

Положительная корреляция: Рост значений us сопровождается увеличением количества ожиданий.

График free memory и cache → отследить использование памяти.

Отрицательная корреляция: Рост значений free сопровождается снижением значений cache.

free -3964 МБ: Сильное снижение свободной памяти, что говорит о её активном использовании.

cache +2416 МБ: Кэш памяти увеличивается, что нормально при активной работе с диском.

3. Анализ iostat для файловой системы /data

На основе данных iostat для устройства vdb за период с 08:40 до 09:40 проведён анализ дисковых операций. Вот ключевые выводы:

📊 1. Общая картина

· Устройство vdb используется для операций чтения и записи.

· %util (загрузка диска) выросла с 5.59% до 6.7% — загрузка низкая.

· aqu_sz (средняя длина очереди) незначительно снизилась с 0.26 до 0.20 — очереди практически нет.

📈 2. Операции чтения (Read)

Показатель Начало Конец Изменение Комментарий

r/s (чтений/с) 40.00 27.92 -30% Снижение количества операций чтения.

rMB/s (МБ/с) 0.84 2.82 +235% Резкий рост объёма читаемых данных.

rareq_sz (средний размер запроса) 19.35 КБ 67.09 КБ +247% Запросы стали крупнее.

r_await (среднее время чтения, мс) 2.07 3.73 +80% Увеличение времени отклика на чтение.

Вывод:

После 09:20 резко вырос объём читаемых данных (rMB/s) и средний размер запроса (rareq_sz). Это говорит о переходе на более крупные блоки данных, возможно из-за изменения типа запросов (например, сканирование больших таблиц).

📉 3. Операции записи (Write)

Показатель Начало Конец Изменение

w/s (записей/с) 16.83 15.20 -10%

wMB/s (МБ/с) 0.62 0.26 -58%

wareq_sz (средний размер записи) 31.74КБ 4.22КБ -87%

w_await (среднее время записи, мс) 5.57 3.59 -36%

Вывод:

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

🔗 4. Корреляция с WAITINGS (PostgreSQL)

· После 09:20 наблюдается рост r_await (время чтения) и WAITINGS (ожидания в СУБД).

· Рост rMB/s совпадает с ростом WAITINGS и падением SPEED.

· Это подтверждает гипотезу о том, что увеличение времени доступа к диску влияет на производительность БД.

⚠️ 5. Потенциальные проблемы

1. Увеличение времени чтения (r_await): с 2 мс до 3.7 мс — рост на 80%.

2. Увеличение размера запросов (rareq_sz): с 19 КБ до 67 КБ — возможно, из-за отсутствия индексов или полного сканирования таблиц.

3. Рост объёма читаемых данных (rMB/s): в 3 раза — нагрузка на диск возросла.

✅ 6. Рекомендации

1. Оптимизация запросов PostgreSQL:

o Проверить выполнение планов запросов (EXPLAIN ANALYZE).

o Добавить индексы для часто сканируемых таблиц.

o Увеличить shared_buffers, чтобы больше данных кэшировалось в памяти.

2. Мониторинг диска:

o Проверить скорость диска (например, с помощью hdparm или fio).

o Убедиться, что диск не фрагментирован.

3. Настройка PostgreSQL:

o Увеличить effective_cache_size.

o Настроить random_page_cost и seq_page_cost в соответствии с типом диска.

4. Аппаратные улучшения:

o Рассмотреть переход на SSD, если используется HDD.

o Увеличить скорость дисковой подсистемы (RAID, кэширование).

📈 7. Визуализация (рекомендуется)

· График rMB/s и WAITINGS на одной оси времени.

· График r_await и rareq_sz.

· График %util и aqu_sz для оценки загрузки диска.

График rMB/s и WAITINGS.

Для наглядности значения rMB/s увеличены в 100 раз

Для наглядности значения rMB/s увеличены в 100 раз

Увеличение rMB/s сопровождается ростом количества ожиданий.

График r_await и rareq_sz.

График %util и aqu_sz для оценки загрузки диска.

Для наглядности значения aqu_sz увеличены в 10 раз

Для наглядности значения aqu_sz увеличены в 10 раз

🎯 Заключение

Основная причина деградации производительности — рост размера и времени выполнения дисковых операций чтения, что приводит к увеличению лёгких блокировок (LWLOCK) и ожиданий (WAITINGS) в СУБД.

Приоритет действий:

1. Оптимизация запросов и индексов

2. Настройка параметров PostgreSQL (память, I/O)

3. Аппаратное улучшение дисковых подсистем

4. Мониторинг и алертинг ключевых метрик

Система не испытывает критической нехватки CPU или памяти, но дисковый ввод-вывод стал узким местом из-за изменившегося характера нагрузки (крупные запросы на чтение).

Показать полностью 11
1

Невидимый чемпион: как EXISTS побеждает IN в бою за ресурсы PostgreSQL

Взято с основного технического канала Postgres DBA (возможны правки в исходной статье).

Паттерн оптимизации - который смог. Доказанная оптимизация: EXISTS быстрее IN в PostgreSQL.

Паттерн оптимизации - который смог. Доказанная оптимизация: EXISTS быстрее IN в PostgreSQL.

Предисловие

В сценариях с параллельными запросами и острой конкуренцией за ресурсы паттерн EXISTS показал себя как однозначно более эффективное решение для PostgreSQL.

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

kznalp/PG_EXPECTO

pg-expecto pg_expecto

Эксперименты с Демобазой 2.0

"Демобаза 2.0" нагрузочное тестирование : СУБД оказалась устойчива к выбору между Join и коррелированным подзапросом.

Начало экспериментов "IN vs EXISTS"

Очередной раунд тестов: EXISTS против IN в условиях параллелизма.

Тестовый запрос-1 : IN

SELECT DISTINCT a.country

FROM airports_data a

JOIN routes r ON (r.arrival_airport = a.airport_code)

WHERE duration IN

(

'09:45:00' , '11:50:00' , '02:40:00' , '05:50:00' , '15:25:00' , '04:30:00' , '11:00:00' , '07:15:00' , '12:40:00' , '03:40:00' , '05:15:00' , '08:35:00' , '10:35:00' , '07:30:00' , '09:35:00' , '04:15:00' , '11:45:00' , '04:05:00' , '01:10:00' , '19:50:00' , '07:55:00' , '01:35:00' , '16:05:00' , '08:15:00' , '04:00:00' , '08:45:00' , '12:25:00' , '16:40:00' , '07:25:00' , '01:50:00' , '14:35:00' , '12:45:00' , '01:20:00' , '02:55:00' , '20:20:00' , '10:45:00' , '02:45:00' , '12:55:00' , '08:25:00' , '00:45:00' , '02:00:00' , '01:15:00' , '08:00:00' , '04:10:00' , '11:35:00' , '16:45:00' , '17:15:00' , '14:40:00' , '15:35:00' , '15:50:00' , '13:30:00' , '04:25:00' , '01:25:00' , '14:10:00' , '15:15:00' , '08:55:00' , '07:00:00' , '05:05:00' , '06:45:00' , '14:20:00' , '09:50:00' , '08:10:00' , '11:30:00' , '13:45:00' , '04:35:00' , '01:30:00' , '15:10:00' , '05:25:00' , '05:20:00' , '16:30:00' , '14:45:00' , '00:40:00' , '13:15:00' , '12:50:00' , '09:05:00' , '17:30:00' , '13:05:00' , '13:10:00' , '10:50:00' , '07:10:00' , '05:00:00' , '10:40:00' , '03:25:00' , '09:00:00' , '13:00:00' , '10:20:00' , '16:20:00' , '08:05:00' , '07:40:00' , '14:30:00' , '16:10:00' , '03:50:00' , '08:30:00' , '05:40:00' , '06:20:00' , '05:30:00' , '11:05:00' , '11:55:00' , '04:20:00' , '06:40:00' );

План выполнения тестового запроса-1 : IN

HashAggregate (cost=282.56..284.86 rows=230 width=54) (actual time=5.604..5.609 rows=17 loops=1)

Group Key: a.country

Batches: 1 Memory Usage: 40kB

-> Nested Loop (cost=0.54..276.22 rows=2534 width=54) (actual time=0.104..3.559 rows=2534 loops=1)

-> Seq Scan on routes r (cost=0.25..185.13 rows=2534 width=4) (actual time=0.049..1.650 rows=2534 loops=1)

Filter: (duration = ANY ('{09:45:00,11:50:00,02:40:00,05:50:00,15:25:00,04:30:00,11:00:00,07:15:00,12:40:00,03:40:00,05:15:00,08:35:00,10:35:00,07:30:00,09:35:00,04:15:00,11:45:00,04:05:00,01:10:00,19:50:00,07:55:00,01:35:

00,16:05:00,08:15:00,04:00:00,08:45:00,12:25:00,16:40:00,07:25:00,01:50:00,14:35:00,12:45:00,01:20:00,02:55:00,20:20:00,10:45:00,02:45:00,12:55:00,08:25:00,00:45:00,02:00:00,01:15:00,08:00:00,04:10:00,11:35:00,16:45:00,17:15:00,14:40:00,

15:35:00,15:50:00,13:30:00,04:25:00,01:25:00,14:10:00,15:15:00,08:55:00,07:00:00,05:05:00,06:45:00,14:20:00,09:50:00,08:10:00,11:30:00,13:45:00,04:35:00,01:30:00,15:10:00,05:25:00,05:20:00,16:30:00,14:45:00,00:40:00,13:15:00,12:50:00,09:

05:00,17:30:00,13:05:00,13:10:00,10:50:00,07:10:00,05:00:00,10:40:00,03:25:00,09:00:00,13:00:00,10:20:00,16:20:00,08:05:00,07:40:00,14:30:00,16:10:00,03:50:00,08:30:00,05:40:00,06:20:00,05:30:00,11:05:00,11:55:00,04:20:00,06:40:00}'::int

erval[]))

Rows Removed by Filter: 3258

-> Memoize (cost=0.29..0.39 rows=1 width=58) (actual time=0.000..0.000 rows=1 loops=2534)

Cache Key: r.arrival_airport

Cache Mode: logical

Hits: 2461 Misses: 73 Evictions: 0 Overflows: 0 Memory Usage: 11kB

-> Index Scan using airports_data_pkey on airports_data a (cost=0.28..0.38 rows=1 width=58) (actual time=0.007..0.007 rows=1 loops=73)

Index Cond: (airport_code = r.arrival_airport)

Тестовый запрос-2 : EXISTS

SELECT DISTINCT a.country

FROM airports_data a

WHERE EXISTS (

SELECT 1

FROM routes r

WHERE r.arrival_airport = a.airport_code

AND r.duration IN (

'09:45:00', '11:50:00', '02:40:00', '05:50:00', '15:25:00', '04:30:00', '11:00:00', '07:15:00',

'12:40:00', '03:40:00', '05:15:00', '08:35:00', '10:35:00', '07:30:00', '09:35:00', '04:15:00',

'11:45:00', '04:05:00', '01:10:00', '19:50:00', '07:55:00', '01:35:00', '16:05:00', '08:15:00',

'04:00:00', '08:45:00', '12:25:00', '16:40:00', '07:25:00', '01:50:00', '14:35:00', '12:45:00',

'01:20:00', '02:55:00', '20:20:00', '10:45:00', '02:45:00', '12:55:00', '08:25:00', '00:45:00',

'02:00:00', '01:15:00', '08:00:00', '04:10:00', '11:35:00', '16:45:00', '17:15:00', '14:40:00',

'15:35:00', '15:50:00', '13:30:00', '04:25:00', '01:25:00', '14:10:00', '15:15:00', '08:55:00',

'07:00:00', '05:05:00', '06:45:00', '14:20:00', '09:50:00', '08:10:00', '11:30:00', '13:45:00',

'04:35:00', '01:30:00', '15:10:00', '05:25:00', '05:20:00', '16:30:00', '14:45:00', '00:40:00',

'13:15:00', '12:50:00', '09:05:00', '17:30:00', '13:05:00', '13:10:00', '10:50:00', '07:10:00',

'05:00:00', '10:40:00', '03:25:00', '09:00:00', '13:00:00', '10:20:00', '16:20:00', '08:05:00',

'07:40:00', '14:30:00', '16:10:00', '03:50:00', '08:30:00', '05:40:00', '06:20:00', '05:30:00',

'11:05:00', '11:55:00', '04:20:00', '06:40:00'

));

План выполнения тестового запроса-2 : EXISTS

Unique (cost=299.91..300.27 rows=73 width=54) (actual time=3.071..3.114 rows=17 loops=1)

-> Sort (cost=299.91..300.09 rows=73 width=54) (actual time=3.069..3.076 rows=73 loops=1)

Sort Key: a.country

Sort Method: quicksort Memory: 25kB

-> Nested Loop (cost=191.75..297.65 rows=73 width=54) (actual time=2.457..2.942 rows=73 loops=1)

-> HashAggregate (cost=191.47..192.19 rows=73 width=4) (actual time=2.408..2.421 rows=73 loops=1)

Group Key: r.arrival_airport

Batches: 1 Memory Usage: 24kB

-> Seq Scan on routes r (cost=0.25..185.13 rows=2534 width=4) (actual time=0.048..1.834 rows=2534 loops=1)

Filter: (duration = ANY ('{09:45:00,11:50:00,02:40:00,05:50:00,15:25:00,04:30:00,11:00:00,07:15:00,12:40:00,03:40:00,05:15:00,08:35:00,10:35:00,07:30:00,09:35:00,04:15:00,11:45:00,04:05:00,01:10:00,19:50:00,07:

55:00,01:35:00,16:05:00,08:15:00,04:00:00,08:45:00,12:25:00,16:40:00,07:25:00,01:50:00,14:35:00,12:45:00,01:20:00,02:55:00,20:20:00,10:45:00,02:45:00,12:55:00,08:25:00,00:45:00,02:00:00,01:15:00,08:00:00,04:10:00,11:35:00,16:45:00,17:15:

00,14:40:00,15:35:00,15:50:00,13:30:00,04:25:00,01:25:00,14:10:00,15:15:00,08:55:00,07:00:00,05:05:00,06:45:00,14:20:00,09:50:00,08:10:00,11:30:00,13:45:00,04:35:00,01:30:00,15:10:00,05:25:00,05:20:00,16:30:00,14:45:00,00:40:00,13:15:00,

12:50:00,09:05:00,17:30:00,13:05:00,13:10:00,10:50:00,07:10:00,05:00:00,10:40:00,03:25:00,09:00:00,13:00:00,10:20:00,16:20:00,08:05:00,07:40:00,14:30:00,16:10:00,03:50:00,08:30:00,05:40:00,06:20:00,05:30:00,11:05:00,11:55:00,04:20:00,06:

40:00}'::interval[]))

Rows Removed by Filter: 3258

-> Index Scan using airports_data_pkey on airports_data a (cost=0.28..1.46 rows=1 width=58) (actual time=0.006..0.006 rows=1 loops=73)

Index Cond: (airport_code = r.arrival_airport)

Результаты сравнительного нагрузочного тестирования

Операционная скорость

График изменения операционной скорости в ходе нагрузочного тестирования

График изменения операционной скорости в ходе нагрузочного тестирования

График изменения относительной разницы операционной скорости в ходе нагрузочного тестирования при использовании EXISTS по сравнению с IN

График изменения относительной разницы операционной скорости в ходе нагрузочного тестирования при использовании EXISTS по сравнению с IN

Среднее превышение операционной скорости при использовании EXISTS составило 23.25%.

Ожидания СУБД

График изменения ожиданий СУБД в ходе нагрузочного тестирования

График изменения ожиданий СУБД в ходе нагрузочного тестирования

График изменения относительной разницы ожиданий СУБД в ходе нагрузочного тестирования при использовании EXISTS по сравнению с IN

График изменения относительной разницы ожиданий СУБД в ходе нагрузочного тестирования при использовании EXISTS по сравнению с IN

Среднее снижение ожиданий СУБД при использовании EXISTS составило 90.60%.

Итог

Использование паттерна EXIST повышает производительность СУБД в среднем на 20%.

Показать полностью 5
6

Нейросеть против PostgreSQL: системные ошибки AI в прогнозировании производительности под нагрузкой

Взято с основного технического канала Postgres DBA (возможны правки в исходной статье).

Стоимостная модель против реальности: как нейросеть не смогла предсказать поведение PostgreSQL при 22 параллельных сессиях

Стоимостная модель против реальности: как нейросеть не смогла предсказать поведение PostgreSQL при 22 параллельных сессиях

Предисловие

Использование нейросетей для оптимизации баз данных кажется перспективным направлением, но реальная эффективность таких систем требует тщательной проверки. В данном исследовании проанализирована способность нейросетевой модели точно прогнозировать производительность СУБД PostgreSQL в условиях экстремальной параллельной нагрузки. Результаты демонстрируют систематические ошибки AI, связанные с неспособностью учесть динамические аспекты работы СУБД.

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

kznalp/PG_EXPECTO

pg-expecto pg_expecto

Методология эксперимента

Для тестирования прогностической способности нейросети была развернута тестовая среда PostgreSQL 17 с конфигурацией CPU=8 ядер, RAM=8GB. Создана таблица pgbench_test с 1 млн записей, выполнялся запрос с соединением по внешнему ключу. Анализировались два метода доступа: последовательное сканирование (Seq Scan) и индексное сканирование (Index Only Scan) с покрывающим индексом idx_pgbench_test_bid_abalance.

Подробности эксперимента(прогноз и анализ нейросети)

Использование нейросети для прогноза производительности СУБД PostgreSQL

Прогноз нейросети и его несоответствие реальности

Нейросеть, проанализировав планы выполнения запросов, выдала категоричный прогноз:

  • Index Only Scan: оптимальная производительность (~2.5 ms) даже при 22 параллельных сессиях

  • Seq Scan: катастрофическая деградация производительности (+400%) при превышении 10 сессий

Реальные результаты нагрузочного тестирования показали принципиально иную картину:

  • Среднее преимущество Seq Scan составило 9%

  • В самой финальной фазе теста Index Only Scan демонстрировал лучшую производительность

  • Оба метода показали сопоставимую устойчивость к нагрузке

График изменения операционной скорости в ходе нагрузочного тестирования при использовании метода доступа Seq Scan и Index only Scan

График изменения операционной скорости в ходе нагрузочного тестирования при использовании метода доступа Seq Scan и Index only Scan

Критический анализ ошибок нейросетевого прогноза

1. Неспособность моделировать динамическое кэширование

Нейросеть основывалась на статической стоимостной модели, игнорируя эффект прогрева БД. В реальности Seq Scan выигрывал от полного размещения данных в shared_buffers, что нивелировало его основной недостаток - физический I/O.

2. Игнорирование конкуренции за индексные структуры

Модель недооценила contention в B-деревьях при высокой параллельности. Index Only Scan, предсказанный как идеальное решение, столкнулся с блокировками страниц индекса при одновременном доступе 22 сессий.

3. Ошибочная оценка масштабируемости

Нейросеть переоценила линейность индексного доступа и недооценила эффективность параллельного Seq Scan. Распределенная нагрузка workers оказалась стабильнее концентрированной нагрузки на индекс.

Системные ограничения нейросетей в экспертизе СУБД

Проведенный эксперимент выявил фундаментальные проблемы применения AI для анализа производительности БД:

Статичность моделей - нейросети работают с моментальными снимками системы, не учитывая временные аспекты работы СУБД.

Игнорирование конкурентного доступа - модели не способны адекватно предсказать поведение системы при одновременном доступе множества процессов.

Неучет аппаратных ограничений - прогноз не учитывал реальные особенности управления памятью и планирования задач в PostgreSQL.

Рекомендации и выводы

Для эффективного использования нейросетей в экспертизе производительности PostgreSQL необходимо:

  1. Обогащение данных обучения динамическими метриками (pg_stat_, pg_statio_)

  2. Учет временны́х характеристик - продолжительности теста, эффекта прогрева

  3. Моделирование реальной конкуренции - тестирование в условиях, приближенных к продуктивным.

  4. Верификация прогнозов обязательным нагрузочным тестированием

Заключение

Нейросетевые модели демонстрируют ограниченную эффективность в прогнозировании поведения СУБД под высокой параллельной нагрузкой. Традиционные методы экспертизы с обязательным эмпирическим тестированием сохраняют критическую важность для принятия архитектурных решений в системах управления базами данных.

Показать полностью 1
3

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

Взято с основного технического канала Postgres DBA (возможны правки в исходной статье).

Преодоление предела: почему традиционные методы оптимизации бессильны против высокой параллельности и что приходит им на смену.

Преодоление предела: почему традиционные методы оптимизации бессильны против высокой параллельности и что приходит им на смену.

Результаты новых исследований указывают на необходимость создания отдельного направления по оптимизации параллельных процессов в СУБД PostgreSQL

Казань, 21.11.2025 – По результатам серии экспериментов, проведенных, был выявлен фундаментальный пробел в современных методологиях оптимизации производительности систем управления базами данных (СУБД). Установлено, что традиционные паттерны и методики оптимизации демонстрируют резкое снижение эффективности или полную неприменимость в средах с высоким уровнем параллельной обработки транзакций.

Эмпирические данные свидетельствуют о том, что при значительной конкурентной нагрузке, когда множество процессов обращаются к данным одновременно, классические подходы, такие как тонкая настройка отдельных запросов или индексация, оказываются недостаточными. Вместо ожидаемого линейного роста производительности наблюдаются нелинейные эффекты, включая интенсивную борьбу за ресурсы (contention), блокировки (locks) и деградацию общей пропускной способности системы.

На основании полученных результатов был сделан вывод о назревшей необходимости системного пересмотра принципов анализа и оптимизации СУБД. Для обеспечения устойчивой работы высоконагруженных информационных систем на базе СУБД PostgreSQL требуется выделение и глубокая проработка нового специализированного подраздела, посвященного исключительно оптимизации параллельных процессов (Parallel Processes Optimization).

Введение данной дисциплины предполагает фокусировку на таких аспектах, как:

  • Анализ и минимизация конфликтов блокировок на уровне строк и таблиц.

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

  • Разработка специализированных метрик для диагностики узких мест, специфичных для параллельной работы.

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

Этот шаг является закономерным ответом на вызовы, связанные с ростом объемов данных и требований к масштабируемости современных приложений. Новая парадигма оптимизации позволит вывести управление производительностью СУБД PostgreSQL на качественно новый уровень, обеспечивая стабильность и эффективность в высокопараллельных средах.

Контактная информация :

  • Ринат Сунгатуллин

  • kznalp@yandex.ru

  • Postgres DBA

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

kznalp/PG_EXPECTO

pg-expecto pg_expecto

Показать полностью
4

ИИ как опасный советчик: Почему нейросетям нельзя доверять настройку производительности PostgreSQL

Взято с основного технического канала Postgres DBA (возможны правки в исходной статье).

Нейросеть видит паттерны, но не чувствует боль базы данных.

Нейросеть видит паттерны, но не чувствует боль базы данных.

Аннотация

В статье проводится сравнительный анализ эффективности использования оператора JOIN и коррелированного подзапроса в СУБД PostgreSQL в условиях высокой параллельной нагрузки. На основе экспериментальных данных опровергаются универсальные рекомендации систем искусственного интеллекта и выявляются ключевые факторы, влияющие на производительность.

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

kznalp/PG_EXPECTO

pg-expecto pg_expecto

1. Постановка задачи

Рассматривается вопрос выбора оптимального паттерна для выполнения запросов при высокой параллельной нагрузке на СУБД: использование JOIN или коррелированного подзапроса.

Были получены рекомендации от нейросетевых моделей:

  • «Ask Postgres»: Для нагрузочных тестов с растущей параллельностью всегда используйте версию с JOIN. Коррелированные подзапросы с агрегациями — плохая практика в сценариях с высокой конкуренцией.

  • «DeepSeek»: Для данного сценария производительность будет выше при использовании запроса с LEFT JOIN и GROUP BY.

2. Детали эксперимента

Полное описание эксперимента:

Опасный мираж оптимизации: почему нейросетевые советы по СУБД PostgreSQL убивают производительность под нагрузкой.

2.1. Тестовый запрос с использованием JOIN

SELECT

c.customer_id, COUNT(o.order_id) AS orders_count

FROM customers c

LEFT JOIN orders o ON c.customer_id = o.customer_id

GROUP BY c.customer_id;

План выполнения

HashAggregate (cost=35.85..37.25 rows=140 width=12) (actual time=0.622..0.629 rows=25 loops=1)

Group Key: c.customer_id

Batches: 1 Memory Usage: 40kB

-> Hash Right Join (cost=13.15..30.85 rows=1000 width=8) (actual time=0.077..0.429 rows=1000 loops=1)

Hash Cond: (o.customer_id = c.customer_id)

-> Seq Scan on orders o (cost=0.00..15.00 rows=1000 width=8) (actual time=0.035..0.148 rows=1000 loops=1)

-> Hash (cost=11.40..11.40 rows=140 width=4) (actual time=0.028..0.028 rows=25 loops=1)

Buckets: 1024 Batches: 1 Memory Usage: 9kB

-> Seq Scan on customers c (cost=0.00..11.40 rows=140 width=4) (actual time=0.018..0.021 rows=25 loops=1)

Planning Time: 0.221 ms

Execution Time: 0.787 ms

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

SELECT c.customer_id,

(SELECT COUNT(o.order_id)

FROM orders o

WHERE o.customer_id = c.customer_id) AS orders_count

FROM customers c;

План выполнения

Seq Scan on customers c (cost=0.00..1015.20 rows=140 width=12) (actual time=0.093..0.614 rows=25 loops=1)

SubPlan 1

-> Aggregate (cost=7.16..7.17 rows=1 width=8) (actual time=0.023..0.023 rows=1 loops=25)

-> Bitmap Heap Scan on orders o (cost=1.56..7.06 rows=40 width=4) (actual time=0.007..0.017 rows=40 loops=25)

Recheck Cond: (customer_id = c.customer_id)

Heap Blocks: exact=125

-> Bitmap Index Scan on idx_orders_customer_id (cost=0.00..1.55 rows=40 width=0) (actual time=0.004..0.004 rows=40 loops=25)

Index Cond: (customer_id = c.customer_id)

Planning Time: 0.145 ms

Execution Time: 0.718 ms

2.3. Сравнение производительности СУБД в ходе нагрузочного тестирования

В ходе сравнительного нагрузочного тестирования была измерена операционная скорость СУБД при использовании оператора JOIN и коррелированного подзапроса. Согласно результатам, среднее снижение операционной скорости при использовании JOIN составило 288% по сравнению с коррелированным подзапросом.

График изменения операционной скорости в ходе нагрузочного тестирования.

График изменения операционной скорости в ходе нагрузочного тестирования.

3. Анализ причин некорректности рекомендаций нейросетей

3.1. Применение статических эвристик вместо анализа плана выполнения

Нейросети опираются на общие рекомендации, такие как:

  • «JOIN обычно эффективнее подзапросов»;

  • «Избегайте N+1 запросов»;

  • «Коррелированные подзапросы плохо масштабируются».

Однако они не анализируют конкретные планы выполнения запросов в условиях высокой нагрузки и конкуренции за ресурсы.

3.2. Игнорирование паттернов доступа к данным

Анализ планов выполнения показал:

  • Запрос 1 (JOIN): Seq Scan on orders (полное сканирование таблицы).

  • Запрос 2 (Подзапрос): Bitmap Index Scan on idx_orders_customer_id (точечный доступ по индексу).

При параллельных соединениях:

  • Количество сессий × Seq Scan = количество полных сканирований таблицы orders.

  • Количество сессий × Index Scan = равномерно распределенная нагрузка на чтение.

3.3. Неучёт механизмов блокировки и конфликтов ресурсов

  • Проблема JOIN при высокой конкуренции: Все сессии одновременно читают одни и те же страницы таблицы orders, что вызывает конфликт ресурсов (contention) на буферный кэш и подсистему ввода-вывода.

  • Преимущество подзапроса: Каждая сессия работает с разными частями индекса, что снижает конкуренцию за блокировки и улучшает параллелизм.

3.4. Разный профиль использования памяти

  • JOIN: Memory Usage: 40 kB + хэш-таблица.

  • Подзапрос: Точечное использование памяти для каждого клиента.

При множественных сессиях JOIN создает значительную нагрузку на shared_buffers.

4. Критические факторы, упускаемые нейросетями

  1. Влияние на shared_buffers: Множественные последовательные сканирования вытесняют полезные данные из кэша.

  2. Lock contention: Конкуренция за одни и те же ресурсы (блокировки).

  3. Распределение операций ввода-вывода: Индексные чтения лучше распределены.

  4. Параметры PostgreSQL: Значения work_mem, shared_buffers, random_page_cost и других настроек существенно влияют на результат.

5. Причины ошибок в рекомендациях нейросетевых моделей

Нейросети обучаются на синтетических или упрощённых данных, для которых характерны:

  • Небольшой объём наборов данных (TPC-H, TPC-DS).

  • Низкая параллельность запросов (1–10 соединений).

  • Идеализированные индексы.

  • Отсутствие блокировок и конкуренции за ресурсы (ЦП, ввод-вывод).

В результате модель вырабатывает универсальное правило «JOIN всегда лучше», которое не работает в реальных условиях высокой конкуренции.

6. Заключение

Рекомендации, сгенерированные нейросетями, основаны на общих эвристиках и не могут учитывать всех особенностей конкретной эксплуатационной среды. Они не заменяют глубокого анализа планов выполнения запросов и понимания архитектуры СУБД под нагрузкой. Проведенный эксперимент наглядно демонстрирует важность практического тестирования и невозможность слепого следования автоматизированным советам.

Показать полностью 1
0

"Демобаза 2.0" нагрузочное тестирование : СУБД оказалась устойчива к выбору между Join и коррелированным подзапросом

Взято с основного технического канала Postgres DBA (возможны правки в исходной статье).

СУБД оказалась прочнее, чем кажется: почему выбор запроса может не иметь значения для общей производительности системы.

СУБД оказалась прочнее, чем кажется: почему выбор запроса может не иметь значения для общей производительности системы.

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

kznalp/PG_EXPECTO

pg-expecto pg_expecto

Предисловие:

Принято считать, что выбор между JOIN и коррелированным подзапросом — одна из ключевых задач оптимизации, способная кардинально повлиять на нагрузку базы данных. В качестве эксперимента, было проведено нагрузочное тестирование, используя Демобазу 2.0 в качестве полигона и vmstat для мониторинга изменений со стороны инфраструктуры, готовясь наглядно продемонстрировать превосходство одного подхода над другим.

Однако результаты оказались неожиданными. Исследование показало практическое отсутствие существенного влияния выбранной структуры запроса на общую производительность СУБД и сервера. В данной статье показано, что в контексте современной оптимизации запросов и мощного аппаратного обеспечения, "страшилка" о катастрофических последствиях использования коррелированных подзапросов часто преувеличена. Нагрузочное тестирование выявило, что СУБД успешно справляется с обоими типами запросов, а реальное влияние на метрики vmstat оказалось малым, что позволяет разработчикам в подобных случаях делать выбор, основываясь на читаемости кода, а не на гипотетических рисках для производительности.

Демобаза 2.0

PG_EXPECTO 4 + Демобаза 2.0 : использование тестовой базы данных для нагрузочного тестирования СУБД

Тестовая виртуальная машина

CPU = 8

RAM = 8GB

PostgreSQL 17

Тестовый сценарий-4.1 (JOIN)

-- Запросы с JOIN

CREATE OR REPLACE FUNCTION scenario5() RETURNS integer AS $$

DECLARE

test_rec record ;

BEGIN

SET application_name = 'scenario4';

WITH seats_available AS

( SELECT airplane_code, fare_conditions, count( * ) AS seats_cnt

FROM bookings.seats

GROUP BY airplane_code, fare_conditions

), seats_booked AS

( SELECT flight_id, fare_conditions, count( * ) AS seats_cnt

FROM bookings.segments

GROUP BY flight_id, fare_conditions

), overbook AS (

SELECT f.flight_id, r.route_no, r.airplane_code, sb.fare_conditions,

sb.seats_cnt AS seats_booked,

sa.seats_cnt AS seats_available

FROM bookings.flights AS f

JOIN bookings.routes AS r ON r.route_no = f.route_no AND r.validity @> f.scheduled_departure

JOIN seats_booked AS sb ON sb.flight_id = f.flight_id

JOIN seats_available AS sa ON sa.airplane_code = r.airplane_code

AND sa.fare_conditions = sb.fare_conditions

WHERE sb.seats_cnt > sa.seats_cnt

)

SELECT count(*) overbookings,

CASE WHEN count(*) > 0 THEN 'ERROR: overbooking' ELSE 'Ok' END verdict

INTO test_rec

FROM overbook;

return 0 ;

END

$$ LANGUAGE plpgsql;

Тестовый сценарий-4.2 (Коррелированный подзапрос)

Создание индексов

demo=# CREATE INDEX CONCURRENTLY idx_seats_airplane_fare ON bookings.seats(airplane_code, fare_conditions);

CREATE INDEX

demo=# CREATE INDEX CONCURRENTLY idx_segments_flight_fare ON bookings.segments(flight_id, fare_conditions);

CREATE INDEX

demo=# CREATE INDEX CONCURRENTLY idx_routes_no_validity ON bookings.routes(route_no, validity);

CREATE INDEX

Изменение SQL запроса

-- коррелированный подзапрос

CREATE OR REPLACE FUNCTION scenario5() RETURNS integer AS $$

DECLARE

test_rec record ;

BEGIN

SET application_name = 'scenario4';

WITH seats_agg AS MATERIALIZED (

SELECT

airplane_code,

fare_conditions,

COUNT(*) AS seats_total

FROM bookings.seats

GROUP BY airplane_code, fare_conditions

)

SELECT

COUNT(*) AS overbookings,

CASE WHEN COUNT(*) > 0 THEN 'ERROR: overbooking' ELSE 'Ok' END AS verdict

INTO test_rec

FROM (

SELECT 1

FROM bookings.flights f

JOIN bookings.routes r ON r.route_no = f.route_no AND r.validity @> f.scheduled_departure

JOIN (

SELECT

flight_id,

fare_conditions,

COUNT(*) AS seats_booked

FROM bookings.segments

GROUP BY flight_id, fare_conditions

) sb ON sb.flight_id = f.flight_id

WHERE sb.seats_booked > (

SELECT sa.seats_total

FROM seats_agg sa

WHERE sa.airplane_code = r.airplane_code

AND sa.fare_conditions = sb.fare_conditions

)

) overbooked;

Анализ результатов нагрузочного тестирования - производительность СУБД

Операционная скорость СУБД

График изменения операционной скорости СУБД для нагрузочного тестирования с использованием Join и Коррелированного подзапроса.

График изменения операционной скорости СУБД для нагрузочного тестирования с использованием Join и Коррелированного подзапроса.

График изменения относительной разницы операционной скорости для нагрузочного тестирования с использованием коррелированного подзапроса по сравнению с использованием JOIN.

График изменения относительной разницы операционной скорости для нагрузочного тестирования с использованием коррелированного подзапроса по сравнению с использованием JOIN.

Средняя разница операционной скорости СУБД при использовании JOIN и Коррелированного подзапроса составила 0.58%.

Вывод по результатам анализа метрик производительности СУБД

Использование для тестового запроса JOIN или Коррелированного подзапроса - не оказывает влияния на производительность СУБД в целом и тестового сценария в частности.

Показать полностью 2
3

Опасный мираж оптимизации: почему нейросетевые советы по СУБД PostgreSQL убивают производительность под нагрузкой

Взято с основного технического канала Postgres DBA (возможны правки в исходной статье).

LLM могут написать запрос, но не несут ответственности за его выполнение в 50 параллельных сессий.

LLM могут написать запрос, но не несут ответственности за его выполнение в 50 параллельных сессий.

Производительность СУБД — это не только одиночные запросы, но и их поведение в условиях высокой конкуренции за ресурсы. Нейросети, не обладая контекстом реальной нагрузки, часто предлагают решения, которые выглядят логично в вакууме, но приводят к катастрофическим последствиям при масштабировании. История о двух запросах — прямое тому доказательство.

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

kznalp/PG_EXPECTO

pg-expecto pg_expecto

Постановка эксперимента

Сравнить производительность тестовых запросов в ходе нагрузочного тестирования

Тестовый запрос-1 : с использованием JOIN

SELECT

c.customer_id, COUNT(o.order_id) AS orders_count

FROM customers c

LEFT JOIN orders o ON c.customer_id = o.customer_id

GROUP BY c.customer_id;

Тестовый запрос-2 : с использованием коррелированного подзапроса

SELECT c.customer_id,

(SELECT COUNT(o.order_id)

FROM orders o

WHERE o.customer_id = c.customer_id) AS orders_count

FROM customers c;

Нагрузка на СУБД

Вопрос нейросети

Тестовые таблицы

"-- Create the customers table

CREATE TABLE customers (

customer_id SERIAL PRIMARY KEY,

name VARCHAR(255) NOT NULL );

-- Insert 25 random customer records

INSERT INTO customers (name) VALUES ('Alice Smith'), ('Bob Johnson'), ('Charlie Brown'), ('Diana Prince'), ('Ethan Hunt'), ('Fiona Gallagher'), ('George Miller'), ('Hannah Abbott'), ('Ian Fleming'), ('Jane Austen'), ('Kevin Spacey'), ('Laura Croft'), ('Michael Jordan'), ('Nancy Drew'), ('Oliver Twist'), ('Penelope Cruz'), ('Quentin Tarantino'), ('Rachel Green'), ('Steve Rogers'), ('Tina Turner'), ('Ursula K. Le Guin'), ('Victor Hugo'), ('Wendy Darling'), ('Xavier Roberts'), ('Yvonne Craig');

-- Create the orders table

CREATE TABLE orders (

order_id SERIAL PRIMARY KEY,

customer_id INT,

order_date TIMESTAMP,

FOREIGN KEY (customer_id) REFERENCES customers(customer_id) ON DELETE CASCADE );

INSERT INTO orders (customer_id) WITH RECURSIVE data_rows (n) AS ( SELECT 1 UNION ALL SELECT n + 1 FROM data_rows WHERE n < 1000 ) SELECT FLOOR(RANDOM() * 25) + 1 FROM data_rows;

-- Create index on orders table

CREATE INDEX idx_orders_customer_id ON orders (customer_id);".

Тестовый запрос-1

"SELECT c.customer_id, COUNT(o.order_id) AS orders_count INTO test_rec FROM customers c LEFT JOIN orders o ON c.customer_id = o.customer_id GROUP BY c.customer_id;" .

Тестовый запрос-2

"SELECT c.customer_id, (SELECT COUNT(o.order_id) FROM orders o WHERE o.customer_id = c.customer_id) AS orders_count INTO test_rec FROM customers c;" . Тестовая СУБД :

CPU = 8 , RAM = 8GB , Версия PostgreSQL 17.

Нагрузка растет экспоненциально от 10 до 45 одновременных сессий, длительность тестовой итерации 10 минут.

Вопрос - для какого тестового запроса производительность СУБД будет выше в ходе нагрузочного тестирования - при использовании тестового запроса-1 или при использовании тестового запроса-2 ?

1️⃣Ответ нейросети "Ask Postgres"

✅ Производительность будет выше при использовании тестового запроса-1 (LEFT JOIN + GROUP BY)

Обоснование

1. Запрос-1 (JOIN + GROUP BY) — оптимальный

  • Один проход по таблице orders с использованием индекса idx_orders_customer_id.

  • PostgreSQL 17 эффективно использует параллельные планы для JOIN и GROUP BY — при 45 сессиях нагрузка распределяется между ядрами CPU.

  • Минимальная конкуренция за ресурсы: индекс сканируется один раз на запрос, а не 25 раз.

  • Оптимизатор может применить Hash Join или Merge Join, что особенно эффективно при наличии индекса и небольшом количестве клиентов (25).

  • Масштабируемость: при росте числа сессий производительность падает умеренно — линейно с числом запросов, а не с числом строк в orders.

Документация подтверждает:

«Subqueries can be transformed into semi-joins» — но только для некоррелир

ованных.

Коррелированные подзапросы (как в запросе-2) не преобразуются в JOIN автом

атически, особенно при использовании COUNT() — это ограничение оптимизатора.

2. Запрос-2 (коррелированный подзапрос) — неэффективный при нагрузке

  • Для каждой из 25 строк customers выполняется отдельный подзапрос.

  • При 45 сессиях1125 отдельных сканов индекса idx_orders_customer_id в секунду.

  • Это вызывает: Конкуренцию за блоки индекса → локальные блокировки.

  • Высокую нагрузку на CPU → много контекстных переключений.

  • Ограничение параллелизма — подзапросы выполняются последовательно внутри одного запроса.

    📌 Рекомендация

    Для нагрузочных тестов с растущей параллельностью всегда используйте JOIN-версию (запрос-1).

    Коррелированные подзапросы с агрегациями — плохая практика в сценариях с высокой конкуренцией, даже если они выглядят «проще».

    Дополнительно: Для ещё большей производительности можно добавить CLUSTER на orders по customer_id, или использовать materialized view для кэширования агрегированных данных, если данные не меняются часто.

    Итог: Запрос-1 (LEFT JOIN + GROUP BY) обеспечивает значительно более высокую производительность при нагрузке 10–45 сессий.

    2️⃣Ответ нейросети "DeepSeek"

    Для данного сценария нагрузочного тестирования производительность будет выше при использовании тестового запроса-1 (с LEFT JOIN и GROUP BY). Обоснование:

    1. Оптимизация выполнения запроса:

    2. Запрос-1 выполняется как одиночный оператор с хеш-соединением и агрегацией, что эффективно обрабатывается планировщиком PostgreSQL.

    3. Запрос-2 использует коррелированный подзапрос, который выполняет 25 отдельных запросов COUNT (по одному на каждого клиента). При параллельных сессиях это создает дополнительную нагрузку.

    4. Эффективность при параллелизме:

    5. Запрос-1 лучше масштабируется благодаря хеш-агрегации и однократному сканированию таблиц.

    6. Запрос-2 генерирует множество индексных сканиваний (используя idx_orders_customer_id), что при 45 сессиях приводит к конкуренции за ресурсы ( latch-конфликты в кэше индексов).

    7. Статистика ресурсов:

    8. При 1000 заказах и 25 клиентах разница незначительна, но при экспоненциальном росте сессий запрос-2 сильнее нагружает CPU (многократные поиски в индексе) и RAM (больше служебных структур на сессию).

    9. Особенности PostgreSQL:

    10. Оптимизатор PostgreSQL эффективнее обрабатывает JOIN-агрегацию, чем вложенные подзапросы, при параллельных выполнениях. Версия 17 дополнительно улучшает параллельные хеш-соединения.

    Рекомендация: Для рабочих нагрузок с высоким параллелизмом предпочтительнее использовать запрос-1.

    ⚠️Результаты экспериментального нагрузочного тестирования

    Абсолютные значения операционной скорости и ожиданий

Операционная скорость

💥Среднее повышение операционной скорости при использовании коррелированного подзапроса 288%

⚠️Вывод - использовать нейросети для анализа и оптимизации производительности СУБД PostgreSQL под нагрузкой - нельзя.⚠️

Для данной виртуальной машины , данной версии СУБД , в ходе данного плана нагрузочного тестирования:

  1. Производительность СУБД при использовании коррелированного подзапроса кардинально выше, чем при использовании JOIN.💥

Показать полностью 3
Отличная работа, все прочитано!