Как се избира база
Накратко: Базата се избира според данните и въпросите към тях: релационната база пази истината, а друг вид се добавя до нея само за въпроса, в който тя е слаба.
Проблем
Много видове бази на масата
Модулът показа key-value, документи, потоци от събития, графи, колони и времеви редове, търсене в текст, вектори и клъстери. Всяка от тези бази е отлична за нещо. Новият проект на магазина пита: коя да изберем?
Грешният начин е да тръгнеш от базата („ползваме MongoDB, защото е модерно“). Правилният е да тръгнеш от данните и от въпросите, които ще им задаваш, и да добавиш нова база чак когато има въпрос, на който основната не отговаря добре.
Идеята
Пет въпроса
- Каква е формата на данните? Редове с еднакви колони, цели документи с вложени части, връзки между нещата, записи по време, свободен текст.
- Какво питаш? Едно нещо по ключ, отчет върху всичко, „кой с кого е свързан“, „кои думи“, „кое е подобно“.
- Колко е важна консистентността? Пари и наличност искат транзакции и четене на последния запис. Брояч на харесвания може да закъснее с секунда.
- Колко са данните и натоварването? Хиляди или милиарди редове, десет или сто хиляди заявки в секунда. Повечето проекти са много по-малки, отколкото си мислят.
- Кой ще я поддържа? Всяка база иска копия, наблюдение, обновявания и човек, който знае какво прави, когато тя падне.
| Въпросът | Вид база |
|---|---|
| Пари, поръчки, наличност, всичко, което трябва да е вярно | релационна (PostgreSQL, MySQL) |
| Едно и също по ключ хиляди пъти, сесии, колички | key-value (Redis) |
| Цели обекти с различна структура | документна (MongoDB) или JSONB |
| Много системи научават за едно и също събитие | поток от събития (Kafka) |
| Препоръки, мрежи, измами през няколко стъпки | графова (Neo4j) |
| Отчети върху милиони редове, сензори, метрики | колонна, времеви редове (ClickHouse, TimescaleDB) |
| Поле за търсене, думи в текст | пълнотекстово търсене (Elasticsearch) |
| „Подобно по смисъл“, чатбот върху документи | векторна (pgvector, Qdrant) |
Идеята
Започни с PostgreSQL
За повечето нови проекти разумното начало е една релационна база. PostgreSQL сама покрива голяма част от модула: JSONB колони за документи, tsvector за търсене в текст, pgvector за вектори, TimescaleDB за времеви редове, реплики за четене и разделяне на таблици по време. Не е най-добрата във всяко от тях, но е достатъчно добра, а една база е много по-лесна за поддръжка от пет.
Отделна база си струва, когато има конкретен въпрос, на който основната отговаря бавно или трудно, и си го измерил: кешът, защото една и съща заявка се пуска 2000 пъти в минута; търсачката, защото търсенето е главната работа на сайта; складът за данни, защото отчетите забавят касата.
По-точно, за любопитните
Това се казва polyglot persistence: различни бази за различни части на системата. Работи добре, когато една от тях е ясно източникът на истината, а другите са копия, които могат да се направят наново. Работи лошо, когато две бази държат различни части от една и съща истина и никой не знае коя е вярната.
Виж го
Магазинът, когато порасне
Така може да изглежда магазинът след няколко години, като всяка част е дошла, когато е потрябвала:
- PostgreSQL пази клиентите, продуктите, поръчките и плащанията. Това е истината. Има една синхронна и една асинхронна реплика.
- Redis пази кеша на каталога, сесиите и количките.
- Kafka носи събитията „поръчката е направена“, „платена“, „изпратена“ към склада, фактурите и пощата.
- Elasticsearch държи копие на продуктите и отзивите за полето за търсене. Пълни се от събитията.
- ClickHouse е складът за данни за отчетите. Пълни се всяка нощ или от потока.
- pgvector в същата PostgreSQL дава „подобни продукти“.
Всички копия идват от PostgreSQL. Ако индексът в Elasticsearch се повреди, той се прави наново от базата. Обратното не е вярно.
Проблем
Двата записа
Поръчката се записва в PostgreSQL, а после програмата праща събитие в Kafka. Ако програмата падне между двете, поръчката е в базата, но събитието го няма: складът никога не приготвя пакета. Ако е обратното (първо събитието, после базата), складът може да приготви пакет за поръчка, която после се е провалила.
Двете системи нямат обща транзакция. Решението е outbox таблица: събитието се записва в таблица в същата база, в същата транзакция като поръчката. Или се записват и двете, или нито едно.
published_at е NULL: събитието още не е пратено. Отделна програма (relay) чете непратените подред, праща ги в потока и чак после ги отбелязва:
А ако нещо се провали преди COMMIT, няма нито промяна, нито събитие:
По-точно, за любопитните
Програмата за препращане може да падне, след като е пратила събитието, но преди да го отбележи. Тогава при рестарта го праща пак. Затова потребителите на събитието трябва да са идемпотентни, като фактурите в урока за потоците. Вместо програма, която чете таблицата, често се ползва Debezium: той чете дневника на промените на PostgreSQL (WAL) и праща всеки нов ред от outbox в Kafka. Това се казва change data capture (CDC).
Пробвай
Отказ на поръчка
Клиентът отказва поръчка 42. В една транзакция смени статуса ѝ на cancelled и запиши в outbox събитие за темата orders с ключ order-42 и съдържание {"order":42,"event":"cancelled"}.
Решение
BEGIN;
UPDATE orders SET status = 'cancelled' WHERE id = 42;
INSERT INTO outbox (topic, key, payload)
VALUES ('orders', 'order-42', '{"order":42,"event":"cancelled"}');
COMMIT;BEGIN, UPDATE orders SET status = 'cancelled' WHERE id = 42, INSERT INTO outbox (topic, key, payload) VALUES ('orders', 'order-42', '…') и COMMIT. Само UPDATE сменя статуса, но складът и пощата никога не научават. Ключът е номерът на поръчката, за да отиде отказът в същия дял като „placed“ и „paid“ и да се прочете след тях.
Упражнение
Пет проекта
За всеки проект: коя база е основата или какво добавяш до нея?
Тест
Провери се
Свързани уроци
Раздел „Свързани уроци“- Нужно отпреди: Колонни и времеви бази · Търсене в текст · Търсене по смисъл · Репликация, шардинг и CAP
- Задачи по темата: Видове бази данни