На прошлой неделе мне предоставилась возможность прочитать научную статью 1986 года о создании Postgres как платформы и о исходных целях её проектирования. Меня поразила степень предусмотрительности её авторов и то, как цели проекта заложили фундамент создания системы управления базами данных, постепенно завоёвывающей мир.

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

В статье перечислены следующие шесть целей проекта:

  1. улучшенная поддержка сложных объектов, расширяющих сценарии использования в бизнесе и разработке

  2. обеспечение возможности расширения типов данных, операторов и способов доступа для пользователей

  3. предоставление средств для активных баз данных (например, уведомлений и триггеров)

  4. упрощение процесса восстановления после сбоев

  5. использование преимуществ нового оборудования

  6. применение реляционной модели Кодда

Давайте рассмотрим их с точки зрения современных возможностей Postgres.

1) Объекты и типы данных для растущего мира бизнеса и разработки

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

Числовые типы наподобие SMALLINT и INTEGER применяются для целых чисел, а BIGINT можно использовать для уникальных ID пользователей или первичных ключей. Точные типы наподобие NUMERIC и DECIMAL применяются в критичных случаях, например, для хранения денежных значений. Типы с плавающей запятой наподобие REAL или DOUBLE PRECISION можно применять для научных или инженерных вычислений, при которых абсолютная точность не так важна, как диапазон значений. Также существуют UUID (индексируемые UUID в Postgres 18) для распределённых систем и безопасных URL.

Символьные типы наподобие VARCHAR(n) или CHAR(n) могут хранить текст переменной длины до указанной максимальной длины (n), занимая при этом только то пространство, которое нужно для хранения самого текста.

Типы даты/времени наподобие DATE хранят только дату (год, месяц, день). TIMESTAMPTZ идеально подходят для хранения времени и даты и легко интегрируются в глобальные системы.

Но и это ещё не всё: Postgres обладает способностью лёгкого создания произвольных типов данных и ограничения значений данных конкретным сценарием использования.

При помощи CREATE DOMAIN можно создать проверку конкретного значения, например, для подтверждения диапазона дат дней рождения или валидности формата ящика электронной почты.

-- Postgres create domain
CREATE DOMAIN date_of_birth AS date
CHECK (value > '1930-01-01'::date);

CREATE DOMAIN valid_email AS text
NOT NULL
CHECK (value ~* '^[A-Za-z0-9._%-]+@[A-Za-z0-9.-]+[.][A-Za-z]+$');

А при помощи CREATE TYPE можно создать новый составной тип, например, новый тип данных для хранения в одном поле высоты, ширины и веса.

-- Postgres create type для составного типа
CREATE TYPE physical_package AS (
height numeric,
width numeric,
weight numeric);

Enum позволяют создавать произвольные типы данных со множеством заранее указанных значений.

-- Postgres enum
CREATE TYPE order_status AS ENUM (
'pending',
'shipped',
'cancelled');

Constraint позволяют указывать правила и ограничения для данных. Например, CHECK позволяет ограничиваться списком или даже ссылаться на другие поля, допустим, при резервировании гостиничного номера со временем начала и завершения.

-- Postgres check contraint
ALTER TABLE public.reservations
ADD CONSTRAINT start_before_end
CHECK (start_time < end_time);

Пусть большинство приложений ограничивает используемые данные по-своему, но строгая и гибкая типизация Postgres обеспечивает и жёсткую валидность, и универсальность.

2) Расширяемость типов данных, операций и способов доступа

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

Возьмём для примера PostGIS. Это расширение добавляет множество ключевых типов данных для хранения геопространственных типов — точек, линий, многоугольников. Также у PostGIS есть сотни функций. Сегодня вокруг этого проекта уже существует настоящая отдельная экосистема, включающая в себя опенсорсную картографию и полностью открытые опенсорсные веб-серверы, конкурирующие с платными ГИС наподобие ESRI.

Ещё один хороший пример расширяемости Postgres — pgvector extension. Теперь Postgres может хранить данные эмбеддингов вместе с данными приложений. LLM могут создавать эмбеддинги на основании данных, после чего можно выполнять запросы к данным для нахождения связанности. Можно даже создать собственную RAG-систему Postgres прямо внутри базы данных.

-- находим расстояние между двумя значениями эмбеддингов
recipe_1.embedding <=> recipe_2.embedding

Типы данных и расширения — это не единственное, что мы получили из этого принципа: сами индексы Postgres невероятно функциональны. Generalized Inverted Index (GIN) и Generalized Search Tree (GiST) сами по себе — это расширяемые фреймворки индексации, поддерживающие многие из описанных выше сложных типов данных.

3) Функции для активных баз данных (например, уведомления и триггеры)

У пользователей современной Postgres есть набор инструментов для того, чтобы база данных выполняла необходимую им работу. Система триггеров обновляет поля при изменении другого поля.

-- пример функции Postgres для обновления полей
CREATE OR REPLACE FUNCTION update_inventory_on_sale()
RETURNS TRIGGER AS $$
BEGIN
UPDATE products
SET quantity_on_hand = quantity_on_hand - NEW.quantity_sold
WHERE id = NEW.product_id;
IF NOT FOUND THEN
RAISE EXCEPTION 'No product found with ID %', NEW.product_id;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;

Для событий вне базы данных у Postgres есть удобный маленький механизм рассылки уведомлений наружу NOTIFY/LISTEN, чтобы приложение или дэшборд знали о размещении новых заказов или выполнении определённого действия. Уже существует расширение для использования событий системы уведомлений в качестве WebSockets.

В логической репликации Postgres используется принцип «активной базы данных». Логическая репликация удобна тем, что выполняет потоковую передачу отдельных изменений данных, а не копирование на уровне физических блоков: это позволяет реплицировать данные между разными старшими версиями PostgreSQL или даже разными платформами. Такая гибкость позволяет реализовывать такие сложные сценарии, как создание специализированных реплик чтения, консолидация нескольких баз данных в одну центральную и выполнение апгрейдов до старших версий с нулевым даунтаймом.

-- Postgres create логической репликации
CREATE PUBLICATION user_pub FOR TABLE user_id, forum_posts;

4) Упрощение процесса восстановления после сбоев

Изначально для восстановления данных в Postgres необходимо было перед каждым коммитом записывать все модификации данных в файлах на диск (это называлось «force-to-disk»). К сожалению, этот изначальный способ сильно влиял на производительность и потенциально мог приводить к повреждению данных. Появившийся в версии 7.1 механизм Write Ahead Log (WAL) реализовывал другую систему: изменения сначала записывались в файл журнала, а затем применялись к основным файлам данных.

WAL стал фундаментом для всей потрясающей истории резервного копирования и восстановления после аварий Postgres. Он используется для создания инкрементных бэкапов и дополнен системой восстановления после сбоев Point-in-Time, которую применяют сегодня многие пользователи.

WAL также стал фундаментом для потоковой репликации Postgres, благодаря которой стала возможной высокая степень доступности. Основной кластер записывает все изменения базы данных (добавления, обновления, удаления) в свой Write-Ahead Log, а затем потоково передаёт эти записи WAL по сети резервным узлам (репликам). Резервные узлы получают эти записи WAL и применяют их к собственной копии базы данных, обеспечивая их синхронизацию с основным кластером. В случае сбоя автоматизированная система перехода на резерв наподобие Patroni может предоставить новый основной кластер.

5) Использование преимуществ современного оборудования

PostgreSQL проектировалась с учётом реальности аппаратного обеспечения того времени: одноядерных CPU, сильно ограниченного ОЗУ, часто измерявшегося в мегабайтах, и медленных жёстких дисков. Основной целью проектирования была корректность и устойчивость данных, а не голая скорость. PostgreSQL наработала свою легендарную репутацию благодаря стабильности и соответствию ACID, обеспечивающим безопасность данных даже при работе на не очень надёжном оборудовании.

Перенесёмся в современность: сегодня PostgreSQL работает на «железе» с десятками ядер CPU, терабайтами сверхбыстрых накопителей NVMe и огромными размерами ОЗУ (до половины терабайта). Недавно в PostgreSQL появилось параллельное выполнение запросов, при котором сложные запросы разбиваются на части и исполняются одновременно со сбором результатов в конце. Кроме того, современная PostgreSQL существенно усовершенствовала свои механизмы блокировок, решения для организации пулов соединений и возможности репликации, эволюционировав из надёжной односерверной базы данных в высокопроизводительную систему, способную масштабироваться горизонтально и справляться с огромными конкурентными нагрузками современного Интернета.

Хотя в Postgres пока нет многопоточности современных CPU, её реализация уже на горизонте, а в Postgres 18 добавили асинхронный ввод-вывод.

6) Использование реляционной модели Кодда

На пике популярности движения NoSQL в конце 2000-х и начале 2010-х часто говорилось о том, что реляционные базы данных — реликт прошлого. С увеличением объёмов больших и неструктурированных данных эта старая модель должна была со временем остаться не у дел.

Postgres продолжала делать то, что делала всегда, а также использовала свою главную сильную сторону — гибкость типизации данных, плюс адаптировала часть идей NoSQL. Postgres внедрила тип данных JSON, а позже двоичный индексируемый тип JSONB. Благодаря этому обновлению приложения могли сохранять данные JSON без схем напрямую в реляционную базу данных и эффективно выполнять запросы к ним при помощи широкого спектра операторов и функций. Благодаря таким возможностям, как json_table, можно было выполнять переходы между массивами и традиционными таблицами.

Последней революцией в мире Postgres стало внедрение технологий привязки Postgres напрямую к неструктурированным двухмерным файлам. Проекты наподобие pg_duckdb, pg_mooncake и Crunchy Data Warehouse используют расширения для прямой работы с файлами в csv, Parquet и Iceberg в удалённых хранилищах объектов озёр данных. Даже когда данные абстрагируются в другую локацию, реляционная модель Postgres остаётся релевантной, эффективной и надёжной.

Подведём итог

Благодаря гибкости Postgres можно создать полностью нормализованную реляционную схему с внешними ключами и JOIN, но при этом имея индексированный документ JSONB и полную пространственную геометрию. Сегодня мы находимся в том моменте, когда ИИ, наука и исследования полагаются на СУБД, которую начали проектировать, понятия не имея, каким будет мир. И Postgres по-прежнему остаётся с нами.

Исходные цели авторов глубоко повлияли на проект. Они обеспечили комплексность и гибкость с условиях развития бизнеса, в то же время позволив настраивать систему под индивидуальные сценарии использования. А благодаря адаптированности под аппаратное обеспечение (и облака) распространение Postgres стало ещё проще.

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