Как PostgreSQL может заменить половину инфраструктуры без лишних сервисов

PostgreSQL умеет выполнять задачи, для которых обычно поднимают отдельные сервисы: очереди задач, блокировки, полнотекстовый поиск и аналитику. При правильной настройке база данных способна заменить половину инфраструктуры, снижая сложность и риски отказов.

Как PostgreSQL может заменить половину инфраструктуры без лишних сервисов

Почему PostgreSQL может заменить половину инфраструктуры

В типичном бэкенде рано или поздно появляются отдельные сервисы и таблицы для очередей, блокировок, поиска по текстам, дедупликации и расчётов. Их поднимают как инфраструктурную надстройку: Redis для очередей, Python-скрипты для фильтрации дублей, Go-циклы для расчёта интервалов между статусами заказов, Elasticsearch для поиска, а ещё самодельная таблица locks для координации воркеров.

Почти всё это PostgreSQL уже умеет делать сам — атомарно, транзакционно, с индексами и без лишних прыжков по сети между приложением и базой. Десять таких фич часто остаются за кадром, хотя могли бы сократить половину инфраструктуры.


Очереди без брокера: FOR UPDATE SKIP LOCKED

Механизм FOR UPDATE SKIP LOCKED (появился в PostgreSQL 9.5) позволяет нескольким воркерам безопасно брать задачи из таблицы, не блокируя друг друга. Воркер, который первым захватил строку, забирает задачу в статус running; остальные пропускают занятую строку и берут следующую. Внешний брокер не нужен — достаточно схемы с колонками queue, priority, status, run_at, unique_key и индексами по ним.

Такой подход требует идемпотентных обработчиков: если задача не выполнилась с первого раза, система должна суметь повторить её без дублей. Для этого вводят экспоненциальные задержки между попытками — например, 10 секунд, 20, 40, до часа. В результате таблица задач становится меньше по объёму, а система — надёжнее: нет риска, что Redis откажет в самый неподходящий момент.


Оконные функции: аналитика без циклов в коде

Если нужно посчитать время между статусами заказа, проще оставить расчёт в SQL:

SELECT order_id, status, created_at,
       created_at - LAG(created_at) OVER (PARTITION BY order_id ORDER BY created_at)
FROM order_events;

Это быстрее, чем вытаскивать все события в код и крутить циклы в Python. Но на больших таблицах сортировка в оконных функциях может стать узким местом. План выполнения покажет Seq ScanSortWindowAgg. Чтобы ускорить, добавляют индекс по (order_id, created_at).


Первая строка в группе: DISTINCT ON вместо подзапросов

Когда нужен последний платёж для каждого пользователя, пишут:

SELECT DISTINCT ON (user_id) *
FROM payments
ORDER BY user_id, created_at DESC;

Это проще и быстрее, чем бороться с ORM, которая не всегда дружит с группировкой по первой строке. Ещё один плюс: индекс по (user_id, created_at DESC) ускоряет запрос, но мало кто его добавляет.


Вычисляемые колонки: GENERATED ALWAYS AS вместо триггеров

Вместо триггеров или ручной синхронизации можно завести колонку, которая обновляется сама:

ALTER TABLE orders
ADD COLUMN status_calculated
GENERATED ALWAYS AS (
  CASE
    WHEN paid_at IS NOT NULL THEN 'paid'
    WHEN shipped_at IS NOT NULL THEN 'shipped'
    ELSE 'pending'
  END
) STORED;

Такая колонка решает проблему рассинхронизации, но не все ORM поддерживают generated columns — и тогда приходится думать, как обойтись без них.


Почему таблица «пухнет»: VACUUM и настройки autovacuum

После удалений и обновлений в таблице остаются мёртвые строки — «призраки», которые давят на диск и тормозят запросы. PostgreSQL чистит их сам, но autovacuum может подвесить базу, если не настроен. Ключевые параметры:

  • autovacuum_vacuum_scale_factor — доля таблицы, после которой запускается очистка;
  • maintenance_work_mem — сколько памяти можно потратить на перестройку индексов.

В продакшене autovacuum нередко становится источником блокировок — и тогда приходится вручную запускать VACUUM в окно низкой нагрузки.


LATERAL JOIN: зависимый подзапрос без тормозов

Если нужно для каждого пользователя достать последний заказ, пишут:

SELECT u.*, o.*
FROM users u,
     LATERAL (
       SELECT * FROM orders
       WHERE user_id = u.id
       ORDER BY created_at DESC
       LIMIT 1
     ) o;

Это быстрее, чем два отдельных запроса, но только если есть индекс по user_id. На больших наборах данных без индекса LATERAL JOIN превращается в кошмар.


Advisory locks: блокировки, которые не блокируют строки

Если нужно гарантировать, что в кластере запустится только одна cron-задача или миграция, используют advisory locks:

SELECT pg_advisory_xact_lock(12345);
-- ... критическая секция ...
-- Блокировка снимается автоматически при завершении транзакции

Проблема в том, что если приложение падает, блокировка может остаться — и её придётся чистить вручную. Поэтому числовые ключи обычно делают «смещёнными», например 1000000 + user_id, чтобы не пересекаться с другими системами.


Встроенный поиск: когда Elasticsearch — это перебор

Встроенный полнотекстовый поиск по tsvector и tsquery решает большинство задач без внешнего индекса. Пример ранжирования:

SELECT title, ts_rank_cd(search_vector, query) AS rank
FROM articles
WHERE search_vector @@ plainto_tsquery('руководство')
ORDER BY rank DESC;

Это достаточно для простого поиска. Elasticsearch понадобится только при сложных токенизаторах или горизонтальном масштабировании.


Частичные индексы: маленькие индексы для реальных нужд

Если нужен индекс только для активных заказов, создают частичный:

CREATE INDEX idx_orders_active ON orders (user_id)
WHERE status IN ('paid', 'shipped');

Он меньше и быстрее полного индекса, но работает только если условие покрывает все запросы. Проверить это можно через EXPLAIN: если индекс не используется, значит, условие не совпадает с запросом.


Граница применимости

PostgreSQL не заменит всё. В высоконагруженных очередях, например, RudderStack использует пакетную обработку и уплотнение блоков — иначе база не выдерживает нагрузки. А для геоданных или временных рядов нужны расширения вроде PostGIS или TimescaleDB.

Но если перестать думать о базе как о простом хранилище, половина инфраструктуры может исчезнуть.

Read more

Тараканы-киборги под водой: как насекомые помогают в спасательных операциях

Тараканы-киборги под водой: как насекомые помогают в спасательных операциях

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

Gamma в ChatGPT: как ИИ ускоряет черновую сборку презентаций

Gamma в ChatGPT: как ИИ ускоряет черновую сборку презентаций

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

Ford вернула инженеров, чтобы исправить ошибки ИИ в контроле качества

Ford вернула инженеров, чтобы исправить ошибки ИИ в контроле качества

В 2023 году Ford начала возвращать уволенных инженеров, чтобы компенсировать провалы автоматизированного контроля качества. Опытные специалисты выявили дефекты, которые ИИ не заметил, и переобучили системы, сэкономив компании сотни миллионов долларов.

Подземная грибная сеть: как вспашка и удобрения разрушают скрытую инфраструктуру полей

Подземная грибная сеть: как вспашка и удобрения разрушают скрытую инфраструктуру полей

В верхних 15 сантиметрах почвы скрыта сеть грибных гиф длиной до 110 квадриллионов километров — она связывает растения, хранит углерод и кормит урожай. Но интенсивное земледелие сокращает её плотность на 50%, лишая поля естественного плодородия и устойчивости.