Просто берите Postgres
Несколько месяцев назад завирусилась статья Just Use Postgres. Она была на всех площадках, а том числе в переводе на русский. Я чуть было не репостнул ее, но передумал. На проверку статья оказалось поверхностной: скажем, автор на полном серьезе сравнивает Postgres с SQLite. Мне показалось, в статье нет глубины, и тезис из заголовка ничем не подтверждается. И хотя вывод верный — Just Use Postgres — автор пришел к нему странным способом.
В своей заметке я расскажу, как пришел к аналогичному выводу сам — только доводов будет больше.
Последний год я занимаюсь Посгресом все активнее. Я мигрировал большую систему с OpenSearch на Postgres. Это 30 сервисов с большими JSON-документами — от нескольких тысяч до миллионов в каждом сервисе. Нужен поиск по вложенным полям, поиск по вхождению, простое ранжирование, а также всякая отчетность. И пока я все это мигрировал, узнал о Постгресе столько, что хватит на несколько докладов. В том числе — почему именно Постгрес так хорош.
Я давно понял одну вещь, а миграция только ее укрепила. Хранение данных определяет разработку. Это фундамент, относительно которого планируешь куда копать и что возводить. Кто-то считает, что абстракцией можно уравновесить любое хранилище: сделать так, что get-by-id либо идет в базу, либо качает файл из S3. Это справедливо в простых случаях. На практике каждое хранилище вносит свои особенности, с которыми нужно мириться. Если у вас условные OpenSearch или Cassandra, их особенности будут фонить сквозь код. Избежать этого нельзя. На мой взгляд, Постгрес фонит меньше всех: с ним у вас будет меньше проблем в абстракцях.
Но довольно расплывчатых слов, перейдем к конкретике. Начну с того, что Постгрес легко ставится и работает на любой машине, будь то локальный комп с Виндой, Маком, Линуксом или сервер. Он есть во всех пакетах. Постгрес написан на Си, и на выходе бинарный файл, которому не нужна Джава. Помню, как ставил Датомик на Убунте — это было тяжело. Вроде бы Джава, “compile once, run everywhere (c)” — но вылетают ошибки о том, что классы не найдены. Оказалось, нужна другая Джава, которую нужно ставить отдельно. С Постгресом такого не было никогда.
Для Мака есть проект Postgres.app. Это приложение с графическим интерфейсом, чтобы запустить Postgres. Можно скачать любую версию по отдельности; есть убер-приложение со всеми версиями и установленным PostGis. Так что любой человек может завести Postgres + PostGis в два клика.
Особой похвалы заслуживает докерный образ Postgres. Он очень гибко настраивается: почти любую опцию можно задать переменной среды, легко прокинуть свою конфигурацию. У образа убойная фича: папка, куда можно накидать файлы .sql, .sh и .gz. При запуске образ запустит эти файлы в алфавитном порядке. Если у вас миграции или посев тестовых данных, смонтируйте файлы в образ, и при запуске получится готовая база.
По наивности я думал, что так работают образы других баз данных. Оказалось нет. Запустил образ Кассандры, а она ничего не знает о первичной настройке. Нужны разделы и таблицы? Создай сам. Нужны топики в Кафке? Создай сам. Нужна точка обмена в RabbitMQ? Создай сам! После запуска образа нужно дождаться, пока поднимутся все потроха (обычно это поллинг порта), а потом создать таблицы и топики. Почему-то в Постгресе подумали над этим, а остальным безразлично. Считаю, что образ postgres нужно брать за образец.
У документо-ориентированных баз и key-value хранилищ есть преимущество: они хорошо реплицируются в силу дизайна. Часто говорят: вы со своим Постгресом упретесь в потолок, когда нужно хранить данные в разных датацентрах, но при этом иметь легкий доступ из одного центра к другому. Условная Кассандра чувствует себя лучше в подобных условиях. Но забывают, что реплицировать нужно не все данные, а только их часть. Например, только базовые сведения о пользователях и сущностях, чтобы быстро выяснить, в каком дата-центре они лежат и выполнить запрос там.
Так у нас было устроено в одном облачном хостинге. В каждом дата-центре данные хранились в MySQL, а пользователи и права доступа дублировались в кластер Кассанды, который в силу репликации был доступен отовсюду.
Postgres тоже не стоит на месте, и в нем все больше средств репликации и кластеризации. Есть проекты вроде Debezium, которые читают журнал WAL и стримят изменения в другие базы, очереди и так далее.
Postgres силен в проекции данных. Бывает, на одни и те же данные нужно смотреть под разным углом: делать группировки, поворачивать таблицы. Часто нужны левые соединения: это когда слева находится полный набор данных, а справа — неполный, и данные слева не должны пропадать. В редких случаях нужно декартово произведение двух таблиц (каждый по каждому), что тоже делается легко. Есть много функций, которые разбивают данные на строки (переводят массивы в строки элементов) и наоборот — агрегируют записи в коллекции.
Postgres силен рекурсивными запросами. Это когда запрос разбивается на две части: первичный посев и рекурсивная логика, которая принимает предыдущий результат и порождает новый. Пока он не пустой, строки складываются в итоговую таблицу. За счет рекурсии прекрасно обходятся таблицы со ссылками на себя, например структура папок, иерархия сущностей.
В финансах очень важны оконные функции: посчитать нарастающий доход, остаток на счете, стоимость проекта по неделям и многое другое. Оконные функции слегка пугают, но достаточно прочитать одну книжку, чтобы овладеть ими (см ниже). Без оконных функций происходит следующее: человек выгребает из базы массив данных и проделывает вручную то, что умеет база — только на порядок медленней и с кучей багов. О похожем случае я как-то уже писал.
Огромную пользу можно получить из связки materialized view и pg_cron. Напомню, materialized view — это вьюхи, которые сбрасываются в физическую таблицу. На нее можно навесить индексы, чтобы ускорить поиск. Польза таких вьюх огромна — это различные проекции и отчеты. Чтобы каждый раз не гонять огромный запрос, его “запекают” во вьюху и материализуют, после чего выбирают строки обычным SELECT.
В текущем проекте мы храним огромные JSON-документы. У них сложная структура, но
отчетность должна быть плоской. Сначала я писал запросы со множеством операторов
#>>
, которые извлекают данные из JSON по пути в виде массива. Но со временем
стал делать плоские представления этих документов вьюхами — и дело пошло
лучше. Аналитикам и менеджерам тоже легче: им постоянно нужные данные, и они
пишут запросы сами, чтобы не дергать программистов.
Расширение pg_cron выводит Постгрес на
новый уровень: с его помощью можно выполнить любой скрипт по
расписанию. Расширение использует стандартный синтаксис crontab. У меня
множество крон-задач на материализацию вьюх и прогрев индексов — их
принудительный загон в оперативную память. С pg_cron
база становится полностью
автономной: не нужен сторонний крон, который пинает скрипты. Я недоволен только
одним: pg_cron
— стороннее расширение, и его нет в поставке. Однако облачные
провайдеры вроде Амазона предустанавливают его.
На сегодня Постгрес — лучшая база для работы с JSON-документами. Я не в восторге от JSON и насколько возможно, храню данные в таблицах. Но порой выбора нет: бизнес завязан на какие-то стандарты. Пример — медицинский формат FHIR. Это огромные документы тройной и более вложенности. Раскладывать их по таблицам и собирать джоинами тяжело, поэтому их хранят в поле jsonb. У меня похожая ситуация : 30 сервисов, каждый отвечает за свою бизнес-сущность. Это большие JSON-ы, и сервисы гоняют их туда-сюда; на них завязан фронтенд. Я пытался представить их в плоском виде, но это очень трудно.
Постгрес предлагает богатные возможности для JSON: доступ ко вложенным полям по массиву ключей, обновление вложенных полей, слияние словарей, гибкую замену, индексацию документа целиком или подмножества… Есть даже встроенный язык JSON PATH для поиска! Да, внутри SQL может быть строка с мини-языком JSON PATH. Я использую его в сочетании индексами btree по отдельным полям.
В последнем Постгресе 17 появилась функция JSON_TABLE. По указанной спеке она переводит JSON в таблицу с выводом типов. Если у вас вектор мап, то легко получить таблицу. JSON_TABLE поддерживает вложенность, в результате чего можно развернуть мапу мап в плоскую таблицу. Далее вы материализуете ее, ставите на крон и готово — можно выбрать плоские данные селектом.
Для Постгреса создано великое число обучающих материалов: книг, курсов, самоучителей. Многие из них изначально созданы на русском, то есть не являются переводами. Российский вендор Postgres Pro не только пишет отличные книги, но и выкладывает на сайте бесплатно. Бесплатно! Я читал некоторые из них, и это не халтура, а действительно проработанные материалы. Книга Егора Рогова “Postgres изнутри” описывает устройство базы в мельчайших деталях. Наверное, нет книги лучше, чтобы понять, как работают современные базы данных.
В Постгресе отличный анализатор запросов: он покажет, какие стратегии выбрал движок для обхода таблиц и джоинов; какие индексы были использованы и какую их часть пришлось читать с диска. Да, понадобится время, чтобы понять его вывод. Но иные базы данных не предлагают вообще ничего! Просто дают рекомендации, а дальше пробуй сам. Есть расширения, которые фиксируют медленные запросы и их план выполнения. Расширение pg_stat_statements ведет статистику по всем запросам: число вызовов, частота, минимальное, максимальное, среднее время выполнения, ожидание, объем передачи данных и прочее. Все это помогает отлаживать случаи, когда базе нехорошо.
В Постгресе достойный полнотекстовый поиск. Для начала подойдет триграммный нечеткий поиск по коэффициенту совпадения. Позже можно добавить ts_vector — вектор лексем и стемминга. Из коробки есть стемминг для десятка языков, в том числе русского. Когда заходят разговоры об OpenSearch и других поисковых движках, оказывается, что на Постгресе можно сделать не хуже и главное — без добавления в систему нового узла.
У Постгреса обширный тулинг: консольные и графические клиенты — браузерные и настольные. PGAdmin, DBeaver, DataGrip… стандартная утилита psql покрывает множество случаев. Она показывает сведения обо всех сущностях, выводит данные разными способами, умеет импорт-экспорт. Можно редактировать запросы и сущности во внешнем редакторе, например Emacs или Vim.
Постгрес поддерживает апишку COPY, с помощью которой данные гоняют в обе стороны. Если я хочу слить таблицу в CSV, пишу что-то вроде:
COPY my_table (id, name, email) to STDOUT
with (HEADER, format CSV)
и Постгрес выплевывает CSV-шный файл. Можно записать файл на диск или читать построчно из сети. Это работает и в обратную сторону: если я хочу вставить CSV в таблицу, то пишу:
COPY my_table (id, name, email) from STDIN
with (HEADER, format CSV)
и стримлю в соединение строки CSV. Кроме CSV, Постгрес поддерживает бинарный формат. Спецификация довольна проста, и на практике он работает на 30% быстрее.
Словом, гонять большие данные в Постгресе очень просто. Я как-то писал о том, что источник данных хорош настолько, насколько удобно забрать из него данные. В том же OpenSearch забор данных превращается в муку: нужна ручная пагинация по страницам. А Постгрес выплюнет миллион строк и не моргнет глазом — только успевай их принимать.
Не менее важна генерация данных. Скажем, у вас на проде миллион записей, и нужно воспроизвести сложный запрос. Если в локальной базе только тысяча записей, он поведет себя по-другому; нужен именно миллион. Как вы их вставите?
Обычно на этом месте расчехляют Питон и всякие FakeMockGenerator-ы — библиотеки для генерации случайных данных по спеке. Этот код долго писать, и вставка тоже будет долгой, потому что мы передаем данные от клиента серверу. Еще нужен рантайм, то есть среда, где запускается код: какая-то машина, нода, плейбук.
А ведь достаточно написать примерно такой скрипт и выполнить его в PGAdmin:
insert into documents (id, document)
select
gen_random_uuid() as id,
jsonb_build_object(
'__generated__', true,
'meta', jsonb_build_object(
'eyes', format('color-%s', x)
),
'attrs', jsonb_build_object(
'email', format('user-%s@test.com', x),
'name', format('Test Name %s', x)
),
'roles', jsonb_build_array(
'user',
'admin'
)
) as document
from
generate_series(1, 500) as x
returning
id;
Он вставит в базу столько JSON-документов, сколько указано в функции generate_series. Хочешь миллион? Да пожалуйста. Важно, что данные генерятся сразу на сервере — мы не гоняем их по сети. Все происходит внутри Постгреса, и это значительно быстрее. Миллион огромных JSON-ов вставляются за несколько минут. Этот скрипт легко поправить, чтобы поля высчитывались по-другому. Каждый может скопировать его и выполнить на свой базе, не прибегая к Питону.
Иные жалуются, что SQL устарел. Мол, это архаичный язык, он плохо шаблонизируется, и логично выразить его данными, например JSON-ом. Что тут сказать… Да, SQL со скрипом ложится на всякие ORM. Но сегодня полно библиотек, которые строят SQL из объектов и коллекций. А во-вторых, мне нравится самобытность SQL, то, что он остается вещью в себе. Когда пишешь большие запросы, начинаешь видеть его красоту. Со временем понимаешь, что заменить его нечем — слишком много ситуаций и выражений.
Я рассматриваю SQL как REPL к данным. Наверное, вы знаете, что программисты на Лиспе днями сидят в репле. О преимуществе REPL-driven develpoment сказано много, и нет смысла повторять. По аналогии, SQL — это репл для данных со всеми преимуществами REPL-driven develpoment. Легко понять, какие данные прилетят в код, выполнив запрос в базе. Вместо быдлокода, который вынимает тысячи записей, исправляет их и записывает в базу, можно выполнить один UPDATE. Поймал себя на том, что целыми днями сижу в PGAdmin, а к коду приступаю в последнюю очередь.
Повторю тезис, который как-то высказывал. Данные — это отдельный домен. Ключевое свойство домена — его ортогональность другим доменам. Я хочу, чтобы данные не были привязаны ко всяким Питонам и Джавам. Я хочу управлять ими независимо от языка или потребителей. Мне не нравятся встраиваемые хранилища или базы, которые “сливаются” с приложением. Если данные можно извлечь только вызовом метода, я пас.
Рассказывать о том, как динамично развивается Постгрес, нет смысла. Это видно всем. Есть классический Постгрес, есть вендорский Postgres Pro, где доступны различные фичи до того, как они окажутся в классике — правда, за деньги. Развиваются расширения, появляются новые вендоры, проводят митапы и конференции, выходят книги, видосы… на любой запрос найдется контент.
Важно, что сообщество Постгреса прошло проверку на адекватность. Как только один чудак заявил, что нужно выгнать русских разработчиков и откатить их код, ему быстро объяснили, что к чему. Больше эту тему не поднимали, по крайней мере в публичном пространстве.
И вот теперь, в конце шестого листа, я говорю: вот поэтому берите Постгрес.
Нашли ошибку? Выделите мышкой и нажмите Ctrl/⌘+Enter