2. Основная БД

[к списку решений]

Дата: 2025-02-10

Статус

Принято

Контекст

Резервирование обязательно (вертикальное масштабирование - кластер с репликацией и автоматическим фейловером). Шардинг желателен (горизонтальное масштабирование на разные серверы или физические диски).

Особое требование: хранить и быстро находить big data (заказы).

В критерии выбора не входит полнотекстовый поиск - для этого есть ElasticSearch, куда данные из основной БД будут зеркалиться через Kafka (Outbox pattern). Бизнес-события вида “продукт добавлен”, “заказ оплачен”, нужные для аудита и бизнес-аналитики, сюда тоже не относятся - для этого есть ClickHouse, куда события будут попадать из Kafka (Outbox pattern).

Решение

Для надёжности: Postgres 17 с кластером от Percona.

Для производительности:

  • разделение таблиц на партиции с отдельными tablespaces (и физическими дисками для них) для самых объёмных таблиц
  • где возможно, отдельные БД для изолированных доменов данных, например для таких микросервисов, чьи данные можно хранить отдельно от основной БД

Что даст внедрение решения?

Приемлемое (не более 1 сек.) время обработки типичных запросов. Резервирование - кластеризация с репликацией и автоматическим (прозрачным для приложений) переходом на другой сервер в случае сбоя.

Варианты

Postgres 17 с разделением таблиц на партиции

[наверх]

См. партиции. В терминах CAP-теоремы это CA, т.к. все партиции (фактически самостоятельные таблицы обслуживаются одним сервером. Но их можно ускорить с помощью RAID с чередованием (например RAID6), что рекомендуется, либо подключая разные диски к разным партициям, которых надо столько, чтобы в каждой партиции было не более 50 млн. записей:

CREATE TABLE orders
(
  id uuid PRIMARY KEY DEFAULT gen_random_uuid()
) PARTITION BY HASH (id);
  
-- хранение партиций в отдельной схеме, чтобы не засорять основное пространство имён
CREATE SCHEMA parts;
  
CREATE TABLESPACE disk1
  LOCATION '/mnt/disk1';
CREATE TABLE parts.orders_part1
  PARTITION OF orders
  FOR VALUES WITH (MODULUS 2, REMAINDER 0)
  TABLESPACE disk1;
  
CREATE TABLESPACE disk2
  LOCATION '/mnt/disk2';
CREATE TABLE parts.orders_part2
  PARTITION OF orders
  FOR VALUES WITH (MODULUS 2, REMAINDER 1)
  TABLESPACE disk2;

Преимущества и недостатки

Преимущества
  • Полный набор возможностей БД, таких как транзакции, ссылочная целостность, процедуры, рекурсивные CTE.
Недостатки
  • Нет нативной кластеризации - решается с помощью кластера от Percona. Это HA-прокси, который берёт на себя репликацию и фейловер.

Postgres 17 с каким-либо расширением для шардинга

[наверх]

См. например Citus.

Преимущества и недостатки

Преимущества
  • Автоматизация репликации и шардинга.
Недостатки
  • Нет экспертизы по администрированию.

ElasticSearch

[наверх]

Поиск происходит в ElasticSearch, кроме того продукты - практически read-only ресурс, поэтому можно рассматривать ElasticSearch как основное их хранилище (шардинг там поддерживается, но нет транзакций и запись происходит с задержкой), а в Postgres’е хранить только id - для вторичных ключей и джойнов

Преимущества и недостатки

Преимущества
  • Экономия места на диске, а значит и скорость выше.
Недостатки
  • Нельзя делать джойны по другим полям.
  • Для получения полной информации нужно делать REST-запрос в ElasticSearch (частично нивелируется кэшированием на уровне отвечающего за CRUD микросервиса в Redis, скажем, на 24 часа).

TimescaleDB

[наверх]

Если трактовать момент сохранения записи (заказа / продукта) в БД как временной ряд, то можно рассмотреть Postgres 17 с расширением TimescaleDB, которое предоставляет hypertables для автосоздания партиций и компрессию данных для снижения объёма на диске до 20 раз (сжатые данные становятся read-only, но момент сжатия можно отсрочить на, к примеру, 90 дней, тем самым получив hot storage и архив).

Можно добавить tablespaces для хранения партиций на разных дисках (хотя рекомендуется один tablespace и под ним RAID с чередованием+зеркалированием). К одной hypertable можно присоединить ряд tablespaces; TimescaleDB это понимает и будет шардить:

CREATE TABLESPACE disk1
    LOCATION '/mnt/disk1';
CREATE TABLESPACE disk2
    LOCATION '/mnt/disk2';

ALTER TABLE orders
SET (
    timescaledb.compress,
    timescaledb.compress_segmentby = 'id',
    timescaledb.compress_orderby = 'created_date'
    );
SELECT attach_tablespace('disk1', 'orders');
SELECT attach_tablespace('disk2', 'orders');
SELECT add_compression_policy('orders', INTERVAL '90 days');

Преимущества и недостатки

Преимущества
  • Полная автоматизация создания партиций.
Недостатки
  • Партиции можно создавать только на основе поля типа “дата” (в статьях в Интернете иногда упоминается, что это может быть любое числовое поле, но на оф. сайте написано, что только дата).
  • Ускоряет поиск, только если в условия поиска всегда входит поле типа “дата” которое используется для выбора той партиции, где будет производиться дальнейший поиск (по каким-то другим полям).
  • Набор аналитических запросов уступает таковым в специализированных OLAP-системах, таких как ClickHouse.

CockroachDB

[наверх]

См. сайт производителя.

Преимущества и недостатки

Преимущества
  • Автоматизация кластеризации.
Недостатки
  • Нет экспертизы по администрированию.
  • Это не Postgres, просто поддерживается его сетевой протокол.
  • Язык хоть и SQL, но не drop-in replacement для Postgres.

MongoDB

[наверх]

В терминах CAP-теоремы это CP, т.к. во время выбора ноды-лидера кластер недоступен.

Лицензия MongoDB 8 (см. пункт 13) запрещает предоставлять продукт в виде сервиса (чтобы облака не перепродавали изначально бесплатный продукт), а для внутреннего использования в проектах - позволяет.

Преимущества и недостатки

Преимущества
  • NoSQL в целом на чтение работает быстрее, чем SQL.
  • ACID-транзакции (появились в 2018 г.).
  • Конвейерная агрегация, позволяющая снизить количество запросов к БД, т.к. действия можно сцеплять друг с другом.
  • Нативное хранение вложенных друг в друга документов, что уменьшает количество джойнов. В Postgres’е так можно хранить либо массивы скаляров, либо сырой JSON, т.обр. структуру вложенных полей невозможно формализовать, а в MongoDB - можно на уровне Spring Data (понятия схемы данных в MongoDB нет).
  • Простота администрирования - многое работает «из коробки»:
  • кластеризация с репликацией, автоматическим фейловером, можно динамически добавлять новые ноды в кластер
  • шардинг на основе consistent hashing с автоматической перебалансировкой (миграцией данных) при добавлении нового сервера (шарда)
Недостатки
  • Язык запросов - ничего общего с SQL.
  • Нет вторичных ключей, т.е. ссылочной целостности, например можно удалить запись, ID которой есть в других записях.
  • Нет понятия схемы данных - Mongo не знает, что и в каком виде хранится, например при опечатке в имени поля при вставке записи появится запись с неправильным именем поля. Одновременно это и достоинство, если нужно хранить данные нефиксированной структуры: в Postgres’е пришло бы либо хранить сырой JSON, либо динамически создавать таблицы нужной структуры и работать из Java сырым SQL, без Hibernate.
  • Для минимального кластера без шардинга нужно 7 виртуалок: 3 реплики с данными, 3 реплики с метаданными кластера, рутер запросов.
  • Для минимального кластера с шардингом нужно 10 виртуалок: 2 шарда (по 3 реплики у каждого шарда, итого 6 нод), 3 реплики с метаданными кластера, рутер запросов.