Каждый новый релиз PostgreSQL — это событие, но 18-я версия выглядит особенно интересно, предлагая фундаментальные улучшения в производительности, значительные удобства для разработчиков и долгожданные доработки в обслуживании и мониторинге. 

Асинхронный I/O и новый «стриминговый» ввод‑вывод

Раньше, когда PostgreSQL нужно было прочитать много данных с диска (например, при полном сканировании таблицы), он делал это последовательно: запросил страницу, дождался, получил, запросил следующую. AIO меняет правила игры, позволяя отправлять сразу несколько запросов на чтение, не дожидаясь ответа на каждый из них.На практике это ускоряет:

  • последовательные сканирования;

  • bitmap heap scan;

  • VACUUM;

  • сбор статистики.

Включается через io_method:

  • worker — работает везде, по умолчанию;

  • io_uring — если собирали с libio_uring;

  • sync — старое синхронное поведение.

Еще в 17-й версии для «стриминга» чтения добавили тонкую настройку: io_combine_limit и новый верхний порог io_max_combine_limit (по умолчанию 128 KiB, можно поднять до 1 MiB). Это помогает крупным сканам собирать данные большими порциями.

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

Меньше боли при апгрейдах

Кто делал major‑апгрейды, знает, что пока ANALYZE отработает, сложные запросы «проседают». В 18‑й версии pg_upgrade переносит базовые статистики планировщика — кластер быстрее выходит на ожидаемую производительность. Если не нужно, есть флаг –no-statistics, но честно: большинству лучше оставить перенос включённым.

Сами pg_dump/pg_dumpall умеют теперь выгружать/загружать статистику (базовую, не extended). А vacuumdb получил «умное дозаполнение»: analyze –in-stages вместе с новым missing stats only — соберёт только то, чего нет.

skip scan и больше шансов ускориться без перекройки схемы

Новая оптимизация для многоколоночных B‑tree: skip scan. Если в WHERE нет равенства (=) по ведущей колонке индекса, но есть условия по последующим: PostgreSQL может всё равно применить индекс. В 18‑й версии это реально «включается» в планах и часто превращает потенциальный seq scan в index scan.

Как это работает в общих чертах: планировщик «разворачивает» поиск по комбинациям допустимых значений ведущих столбцов и многократно проходит индекс по второй (или дальше) колонке. Да, это несколько проходов вместо одного, но всё равно лучше, чем читать всю таблицу. 

Проверить легко: EXPLAIN ANALYZE теперь показывает «Index searches: N» — увидите, сколько раз индекс искали.

Кстати, GIN теперь можно строить параллельно (как и B‑tree/BRIN ранее). Если у вас большие наборы JSONB/полнотекст — время сборки индекса уменьшится.

EXPLAIN стал полезнее «из коробки»:

  • BUFFERS теперь автоматически включён в EXPLAIN ANALYZE: увидите, сколько блоков реально трогали, без допонлительных флагов.

  • Поиндексные счётчики: в узле Index Scan появилась строка с количеством обращений к индексу (Index searches), что помогает отличать «один длинный проход» от «сотни мелких».

  • Появилась дробная часть у actual rows: планировщик всегда считал её, теперь вы видите реальные доли на итерацию. Особенно полезно при больших loops, где «0» на самом деле «0.49».

  • Узлы, использующие временное хранилище (Materialize, CTE Scan, Window Agg и тому подобные), теперь показывают Storage: диск или память, плюс объём. Управлять work_mem стало чуть менее «вслепую».

  • EXPLAIN ANALYZE VERBOSE дополнительно умеет показывать WAL, CPU и средние чтения — метрики стали богаче. А если диагностики мало, расширение pg_overexplain добавляет свои опции к EXPLAIN.

Детальнее и по‑процессно мониторинг I/O и WAL

 pg_stat_io переписан с точки зрения байтов:

  • вместо фиксированного op_bytes (равного размеру блока) появились read_bytes, write_bytes, extend_bytes;

  • добавлены строки об активности WAL (в контекстах Init/Normal — инициализация нового сегмента и обычная запись).

Хотите видеть, кто конкретно «шумит» по I/O?

  • pg_stat_get_backend_io(pid) — пер‑бэкендовая статистика ввода‑вывода в структуре как у pg_stat_io;

  • pg_stat_get_backend_wal(pid) — то же для WAL. Скомбинируйте с pg_stat_activity — получите топ процессов по чтению/записи, а нужный момент обнуляйте через pg_stat_reset_backend_stats().

NUMA‑сборки теперь наблюдаемы: pg_numa_available() подскажет, включён ли режим, а pg_shmem_allocations_numa и pg_buffercache_numa покажут распределение памяти и буферов по узлам — полезно для тюнинга на больших машинах.

Логическая репликация: конфликты видны и считаются

Раньше «тихие» конфликты могли проходить мимо глаз. В 18‑й есть классификация конфликтов и счётчики pg_stat_subscription_stats по каждому типу; подробности падают в лог. Плюс дефолтный режим CREATE SUBSCRIPTION — parallel streaming, чтобы быстрее применять транзакции.

Улучшения VACUUM: меньше блокировок, больше контроля, «нетерпеливая заморозка»

Сразу несколько практичных вещей:

  • vacuum_truncate — глобальный переключатель усечения «пустого хвоста» таблицы. Раньше можно было отключать truncate только ручным VACUUM или per‑relation настройками; теперь централизованно. Удобно на горячих таблицах очередей, где эксклюзивные блокировки дорого обходятся.

  • Автовакуум можно масштабировать «на лету»: autovacuum_max_workers имеет контекст SIGHUP, а новый autovacuum_worker_slots задаёт верхнюю планку, до которой можно поднимать без рестарта.

  • Триггер автовакуумов для огромных таблиц стал разумнее: появился autovacuum_vacuum_max_threshold (по умолчанию 100 млн «мёртвых» строк). Если стандартная формула 20% + threshold уводит запуск в «когда‑нибудь», max_threshold заставит приходить раньше — без ручной настройки конкретной таблицы.

  • Для таблиц «только вставка» (инсерты без апдейтов/удалений) частота автовакуумов теперь выше: insert_scale_factor умножается только на незамороженные строки, а не на весь объём. В pg_class добавили relallfrozen — помогает быстро оценить, что уже заморожено.

Самое интересное — eager freezing: обычный VACUUM может выборочно заходить на страницы, помеченные как all visible, чтобы заранее их заморозить и разгрузить будущий «агрессивный» проход. Есть два ограничителя:

  • vacuum_max_eager_freeze_failure_rate (по умолчанию ~3%): если попытки заморозки часто «не заходят» (страницы меняются), прекращаем.

  • порог успешных заморозок (примерно 20% от объёма): достигли — дальше оставляем работу агрессивному режиму.

И да, появилась «телеметрия» пауз: включите track_cost_delay_timing и вы увидите delay_time в pg_stat_progress_vacuum/pg_stat_progress_analyze, VACUUM VERBOSE и в логе. Это помогает настроить троттлинг автоочистки так, чтобы не душить продуктивную нагрузку.

OAuth 2.0 и курс на отказ от MD5

PostgreSQL теперь умеет работать с OAuth 2.0: метод oauth в pg_hba.conf, параметры в libpq, и обязательная загрузка валидатора через oauth_validator_libraries (готовых библиотек в дистрибутиве нет — интерфейс описан в доках, можно взять сторонние или написать свой). Практический профит — SSO, единая точка управления доступом, и база перестаёт хранить пароли. Параллельно MD5 объявлен deprecated: если нужен пароль, используйте SCRAM. Есть и SCRAM passthrough для postgres_fdw/dblink — чтобы не складировать креды в БД.

UUIDv7(): дружелюбный к индексам случайность со временем

gen_random_uuid() (v4) всегда был случайным, что хорошо для уникальности, плохо для индекса: пачка вставок превращается в прыжки по B‑дереву и лишние чтения/записи. Новый uuidv7() выдаёт случайный UUID, упорядоченный по времени (timestamp‑ordered): вставки идут «в хвост» индекса, страницы меняются последовательнее — выигрываете на кэше и WAL. Для удобства есть alias uuidv4() на старую генерацию.

Кстати, если используете виртуальные столбцы (см. ниже), удобный паттерн — хранить первичный ключ как UUIDv7(), а «время» извлекать из него при чтении.

Виртуальные вычисляемые столбцы

Generated columns теперь бывают «virtual»: значение считается на чтении, а не хранится на диске. Это дефолт для generated в 18‑й. Если нужно хранить, укажите STORED. Ограничение: индекс напрямую на виртуальный столбец не сделать, но индекс по выражению — пожалуйста.

RETURNING теперь «видит» OLD и NEW

INSERT/UPDATE/DELETE/MERGE получили явную поддержку old/new в RETURNING. Для UPDATE это полезно: можно логировать, что менялось «в одну команду», без триггеров. Для MERGE — тоже самое: специфичные ветки логируются явным образом.

Темпоральные ограничения: WITHOUT OVERLAPS и PERIOD

Можно объявлять «диапазонные» PRIMARY KEY и UNIQUE через WITHOUT OVERLAPS — последний столбец ключа задаёт интервал, а движок следит, чтобы для одной сущности интервалы не пересекались. Ссылочные связи — через PERIOD в FOREIGN KEY. Реализация базируется на механизме exclusion constraints и требует расширения btree_gist, это честная «интеграция стандарта с реальностью», а не «магия».

Текст и коллации: быстрее регистрозависимые операции

Добавили PG_UNICODE_FAST — коллацию с полными Unicode‑семантиками для преобразований регистра (upper/lower/casefold) и ускоренными сравнениями; появилась функция casefold() для аккуратных case‑insensitive сравнений. LIKE теперь умеет работать и с недетерминированными коллациями, а полнотекст и pg_trgm перешли на «коллацию по умолчанию кластера» (не всегда libc) — после апгрейда может понадобиться переиндексация соответствующих индексов.

Прочее важное

  • Page checksums включены по умолчанию в initdb. Если апгрейдите без чек‑сумм, используйте –no-data-checksums в pg_upgrade, иначе не состыкуется.

  • Проволока (wire protocol) обновлена до версии 3.2 — впервые с 2003 года. libpq по умолчанию всё ещё 3.0, клиенты постепенно добавят поддержку.

  • psql освоил «конвейер» (pipeline) поверх расширенного протокола: можно отправить пачку команд без ожидания ответов на каждую и забрать результаты одним махом. На линиях с сетевой латентностью экономия реальна. Новые команды \startpipeline, \sendpipeline, \flush, \getresults, \endpipeline.

  • В аппаратных оптимизациях: ARM NEON/SVE для popcount (bit_count и пр.), SIMD‑ускорение длинных JSON‑строк, AVX‑512 для CRC32C — не «всем всё», но приятно для тех, у кого железо позволяет.

Что оставить на чек‑листе перед обновлением

  • Оцените, нужен ли вам io_uring (соберите с libio_uring) и подстройте io_combine_limit/io_max_combine_limit.

  • Примите решение по переносимости статистики (скорее «да») и добавьте vacuumdb analyze –in-stages –missing-stats-only в пост‑апгрейдные шаги.

  • Проверьте коллации: если у кластера default provider не libc — переиндексируйте FTS/pg_trgm после pg_upgrade.

  • Включите page checksums как есть или заранее спланируйте –no-data-checksums.

  • Если у вас «горячие» очереди — рассмотрите выключение vacuum_truncate глобально.

  • Запланируйте переход от MD5 к SCRAM, а для SSO — подготовьте валидатор OAuth.

  • Для больших JSONB/FTS — пересоберите GIN параллельно.

PostgreSQL 18 — это не просто набор новых функций, а продуманный шаг вперёд. Внедрение асинхронного ввода-вывода закладывает фундамент для будущих рекордов производительности. Улучшения для разработчиков, такие как uuidv7() и виртуальные столбцы, делают код чище и эффективнее. А доработки в VACUUM и pg_upgrade решают давние проблемы администраторов, делая обслуживание больших систем проще и предсказуемее.

Разумеется, это лишь верхушка айсберга. Полный список изменений, как всегда, доступен в официальных Release Notes. Но уже сейчас ясно: PostgreSQL продолжает развиваться, становясь ещё мощнее, быстрее и удобнее.

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


  1. avbochagov
    01.10.2025 15:31

    вот только io_uring не работает из Docker контейнера...


    1. Sleuthhound
      01.10.2025 15:31

      Это пол беды, он дырявый как решето. Наверно каждые полгода, а то и чаще в io_uring находят 0-day

      Что-то я не очень хочу сажать базу на решето.


  1. Akina
    01.10.2025 15:31

    Generated columns теперь бывают «virtual»: значение считается на чтении, а не хранится на диске. Это дефолт для generated в 18‑й.

    Если вычисляемое поле таблицы используется в запросе несколько раз - его значение считается однократно или каждый раз? А для двух копий таблицы?