Learning Platform
Урок 07.03 · 22 мин
Продвинутый
PlannerGEQOJoin orderingOptimizer

В прошлом уроке мы видели: planner перебирает physical planы для каждой equivalence class и выбирает дешёвый. Но в формуле «перебирает» прячется боль. Сколько именно physical planов он рассматривает? Для 4 таблиц это десятки. Для 10 — миллиарды. Если бы planner честно искал всё, простой SELECT мог бы планироваться часами.

Postgres использует два разных алгоритма: dynamic programming для маленьких join-сетов и GEQO (Genetic Query Optimizer) для больших.

Комбинаторика join order

Для N таблиц число различных

join order
деревьев растёт как N! (для left-deep) или хуже (для bushy). Цифры:

Взрыв комбинаторики

Standard exhaustive search планируется через DP за O(2^N × N). На 12 таблицах уже 50 000 состояний, на 16 — миллион, на 20 — астрономия.

N = 4~24 порядка / DP 2^4 = 16
N = 8~40 320 порядков / DP 2^8 = 256
N = 12479M порядков / DP 4096
N = 1620 трлн порядков / DP 65K
Standard DP-search Postgresприемлем до ~10 таблиц, дальше — секунды на planning

Стандартный поиск Postgres — System R-style dynamic programming (не полный перебор, а DP, который запоминает оптимум для каждого подмножества таблиц). Сложность ≈ O(2^N × N). На 8 таблицах — мгновенно. На 12 — заметно. На 16+ planning может занимать сравнимо или больше, чем сам запрос.

И здесь включается GEQO.

Что такое GEQO

GEQO = Genetic Query Optimizer. Идея простая: вместо честного перебора устраиваем эволюционный поиск. Каждый «индивид» — это порядок join’ов (например, (((A ⋈ C) ⋈ B) ⋈ D)). Считаем его cost. Затем:

  1. Создаём начальную популяцию случайных порядков.
  2. Скрещиваем (crossover) — берём кусок порядка от одного, кусок от другого.
  3. Мутируем — случайно меняем местами пары.
  4. Отбираем дешёвых — они дают следующее поколение.
  5. Через несколько поколений лучший индивид — наш план.
GEQO в одном цикле

Поколения сменяются, средний cost популяции падает. Лучший индивид к концу — итоговый порядок JOIN'ов.

поколение 0случайные порядки cost ~12000
поколение 5cost ~9000
поколение 20cost ~7500
finalplanbest individual
operationsselection (родители) → crossover (потомки) → mutation → fitness eval

GEQO не гарантирует оптимальный план — только достаточно хороший за фиксированное время. Это компромисс: planning ~50 ms против ~50 секунд DP, ценой плана, который, возможно, на 10-20% хуже оптимального.

Когда включается

Параметр geqo_threshold (по умолчанию 12) — если в запросе количество

FROM items
≥ 12, planner автоматически переключается на GEQO. Параметр geqo = on/off глобально включает/выключает.

Проверим:

Текущие настройки GEQO. По умолчанию: geqo = on, threshold = 12.

PostgreSQL

Снижаем threshold до 4, чтобы GEQO сработал даже на простом запросе с 4 join'ами. В реальной БД с большим количеством таблиц это покажет нестабильность плана между запусками.

PostgreSQL

Если бы pglite поддерживал большие планы и реальные временные замеры, ты бы увидел, как при разных geqo_seed план меняется — это и есть стохастичность генетического поиска.

Главная проблема: нестабильность плана

Поскольку GEQO — это вероятностный алгоритм, два запуска одного и того же запроса могут дать разные планы. И, как следствие, разное время выполнения.

В продакшене это часто неприемлемо: SLA требует предсказуемости. Один день запрос летает за 50ms, другой — за 5 секунд, потому что GEQO в этот раз нашёл плохой порядок.

Лечение:

  • Зафиксировать seed: SET geqo_seed = 0.5; — даёт детерминированный поиск, но всё ещё не оптимальный.
  • Поднять geqo_threshold: если у тебя обычно 8-10 таблиц, поставь threshold = 15 — будет работать DP, медленнее planning, но стабильно.
  • Совсем отключить: SET geqo = off — все запросы пойдут через DP. Опасно: на действительно больших запросах planning займёт минуты.
  • Увеличить количество поколений/популяцию: geqo_generations, geqo_pool_size — больше шансов на оптимум, но медленнее.

Не путать с join_collapse_limit

Часто параметры GEQO путают с другим: join_collapse_limit и from_collapse_limit (оба по умолчанию 8). Они контролируют не алгоритм поиска, а уплощение JOIN-дерева. Если у тебя 15 JOIN’ов, но join_collapse_limit = 8, planner возьмёт первые 8 и оптимизирует их вместе, а остальные — в порядке написания. То есть «защита» от долгого планирования через ограничение пространства, а не через GEQO.

Иногда выгоднее поднять join_collapse_limit (например, до 12-15) и выключить GEQO — тогда DP честно ищет оптимум на чуть большем пространстве, но без рандома. Это типичная настройка под аналитические workloads.

Параметры коллапса — еще одна крутилка для больших JOIN-планов.

PostgreSQL

Когда отключают GEQO

В большинстве OLTP-нагрузок GEQO не срабатывает: запросы имеют 1-5 таблиц, до threshold далеко. Он становится темой при:

  • Аналитика / OLAP: сложные звёздные/снежинковые схемы с 10+ dim-таблицами. Здесь нестабильность времени запроса заметна и часто отключают geqo = off.
  • ORM-сгенерированный код: некоторые ORM (особенно для multi-tenant с большим количеством join’ов на permission-фильтры) производят запросы с 15+ join’ами случайно. Тут GEQO — палка о двух концах.
  • Reporting: где время planning не критично, а вот стабильное время выполнения важно.

Эмпирическое правило: если у тебя есть запросы с 12+ JOIN’ов в проде, проверь, нужен ли тебе GEQO — либо подкрути threshold/seed, либо отключи.

Проверка знанийKnowledge check
У тебя OLAP-запрос с 14 JOIN'ами выполняется стабильно ~3 секунды, planning занимает 50 ms. Однажды время выросло до 12 секунд, но при повторе вернулось к 3 секундам. Какова вероятная причина и что сделать?
ОтветAnswer
Скорее всего GEQO «не повезло» с одним из вариантов плана: для 14 таблиц planner идёт через GEQO (default threshold=12), а каждый раз стартует с случайной популяции (если geqo_seed не фиксирован). Один из запусков выбрал плохой порядок join'ов и привёл к 4x slowdown. Решение: либо SET geqo_seed = <фиксированное> для стабильности, либо SET geqo = off + SET join_collapse_limit = 16, чтобы DP честно искал оптимум — planning подорожает, но время станет предсказуемым. Если запрос параметризованный, можно завернуть в prepared statement, чтобы не платить за GEQO повторно.

Чек-лист

  • DP-search (System R) экспоненциален: на ~12+ таблицах становится слишком дорогим.
  • GEQO = эволюционный поиск порядка JOIN’ов: популяция, crossover, mutation, fitness = cost.
  • Включается автоматически при геqo_threshold = 12 и более FROM items.
  • Главный недостаток — нестабильность: один и тот же запрос → разные планы между запусками.
  • Лечение: фиксированный geqo_seed, поднять threshold или совсем geqo = off (с большим join_collapse_limit).
  • Не путать с join_collapse_limit / from_collapse_limit — это про границу оптимизации, а не про алгоритм.
  • OLTP редко затрагивается; OLAP/reporting/ORM-генерация — типичные кандидаты на ручную настройку.
Интуиция производительности: nested loop, hash join, merge join

Проверьте понимание

Результат: 0 из 0
Концептуальный
Вопрос 1 из 4. Что такое GEQO в PostgreSQL и зачем он нужен?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 5