Главный принцип

Базу данных выбирают не по категории приложения и не по моде.

Не так:

«У нас микросервисы, значит MongoDB».

«У нас много данных, значит Cassandra».

«Нужна скорость, значит Redis».

А так:

Какие состояния система должна хранить, какие инварианты обязана защищать, какие операции выполняются чаще всего и что произойдёт при отказе?

Выбор базы данных начинается не с базы. Он начинается с модели реальности системы.


1. Сначала определить, что именно хранится

Нужно понять природу данных.

Например, в системе бронирования отелей существуют разные сущности:

  • отель;
  • тип номера;
  • конкретный номер;
  • доступность типа номера по датам;
  • бронирование;
  • платёж;
  • журнал изменений;
  • поисковый индекс;
  • пользовательская сессия.

Это не обязательно должна быть одна база данных.

Частая архитектурная ошибка заключается в поиске «идеальной базы для всей системы». Обычно разные части системы имеют разные требования.

Например:

  • бронирования хранятся в PostgreSQL;
  • доступность кешируется в Redis;
  • поиск отелей выполняется через OpenSearch;
  • события изменений отправляются в Kafka;
  • фотографии хранятся в S3;
  • аналитика строится в ClickHouse.

Это называется polyglot persistence, но применять его нужно не из любви к зоопарку технологий, а когда требования действительно различаются.


2. Начинать с инвариантов

Инвариант представляет собой правило, которое система не имеет права нарушить.

Для бронирования:

Количество подтверждённых бронирований не должно превышать доступное количество номеров.

Для банковского перевода:

Деньги не должны одновременно исчезнуть с одного счёта и не появиться на другом без зафиксированного промежуточного состояния.

Для склада:

Остаток товара не должен стать отрицательным, если отрицательные остатки запрещены бизнесом.

Для продажи билетов:

Одно место не должно быть продано двум пользователям.

Если база должна защищать подобные правила при конкурентных запросах, нужны:

  • транзакции;
  • ограничения;
  • уникальные индексы;
  • блокировки;
  • атомарные условные обновления;
  • предсказуемая модель консистентности.

В таком случае реляционная база данных часто становится естественным исходным выбором.

Например:

UPDATE room_inventory
SET available = available - 1
WHERE hotel_id = :hotelId
  AND room_type_id = :roomTypeId
  AND date = :date
  AND available > 0;

Если обновлено 0 строк, номера закончились.

Здесь база не просто хранит данные. Она участвует в обеспечении бизнес-инварианта.


3. Определить основные паттерны чтения и записи

Нельзя корректно выбрать базу, рассматривая только структуру данных.

Нужно знать, как данные будут использоваться.

Вопросы по записи

  • Сколько записей в секунду?
  • Записи одиночные или пакетные?
  • Обновляются существующие данные или постоянно добавляются новые?
  • Есть ли конкурирующие записи в одну сущность?
  • Требуется ли транзакция над несколькими строками или таблицами?
  • Можно ли временно принять дубликаты?
  • Можно ли потерять последние несколько секунд данных?
  • Записи распределены равномерно или существует горячий ключ?

Горячий ключ может возникнуть, например, при продаже билетов на один конкретный концерт. Общий RPS системы может быть умеренным, но тысячи запросов будут одновременно изменять одну запись доступности.

Вопросы по чтению

  • Какие запросы выполняются чаще всего?
  • Требуются ли фильтрация, сортировка и пагинация?
  • Нужны ли JOIN?
  • Нужен ли полнотекстовый поиск?
  • Есть ли запросы по диапазонам?
  • Доступ к данным идёт по идентификатору или по множеству параметров?
  • Нужны ли агрегации по миллиардам строк?
  • Насколько допустимы устаревшие данные?

Фраза «у нас миллион пользователей» почти ничего не говорит о выборе базы.

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

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


4. Определить модель консистентности

Нужно задать прямой вопрос:

После успешной записи обязан ли следующий запрос увидеть новое значение?

Strong consistency

Записали данные, после чего последующее чтение должно видеть актуальное состояние.

Обычно необходимо для:

  • финансовых остатков;
  • бронирований;
  • доступности товара;
  • управления правами;
  • критических настроек;
  • уникальности сущностей.

Eventual consistency

После записи разные узлы некоторое время могут видеть разные версии данных, но позднее состояние сойдётся.

Обычно допустимо для:

  • счётчиков просмотров;
  • количества лайков;
  • рекомендаций;
  • поискового индекса;
  • кешей;
  • аналитических витрин;
  • ленты событий.

Важно не путать eventual consistency с потерей данных. Данные могут быть надёжно записаны, но ещё не видны во всех представлениях системы.

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

Например:

  • само бронирование должно быть строго консистентным;
  • поисковая выдача может обновиться через пять секунд;
  • аналитический отчёт может отставать на час;
  • счётчик просмотров может обновляться приблизительно.

5. Определить необходимость транзакций

Транзакция требуется, когда несколько изменений образуют одну логическую операцию.

Например:

  1. создать бронирование;
  2. уменьшить доступность;
  3. сохранить платёжную попытку;
  4. записать событие для дальнейшей отправки.

В пределах одной реляционной базы первые, вторые и четвёртые действия можно выполнить в одной транзакции.

BEGIN;

INSERT INTO bookings (...);

UPDATE room_inventory
SET available = available - 1
WHERE ...
  AND available > 0;

INSERT INTO outbox_events (...);

COMMIT;

Если обновление доступности не произошло, транзакция откатывается.

Когда данные разнесены по нескольким независимым системам, единой ACID-транзакции обычно нет. Тогда появляются:

  • Saga;
  • Transactional Outbox;
  • идемпотентность;
  • компенсационные операции;
  • повторная доставка;
  • дедупликация;
  • reconciliation.

Поэтому распределение данных по разным базам имеет цену. Оно не является бесплатным «масштабированием».


6. Основные классы баз данных

Реляционные базы данных

Примеры:

  • PostgreSQL;
  • MySQL;
  • Oracle;
  • Microsoft SQL Server.

Хорошо подходят, когда нужны:

  • сложные отношения между сущностями;
  • транзакции;
  • ограничения целостности;
  • уникальность;
  • JOIN;
  • гибкие запросы;
  • сортировки и фильтры;
  • отчётность;
  • конкурентное изменение данных.

Типичные применения:

  • пользователи;
  • заказы;
  • платежи;
  • бронирования;
  • складские остатки;
  • права доступа;
  • договоры;
  • бизнес-сущности.

Сильные стороны

  • выразительная модель данных;
  • ACID-транзакции;
  • зрелые индексы;
  • constraints;
  • удобные ad hoc запросы;
  • понятная работа с конкурентностью;
  • развитая экосистема.

Ограничения

  • горизонтальное масштабирование сложнее, чем в системах, изначально построенных вокруг шардинга;
  • неправильные JOIN и индексы могут создавать тяжёлую нагрузку;
  • единичный горячий объект всё равно может стать точкой конкуренции;
  • схема требует управляемых миграций.

Практический default

Для новой бизнес-системы разумным исходным выбором обычно является PostgreSQL.

Не потому, что PostgreSQL всегда лучшая база, а потому что она покрывает очень широкий диапазон требований:

  • транзакции;
  • JSONB;
  • полнотекстовый поиск умеренного масштаба;
  • географические данные через PostGIS;
  • репликация;
  • партиционирование;
  • расширения;
  • сложные запросы.

Нужно не доказать, почему следует взять PostgreSQL. Нужно найти требование, которое делает PostgreSQL недостаточным или невыгодным.


Документные базы данных

Примеры:

  • MongoDB;
  • Couchbase.

Данные хранятся документами, обычно JSON-подобной структуры.

Подходят, когда:

  • объект естественно читается и записывается целиком;
  • структура документов различается;
  • вложенные данные принадлежат одному агрегату;
  • JOIN не являются основным способом доступа;
  • схема часто развивается;
  • запросы известны заранее.

Пример документа:

{
  "orderId": "123",
  "customer": {
    "id": "42",
    "name": "Igor"
  },
  "items": [
    {
      "productId": "A1",
      "quantity": 2,
      "price": 100
    }
  ],
  "total": 200
}

Сильные стороны

  • удобно хранить агрегат целиком;
  • меньше преобразований между объектом приложения и хранилищем;
  • гибкая структура;
  • удобно для вложенных данных.

Риски

Гибкая схема не означает отсутствие схемы. Схема просто перестаёт централизованно контролироваться базой и расползается по коду.

Через некоторое время могут существовать документы разных поколений:

{"name": "Igor"}
{"firstName": "Igor", "lastName": null}
{"profile": {"displayName": "Igor"}}

Тогда приложение вынуждено понимать все исторические варианты.

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


Key-value хранилища

Примеры:

  • Redis;
  • Amazon DynamoDB в простейших сценариях доступа;
  • Aerospike.

Основная модель:

key -> value

Подходят, когда:

  • чтение выполняется по известному ключу;
  • требуется очень низкая задержка;
  • данные короткоживущие;
  • структура запросов ограничена;
  • нужна высокая пропускная способность.

Типичные применения Redis:

  • кеш;
  • сессии;
  • rate limiting;
  • distributed locks с осторожностью;
  • временные токены;
  • счётчики;
  • лидерборды;
  • очереди небольшого масштаба;
  • хранение результатов дорогих вычислений.

Redis не следует автоматически выбирать как основную базу только потому, что он быстрый.

Нужно учитывать:

  • данные находятся преимущественно в памяти;
  • память дороже диска;
  • eviction может удалить ключи;
  • персистентность имеет свои компромиссы;
  • большие значения ухудшают задержки;
  • отдельные команды могут блокировать event loop;
  • горячие ключи создают локальную перегрузку.

Wide-column базы

Примеры:

  • Cassandra;
  • ScyllaDB;
  • HBase.

Подходят для:

  • огромного потока записей;
  • распределения по множеству узлов;
  • заранее известных запросов;
  • географически распределённых систем;
  • временных рядов;
  • событий;
  • телеметрии;
  • исторических данных.

В Cassandra таблица проектируется не от сущностей, а от запросов.

Не сначала:

Какие сущности существуют?

А сначала:

Каким запросом мы будем получать данные?

Например, если нужно получать события устройства за период:

partition key: device_id + day
clustering key: event_time

Это позволяет эффективно читать события конкретного устройства за конкретный день.

Сильные стороны

  • высокая скорость записи;
  • горизонтальное масштабирование;
  • отсутствие одного центрального master-узла;
  • высокая доступность;
  • хорошая работа с append-heavy нагрузкой.

Ограничения

  • слабее поддержка произвольных запросов;
  • денормализация;
  • данные часто дублируются под разные запросы;
  • сложнее поддерживать строгие инварианты;
  • неправильный partition key разрушает масштабирование;
  • большие партиции становятся проблемой;
  • нельзя мыслить Cassandra как «PostgreSQL, только распределённый».

Поисковые движки

Примеры:

  • Elasticsearch;
  • OpenSearch;
  • Apache Solr.

Подходят для:

  • полнотекстового поиска;
  • релевантности;
  • морфологии;
  • fuzzy matching;
  • сложной фильтрации;
  • фасетов;
  • поиска по множеству полей;
  • логов и наблюдаемости.

Например:

Найти отели в Берлине, где в описании встречается смысл «тихий район», рейтинг выше 8, есть парковка, цена от 100 до 200 евро.

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

  • токенизацией;
  • анализаторами;
  • синонимами;
  • опечатками;
  • ранжированием;
  • inverted index.

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

Чаще архитектура выглядит так:

PostgreSQL
    ↓
Transactional Outbox
    ↓
Kafka
    ↓
Indexer
    ↓
OpenSearch

PostgreSQL является системой записи, а OpenSearch представляет поисковую проекцию.


Time-series базы

Примеры:

  • TimescaleDB;
  • InfluxDB;
  • VictoriaMetrics;
  • Prometheus для метрик.

Подходят для данных вида:

объект + timestamp + значение

Например:

  • загрузка CPU;
  • температура;
  • телеметрия автомобиля;
  • бизнес-метрики;
  • сетевые показатели;
  • события устройств.

Основные требования:

  • высокая скорость добавления;
  • запросы по временным диапазонам;
  • retention;
  • downsampling;
  • агрегации по временным окнам;
  • сжатие исторических данных.

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


Аналитические колоночные базы

Примеры:

  • ClickHouse;
  • BigQuery;
  • Snowflake;
  • Redshift.

Подходят для:

  • агрегаций над миллиардами строк;
  • аналитических отчётов;
  • событий пользователей;
  • продуктовой аналитики;
  • логов;
  • больших сканирований;
  • OLAP-нагрузки.

Колоночное хранение эффективно, когда запрос читает несколько колонок из огромного количества строк.

Например:

SELECT
    country,
    count(*) AS bookings,
    sum(amount) AS revenue
FROM booking_events
WHERE event_date BETWEEN ...
GROUP BY country;

Для обработки одной пользовательской транзакции ClickHouse обычно не заменяет PostgreSQL.

Различие:

  • PostgreSQL оптимизирован преимущественно для OLTP;
  • ClickHouse оптимизирован для OLAP.

OLTP:

  • короткие запросы;
  • небольшое количество строк;
  • частые изменения;
  • транзакции;
  • высокая конкурентность.

OLAP:

  • большие сканирования;
  • агрегации;
  • история;
  • пакетная загрузка;
  • относительно редкие обновления отдельных строк.

Графовые базы

Примеры:

  • Neo4j;
  • Amazon Neptune;
  • JanusGraph.

Подходят, когда отношения являются главным объектом модели.

Например:

  • социальный граф;
  • маршруты;
  • зависимости;
  • fraud detection;
  • связи между аккаунтами, устройствами, картами и адресами;
  • knowledge graph;
  • цепочки владения.

Запрос:

Найти все аккаунты, связанные с этим устройством через не более чем четыре промежуточных связи.

В реляционной базе такой запрос возможен через recursive CTE, но при глубоком и часто изменяющемся графе графовая база может быть удобнее и производительнее.

Не следует выбирать графовую базу только потому, что между таблицами существуют связи. Связи есть почти в любой системе.


Объектные и файловые хранилища

Примеры:

  • S3;
  • Google Cloud Storage;
  • Azure Blob Storage;
  • MinIO.

Подходят для:

  • изображений;
  • видео;
  • документов;
  • резервных копий;
  • больших бинарных объектов;
  • архивов;
  • data lake.

Обычно в реляционной базе хранится не сам многогигабайтный файл, а метаданные и ссылка:

file_id
owner_id
object_key
content_type
size
checksum
created_at

Сам файл находится в object storage.


7. SQL против NoSQL является слишком грубым разделением

Термин NoSQL объединяет совершенно разные системы:

  • Redis;
  • MongoDB;
  • Cassandra;
  • Neo4j;
  • DynamoDB;
  • Elasticsearch.

Между Redis и Neo4j меньше сходства, чем между PostgreSQL и MongoDB.

Поэтому вопрос:

SQL или NoSQL?

нужно раскладывать на конкретные оси:

  • транзакции;
  • модель данных;
  • модель запросов;
  • консистентность;
  • распределение;
  • масштаб;
  • задержка;
  • доступность;
  • операционная сложность.

8. Масштаб нужно выражать числами

Нужно определить хотя бы порядок величин.

Пользователи

  • MAU;
  • DAU;
  • пиковые одновременные пользователи;
  • географическое распределение.

Трафик

  • средний RPS;
  • пиковый RPS;
  • read/write ratio;
  • размер запроса;
  • размер ответа;
  • сезонные пики;
  • допустимая задержка.

Данные

  • размер одной записи;
  • количество новых записей в сутки;
  • общий объём через год;
  • срок хранения;
  • процент горячих данных;
  • необходимый объём индексов;
  • рост вложений и файлов.

Пример оценки:

10 000 записей в секунду
× 500 байт
= 5 МБ/с

За сутки:

5 МБ/с × 86 400 секунд
≈ 432 ГБ в сутки

За год:

432 ГБ × 365
≈ 158 ТБ сырых данных

После этого выбор обычной одиночной PostgreSQL-инсталляции требует уже серьёзного обоснования.

Но если система выполняет 500 записей в секунду и хранит 200 ГБ данных, PostgreSQL может прожить очень долго без архитектурной пиротехники.


9. Проверить наличие горячих ключей и горячих партиций

Средняя нагрузка часто скрывает настоящую проблему.

Например, сервис продажи билетов имеет:

  • 100 000 событий;
  • 10 миллионов мест;
  • 5 000 RPS.

На бумаге нагрузка распределена.

Но при старте продаж популярного концерта:

  • 4 000 RPS идут на один концерт;
  • 1 000 пользователей пытаются купить один сектор;
  • десятки пользователей конкурируют за одно место.

Проблемой становится не общий объём базы, а конкурентный доступ к одному участку данных.

Нужно анализировать:

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

10. Определить требования к доступности

Вопрос не просто в том, должна ли база «работать всегда».

Нужно определить:

  • допустимый простой;
  • RTO;
  • RPO;
  • возможность работы при сетевом разделении;
  • необходимость нескольких регионов;
  • допустимость чтения устаревших реплик;
  • поведение при потере primary;
  • требования к резервным копиям.

RPO

Сколько данных можно потерять.

Например:

  • RPO = 0 означает, что потеря подтверждённых записей недопустима;
  • RPO = 5 минут означает, что при аварии допустима потеря последних пяти минут.

RTO

Сколько времени система может восстанавливаться.

Например:

  • RTO = 30 секунд;
  • RTO = 15 минут;
  • RTO = 4 часа.

Эти требования напрямую влияют на:

  • синхронную или асинхронную репликацию;
  • количество реплик;
  • multi-AZ;
  • multi-region;
  • стоимость;
  • сложность эксплуатации.

11. CAP нельзя использовать как лозунг

CAP относится к поведению распределённой системы во время сетевого разделения.

При partition система должна сделать выбор между:

  • Consistency;
  • Availability.

Но формулировка «PostgreSQL это CP, Cassandra это AP» слишком груба.

Реальные системы позволяют настраивать поведение:

  • quorum reads;
  • quorum writes;
  • synchronous replication;
  • read from replica;
  • consistency levels;
  • leader election;
  • bounded staleness.

Полезный вопрос звучит не так:

Эта база CP или AP?

А так:

Что произойдёт с конкретной операцией при потере связи между узлами?

Например:

  • будет ли операция отклонена;
  • будет ли принята на одном узле;
  • сможет ли возникнуть конфликт;
  • как конфликт будет разрешён;
  • увидит ли пользователь старое состояние;
  • можно ли совершить две противоречащие операции.

12. Учитывать эволюцию схемы

Нужно понять, насколько сильно будет меняться модель данных.

Реляционная база требует миграций:

ALTER TABLE bookings
ADD COLUMN cancellation_reason TEXT;

Это не недостаток само по себе. Явная миграция фиксирует изменение модели.

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

  • поддерживать старые версии;
  • мигрировать документы;
  • добавлять значения по умолчанию;
  • понимать отсутствующие поля;
  • различать семантически разные поколения данных.

Следовательно, вопрос не в том, есть схема или нет.

Вопрос:

Где схема контролируется и кто несёт цену её изменения?


13. Учитывать операционную сложность

Технология существует не только на архитектурной диаграмме.

Нужно спросить:

  • умеет ли команда её поддерживать;
  • кто будет настраивать репликацию;
  • кто будет восстанавливать данные;
  • как выполняются backup и restore;
  • как обновляется версия;
  • как наблюдать за производительностью;
  • как обнаруживать медленные запросы;
  • как менять схему;
  • есть ли managed-версия;
  • сколько стоит инфраструктура;
  • насколько сложно найти специалистов.

Cassandra из пяти узлов может великолепно выглядеть на схеме. Но если команда не умеет:

  • диагностировать tombstones;
  • контролировать compaction;
  • проектировать partition key;
  • предотвращать oversized partitions;
  • выполнять repair;
  • управлять consistency level,

то архитектурное преимущество превращается в инфраструктурную ловушку.


14. Цена миграции и vendor lock-in

Нужно оценить не только цену запуска, но и цену выхода.

Вопросы:

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

Иногда полностью managed-база с vendor lock-in рациональна. Но это должно быть осознанное решение, а не обнаружение через три года, когда миграция уже напоминает пересадку нервной системы.


15. Матрица выбора

Требование Вероятный кандидат
Транзакционные бизнес-данные PostgreSQL, MySQL
Сложные связи и JOIN PostgreSQL
Гибкие вложенные документы MongoDB
Кеш и сессии Redis
Высокий поток распределённых записей Cassandra, ScyllaDB
Полнотекстовый поиск OpenSearch, Elasticsearch
Метрики и временные ряды TimescaleDB, VictoriaMetrics, InfluxDB
Аналитика по большим объёмам ClickHouse, BigQuery, Snowflake
Граф отношений Neo4j, Neptune
Файлы и бинарные объекты S3, MinIO
Простые запросы по ключу при огромном масштабе DynamoDB
Локальная встроенная база SQLite

Эта таблица не выбирает базу автоматически. Она только определяет пространство кандидатов.


16. Практический алгоритм выбора

Шаг 1. Описать бизнес-инварианты

Пример:

  • одно место нельзя продать дважды;
  • платёж должен быть связан с заказом;
  • подтверждённое бронирование нельзя потерять;
  • email пользователя должен быть уникальным.

Шаг 2. Выписать основные запросы

Не абстрактно «получение данных», а конкретно:

Получить бронирования пользователя за последние шесть месяцев.

Найти свободные номера определённого типа на интервал дат.

Создать бронирование, если доступность больше нуля.

Найти отели в радиусе пяти километров.

Получить выручку по странам за сутки.

Шаг 3. Оценить масштаб

  • RPS;
  • writes per second;
  • read/write ratio;
  • объём данных;
  • размер записей;
  • рост;
  • пики;
  • горячие ключи.

Шаг 4. Определить консистентность

Для каждого запроса отдельно:

  • strict;
  • read-your-writes;
  • bounded staleness;
  • eventual;
  • approximate.

Шаг 5. Определить модель отказа

  • один дата-центр;
  • несколько availability zones;
  • несколько регионов;
  • допустимый RPO;
  • допустимый RTO;
  • поведение при network partition.

Шаг 6. Выбрать минимально сложную технологию

Не выбирать распределённую базу, пока обычная база решает задачу.

Шаг 7. Провести proof of concept

Проверить не демонстрационный сценарий, а худшие места:

  • конкурентные записи;
  • горячие ключи;
  • тяжёлые запросы;
  • failover;
  • восстановление;
  • рост объёма;
  • миграции;
  • перестроение индексов;
  • деградацию при заполнении диска.

Шаг 8. Зафиксировать решение в ADR

Например:

Context

Сервис бронирования должен предотвращать overbooking.
Ожидается 2 000 записей в секунду на пике.
Требуются транзакции над бронированием, доступностью и outbox.
Основные запросы используют фильтрацию по hotel_id, room_type_id и date.

Decision

Использовать PostgreSQL как source of truth.
Использовать Redis только для кеширования результатов поиска.
Использовать OpenSearch как асинхронно обновляемый поисковый индекс.

Consequences

Потребуются партиционирование inventory по датам,
read replicas для части запросов,
Transactional Outbox для обновления поискового индекса.
Redis и OpenSearch не участвуют в подтверждении бронирования.

17. Как отвечать на собеседовании

Хороший ответ начинается не с названия технологии.

Можно сказать:

Сначала я определю бизнес-инварианты, паттерны чтения и записи, требования к транзакциям, консистентности, объёму и доступности. Если это транзакционная система бронирования, где нужно предотвращать двойную продажу и выполнять условные конкурентные обновления, я начну с PostgreSQL как source of truth. Затем отдельно рассмотрю Redis для кеша, OpenSearch для поиска и ClickHouse для аналитики. Переход к Cassandra, DynamoDB или шардингу должен быть вызван конкретным пределом по нагрузке, объёму, географии или доступности, а не абстрактным требованием «система должна масштабироваться».

Если интервьюер спрашивает:

SQL или NoSQL?

Можно ответить:

Это недостаточно конкретное разделение. Мне нужно понять, какие запросы выполняются, нужны ли транзакции между сущностями, допустима ли eventual consistency, каков объём записи, есть ли горячие ключи и как система должна вести себя при сетевом разделении.


18. Типичные ошибки

«Берём NoSQL, потому что данные будут расти»

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

«MongoDB не требует схемы»

Схема существует в данных и коде, даже если база её строго не валидирует.

«Redis быстрый, поэтому храним всё в Redis»

Скорость не отменяет стоимость памяти, eviction, модель персистентности и ограничения по размеру данных.

«Cassandra масштабируется горизонтально, значит лучше PostgreSQL»

Cassandra лучше только для определённых моделей нагрузки и запросов. Цена заключается в модели данных, денормализации и сложности эксплуатации.

«Elasticsearch можно использовать как основную базу»

Для поиска это мощный инструмент. Для строгих транзакционных инвариантов обычно нет.

«Нужен шардинг с первого дня»

Шардинг резко усложняет:

  • транзакции;
  • JOIN;
  • уникальность;
  • миграции;
  • ребалансировку;
  • операции;
  • диагностику.

Иногда шардинг нужен. Но ранний шардинг часто решает воображаемую проблему и создаёт реальные.

«Одна база на всё»

Одна база упрощает эксплуатацию, и это хорошо. Но нельзя заставлять PostgreSQL быть одновременно CDN, поисковым движком, хранилищем видео и системой аналитики на петабайтах.

«Отдельная база для каждого микросервиса автоматически»

Изоляция владения данными полезна. Но физическое разделение создаёт распределённые транзакции, eventual consistency и сложность объединения данных. Границы должны следовать бизнес-владению, а не количеству сервисов на диаграмме.


19. Сжатая формула

Выбор базы данных можно выразить так:

Database choice =
    data model
  + invariants
  + access patterns
  + consistency
  + scale
  + failure model
  + operational capacity
  + cost of evolution

И главный практический принцип:

Выбирать нужно не самую мощную базу, а наименее сложную базу, которая надёжно выполняет требования системы и оставляет понятный путь роста.

Для большинства обычных транзакционных продуктов исходная конструкция будет такой:

PostgreSQL     source of truth
Redis          cache / ephemeral state
OpenSearch     search projection
Kafka          event transport
ClickHouse     analytics
S3             files

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

Прокрутить вверх