PostgreSQL 18 вот-вот выйдет, и это не просто минорное обновление, а настоящий прорыв для разработчиков и администраторов БД. В новом переводе от команды Spring АйО рассмотрим ключевые новинки — асинхронный I/O для ускорения чтения, поддержка UUID версии 7 с улучшенной сортировкой, skip scans в B-tree индексах, виртуальные вычисляемые столбцы и даже OAUTH 2.0 для аутентификации. Всё это делает Postgres ещё более быстрым, гибким и современным.
Асинхронный ввод-вывод (I/O)
Postgres 18 добавляет поддержку асинхронного ввода-вывода. Это означает более быстрые операции чтения во многих сценариях использования. Это также часть более масштабной программы повышения производительности, запланированной для будущих версий Postgres, включая потенциальную поддержку многопоточности.
Что такое асинхронный I/O?
Когда данные отсутствуют в общих буферах памяти, Postgres считывает их с диска, и для этого требуется операция ввода‑вывода. При синхронном I/O каждая отдельная операция чтения с диска является блокирующей — backend процесс, который ответственен за выполнение запроса вынужден ждать результата I/O. В высоконагруженных базах данных с высокой активностью это может стать и становится узким местом.
Postgres 18 внедряет асинхронный I/O, позволяя воркерам эффективнее использовать время простоя и повышать общую пропускную способность системы за счёт батчёвой обработки чтений. В текущих версиях Postgres во многом надеется на благоразумие операционной системы для управления вводом-выводом — например, read-ahead чтение при seq-scan-ах или использование posix_fadvise в Linux для таких операций, как Bitmap Index Scan.
Комментарий от эксперта Spring АйО, Михаила Поливахи
Не многие знают, но ОС дает возможность процессам определенным образом давать себе hint-ы/подскази о том, как конкретно файл планируется исопльзовать в будущем. Например, если Postgres планирует читать файл последовательно, он может вызывать syscall
posix_fadvise
для того, чтобы сказать об этом ОС.
ОС может, в свою очередь, это информацией воспользоваться для того, чтобы, например, при последующем чтении по файловому дискриптору просто вычитать дополнительные последовательные N страниц с диска, чтобы потом не пришлось бегать на диск снова.
Несмотря на то, что этот механизм вполне себе неплох, ядро ОС ничего особо Вам не гарантирует, и надеятся на него можно, но это не самая идеальная стратегия. Хуже Вы вряд ли сделаете :)
Перенос этой логики в саму СУБД с помощью асинхронного I/O позволит добиться большей предсказуемости и лучшей производительности при батчевой обработке на уровне базы данных. Также появится новая системная view - pg_aios
, в которой можно будет получать данные об определенной статистике использования асинхронного ввода-вывода.
Операции записи останутся синхронными — это необходимо для соблюдения принципов ACID!
Асинхронный I/O повлияет на:
последовательные сканирования (sequential scans)
bitmap heap scans (выполняющиеся вслед за bitmap index scans)
некоторые операции обслуживания, такие как VACUUM
По умолчанию в Postgres будет включён метод io_method = worker
. Также по умолчанию будет использоваться 3 воркер процесса, и это количество можно увеличить на системах с большим количеством CPU.
Для Postgres, работающего на Linux 5.1+, можно использовать системные вызовы io_uring
и выполнять их непосредственно через backend-процессы, вместо отдельных воркеров, с помощью опции io_method = io_uring
.
Комментарий от эксперта Spring АйО, Михаила Поливахи
На деле, скорее всего, у большинства людей в продакшене на серверах стоит Linux 6+, поэтому для реализации асинхронного I/O лучше всего использовать io_uring
, а не традицонный подход с очередью воркеров (io_method = worker
), которые как раз выполняют асинхронные операции чтения.
Причина в том, что в случае io_uring
за счет двух смапленых (через mmap) циркулярных буферов, в которых ядро ОС и приложение обмениваются данными, сильно минимизируется кол-во syscall-ов и соответственно свичей контекста и т.п.
Поэтому в идеале, конечно, работать именно с io_uring
, если у Вас есть такая возможность.
UUID версии 7
UUID в этой версии также претерпевают изменения: теперь будет использоваться версия 7. Ссылка на нашу статью про UUIDv7 тут.
UUID — это случайно сгенерированные строки, которые являются глобально уникальными и часто применяются в качестве первичных ключей. UUID
популярны в современных приложениях по нескольким причинам:
Уникальность: ключи могут генерироваться в разных частях системы без риска коллизий
Независимость: приложение может создать ключ до отправки данных в базу
Обфускация URL: если в URL используются числовые идентификаторы (например, /users/5), остальные легко угадать (/users/6, /users/7). С
UUID
(/users/f47ac10b-58cc-4372-a567-0e02b2c3d479) это невозможно.
Новый стандарт UUID v7 появился в середине 2024 года в результате серии обновлений стандартов. Ранее в Postgres поддерживался UUID v4. Однако при работе с большими таблицами у v4 возникали проблемы с сортировкой и индексированием из-за высокой случайности значений, что приводило к фрагментации индексов и плохой локальности данных.
UUID v7 решает эти проблемы. Он остаётся случайным, но первые 48 бит (12 символов) представляют собой временную метку (по сути unix timestamp),
Комментарий от эксперта Spring АйО, Ильи Сазонова
Потом 4 бита отведены на номер версии, а ещё 12 бит отводятся на часть временной метки, которая отвечает за микросекунды и наносекунды (точность до 250 наносекунд)
Остальные биты — случайные. Это повышает locallity данных, вставленных примерно в одно и то же время, и, соответственно, снимает часть нагрузки с индексов.
Временная метка — это шестнадцатеричное значение (т.е. число в системе счисления с основанием 16). Например, UUID
, начинающийся с 01896d6e4a5d6 (hex), представляет число 2707238289622 (decimal) — количество миллисекунд с 1970 года.

Комментарий от эксперта Spring АйО, Ильи Сазонова
На иллюстрации выше не отображена 12-битная часть таймстемпа. В PostgreSQL временная метка состоит из 60 бит: 48 бит в начале UUID и ещё 12 бит сразу после поля версии. Эта часть отвечает за микросекундную/наносекундную точность (до 250 нс).
Вот как будет выглядеть DDL для UUID
версии 7:
CREATE TABLE user_actions (
action_id UUID PRIMARY KEY DEFAULT uuidv7(),
user_id BIGINT NOT NULL,
action_description TEXT,
action_time TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_action_id ON user_actions (action_id);
B-tree skip scans
В Postgres 18 ожидается серьёзный прирост производительности при использовании некоторых многоколоночных B-tree индексов.
В Postgres, если у вас есть индекс по столбцам (status
, date
), планировщик может решить использовать этот индекс для запросов, которые фильтруют сразу по полям status
и date
, либо только по status
.
В версиях Postgres 17 и ниже этот же индекс не может быть использован для запросов, фильтрующих только по полю date. В таком случае необходимо было либо создать отдельный индекс по date, либо СУБД выполняла последовательное сканирование таблицы с последующей фильтрацией.
В Postgres 18 во многих случаях можно будет автоматически использовать такой многоколоночный индекс даже в запросах, касающихся только поля date
. Эта оптимизация называется skip scan, и она позволяет "перепрыгивать" через части индекса.
Оптимизация работает в случаях, когда запросы не используют ведущие колонки в условиях, а пропущенные колонки имеют низкую кардинальность (то есть ограниченное число уникальных значений). Принцип работы следующий:
Определяются все уникальные значения в пропущенных (ведущих) колонках;
Запрос фактически преобразуется с добавлением условий для подстановки этих значений;
Итоговый запрос использует существующую инфраструктуру индексов для оптимизации поиска по нескольким ведущим колонкам, пропуская страницы индекса, которые не соответствуют условиям.
Например, если у нас есть таблица sales
со столбцами status
и date
, и многоколоночный индекс:
CREATE INDEX idx_status_date
ON sales (status, date);
Пример запроса может содержать условие WHERE
, в котором не используется поле status
.
SELECT * FROM sales
WHERE date = '2025-01-01';
В плане выполнения запроса никак не указано, что используется skip scan, поэтому вы увидите обычное индексное сканирование (Index Scan), в котором отображаются условия по индексу.
QUERY PLAN
-------------------------------------------------------------
Index Only Scan using idx_status_date on sales (cost=0.29..21.54 rows=4 width=8)
Index Cond: (date = '2025-01-01'::date)
(2 rows)
До версии 18 выполнялось бы полное сканирование таблицы, поскольку ведущий столбец индекса не используется в запросе. Однако с внедрением skip scan в Postgres 18 можно использовать тот же многоколоночный индекс для индексного сканирования.
В Postgres 18, благодаря тому что поле status обладает низкой кардинальностью и содержит лишь несколько уникальных значений, возможно выполнение составного индексного сканирования. Обратите внимание: эта оптимизация работает только для запросов, использующих оператор =, и не применяется к неравенствам или диапазонам.
Всё это происходит автоматически на уровне планировщика запросов Postgres — включать ничего не нужно. Идея заключается в том, чтобы улучшить производительность аналитических сценариев, в которых условия фильтрации часто меняются и не всегда соответствуют существующим индексам.
Планировщик будет сам принимать решение, целесообразно ли использовать skip scan, основываясь на статистике таблицы и количестве уникальных значений в пропущенных колонках.

Виртуальные вычисляемые столбцы
PostgreSQL 18 представляет виртуальные вычисляемые столбцы. Ранее вычисляемые столбцы всегда сохранялись на диск. Это означало, что их значения вычислялись во время операций вставки или обновления, что добавляло определённую нагрузку на запись.
В PostgreSQL 18 виртуальные вычисляемые столбцы теперь становятся типом по умолчанию для всех вычисляемых столбцов. Если вы определяете вычисляемый столбец без явного указания STORED
, он будет создан как виртуальный.
CREATE TABLE user_profiles (
user_id SERIAL PRIMARY KEY,
settings JSONB,
username VARCHAR(100) GENERATED ALWAYS AS (settings ->> 'username') VIRTUAL
);
Это отличное обновление для тех, кто работает с JSON-данными: запросы становятся проще, а изменения или нормализация данных теперь могут выполняться «на лету» при необходимости.
Обратите внимание: виртуальные вычисляемые столбцы не подлежат индексированию, поскольку они не сохраняются на диск. Для индексации данных JSONB
следует использовать либо вариант с STORED
, либо индекс по выражению.
Поддержка OAUTH 2.0
Хорошие новости для тех, кто использует Okta, Keycloak и другие сервисы управления аутентификацией: теперь Postgres совместим с OAUTH 2.0. Настройка производится в основном конфигурационном файле аутентификации pg_hba.conf.
Система OAUTH использует специальные токены доступа, где клиентское приложение предъявляет токен вместо пароля для подтверждения личности. Токен является “непрозрачным” (по англ. - opaque), формат которого определяется сервером авторизации (authorization server в рамках OAuth2). Это нововведение устраняет необходимость хранения паролей в базе данных, а также позволяет реализовать более надёжные меры безопасности — такие как многофакторная аутентификация (MFA) и единый вход (SSO), управляемые внешними поставщиками идентификации.
Выпуск Postgres 18 включает и множество других улучшений
Postgres 18 включает впечатляющие 3000 коммитов от более чем 200 авторов. Помимо новых функций, в систему внесено множество внутренних улучшений и оптимизаций — в первую очередь в планировщик запросов и другие ключевые компоненты. Даже если вы не планируете использовать новые опциональные возможности, вы всё равно получите преимущества: повышение производительности (например, за счёт асинхронного ввода-вывода), исправления ошибок и обновления безопасности. Регулярные обновления — разумная стратегия.

Присоединяйтесь к русскоязычному сообществу разработчиков на Spring Boot в телеграм — Spring АйО, чтобы быть в курсе последних новостей из мира разработки на Spring Boot и всего, что с ним связано