Базы данных и хранение данных
Типы баз данных
Выбор базы данных — одно из ключевых архитектурных решений при проектировании системы. Каждый тип оптимален для определённых сценариев.
┌──────────────────────────────────────────────────────────┐
│ ТИПЫ БАЗ ДАННЫХ │
├──────────────────────────────────────────────────────────┤
│ │
│ Реляционные (SQL) │ Нереляционные (NoSQL) │
│ ───────────────── │ ────────────────────── │
│ PostgreSQL │ Document: MongoDB │
│ MySQL │ Key-Value: Redis │
│ Oracle │ Column: Cassandra │
│ SQL Server │ Graph: Neo4j │
│ │ Time Series: InfluxDB │
│ │ Search: Elasticsearch │
└──────────────────────────────────────────────────────────┘Реляционные базы данных (SQL)
Когда использовать
- Структурированные данные с чёткой схемой
- Сложные связи между сущностями
- Транзакционная целостность (ACID)
- Финансовые системы, ERP, CRM
ACID-свойства
┌─────────────────────────────────────────────────────────┐
│ A — Atomicity (Атомарность) │
│ Транзакция выполняется полностью или не │
│ выполняется вообще │
├─────────────────────────────────────────────────────────┤
│ C — Consistency (Согласованность) │
│ БД переходит из одного валидного состояния │
│ в другое │
├─────────────────────────────────────────────────────────┤
│ I — Isolation (Изоляция) │
│ Параллельные транзакции не влияют друг на друга │
├─────────────────────────────────────────────────────────┤
│ D — Durability (Долговечность) │
│ Закоммиченные данные сохраняются даже │
│ при сбое системы │
└─────────────────────────────────────────────────────────┘Нормализация
1NF — Атомарные значения, нет повторяющихся групп
┌────────┬──────────┐
│ user_id│ email │ ✓ Одно значение в ячейке
├────────┼──────────┤
│ 1 │ a@b.com │
│ 2 │ c@d.com │
└────────┴──────────┘
2NF — Нет частичных зависимостей (каждый неключевой
атрибут зависит от всего составного ключа)
3NF — Нет транзитивных зависимостей (неключевые
атрибуты зависят только от первичного ключа)Индексы
-- B-Tree индекс (по умолчанию) — для точного поиска и диапазонов
CREATE INDEX idx_users_email ON users(email);
-- Составной индекс — порядок колонок важен!
CREATE INDEX idx_orders_user_date ON orders(user_id, created_at);
-- Частичный индекс — только для подмножества данных
CREATE INDEX idx_active_users ON users(email) WHERE is_active = true;
-- Покрывающий индекс — все нужные данные в индексе
CREATE INDEX idx_cover ON orders(user_id, status, total);
-- SELECT status, total FROM orders WHERE user_id = 1
-- → Index Only Scan (не обращается к таблице)Цена индексов
- Замедляют операции записи (INSERT, UPDATE, DELETE)
- Занимают дополнительное дисковое пространство
- Создавайте только для часто используемых запросов
NoSQL базы данных
Document Store (MongoDB)
Хранят данные в JSON-подобных документах. Гибкая схема.
// Пример документа в MongoDB
{
_id: ObjectId("64a7b2c3d4e5f6a7b8c9d0e1"),
username: "john_doe",
email: "john@example.com",
profile: {
age: 30,
interests: ["coding", "gaming"],
address: {
city: "Москва",
country: "Россия"
}
},
orders: [
{ product: "Laptop", price: 1200, date: "2024-01-15" },
{ product: "Mouse", price: 25, date: "2024-02-01" }
]
}Когда использовать:
- Каталоги продуктов (разные атрибуты)
- Системы управления контентом (CMS)
- Пользовательские профили
- Быстрое прототипирование
Key-Value Store (Redis)
Простейшая модель: ключ → значение. Очень быстрый доступ.
┌────────────────────┬───────────────────────┐
│ Ключ │ Значение │
├────────────────────┼───────────────────────┤
│ user:1001 │ {"name":"John",...} │
│ session:abc123 │ {"userId":1001,...} │
│ cache:product:42 │ {"title":"Phone",...} │
│ counter:views:42 │ 15832 │
│ rate:ip:192.168.1 │ 47 │
└────────────────────┴───────────────────────┘Когда использовать:
- Кеширование
- Управление сессиями
- Очереди задач
- Счётчики и rate limiting
- Pub/Sub уведомления
Column-Family Store (Cassandra)
Оптимизированы для записи и чтения больших объёмов данных.
Row Key: user:1001
┌──────────────┬──────────────────────────────────────┐
│ Column Family│ Columns │
├──────────────┼──────────────────────────────────────┤
│ profile │ name:"John" | age:30 | city:"Moscow" │
│ activity │ 2024-01-15:"login" | 2024-01-16:"buy"│
│ preferences │ theme:"dark" | lang:"ru" │
└──────────────┴──────────────────────────────────────┘Когда использовать:
- Временные ряды (IoT, логи)
- Высокая пропускная способность записи
- Географическая репликация
- Системы рекомендаций
Graph Database (Neo4j)
Хранят узлы и связи между ними. Оптимальны для данных со сложными связями.
┌──────────┐ FOLLOWS ┌──────────┐
│ Alice │──────────────→│ Bob │
└──────────┘ └──────────┘
│ │
│ LIKES │ POSTED
↓ ↓
┌──────────┐ TAGGED ┌──────────┐
│ Post#1 │←──────────────│ Post#2 │
└──────────┘ └──────────┘Когда использовать:
- Социальные сети (друзья друзей)
- Графы знаний
- Системы рекомендаций
- Обнаружение мошенничества
SQL vs NoSQL: как выбрать
┌──────────────────┬──────────────────┬──────────────────┐
│ Критерий │ SQL │ NoSQL │
├──────────────────┼──────────────────┼──────────────────┤
│ Схема данных │ Фиксированная │ Гибкая │
│ Масштабирование │ Вертикальное │ Горизонтальное │
│ Транзакции │ ACID │ BASE (обычно) │
│ Связи │ Сложные JOIN-ы │ Денормализация │
│ Запросы │ SQL (мощный) │ Зависит от БД │
│ Согласованность │ Строгая │ Eventual │
│ Производительн. │ Средняя │ Высокая (чтение) │
└──────────────────┴──────────────────┴──────────────────┘Правило выбора
- Есть сложные связи и нужна согласованность? → SQL
- Нужна гибкая схема и горизонтальное масштабирование? → NoSQL
- Часто обе технологии используются вместе (polyglot persistence)
CAP-теорема
В распределённой системе невозможно одновременно гарантировать все три свойства:
Consistency
(Согласованность)
/\
/ \
/ \
/ CA \ ← MySQL, PostgreSQL
/ (не \ (single node)
/ распредел.)\
/──────────────\
/ \
/ CP AP \
/ Согласованн. Доступн.\
/ + Разделение + Разделение
/ MongoDB(*) Cassandra \
/ HBase DynamoDB \
/──────────────────────────────\
Partition Tolerance (Устойчивость к разделению)BASE vs ACID
ACID (SQL): BASE (NoSQL):
───────────── ──────────────
Atomicity Basically Available
Consistency Soft state
Isolation Eventually consistent
DurabilityРепликация данных
Master-Slave (Primary-Replica)
┌──────────────┐
│ MASTER │ ← Запись (INSERT/UPDATE/DELETE)
│ (Primary) │
└──────┬───────┘
│ Репликация
┌────────┼────────┐
↓ ↓ ↓
┌────────┐ ┌────────┐ ┌────────┐
│ SLAVE1 │ │ SLAVE2 │ │ SLAVE3 │ ← Чтение (SELECT)
│(Replica)│ │(Replica)│ │(Replica)│
└────────┘ └────────┘ └────────┘Преимущества: простота, масштабирование чтения Недостатки: задержка репликации, единая точка отказа (master)
Master-Master (Multi-Primary)
┌──────────┐ ┌──────────┐
│ MASTER 1 │ ←─────→ │ MASTER 2 │
│ (R + W) │ Реплик. │ (R + W) │
└──────────┘ └──────────┘Преимущества: высокая доступность, запись в любой узел Недостатки: конфликты записи, сложность
Шардирование (Partitioning)
Разделение данных между несколькими серверами для горизонтального масштабирования.
Стратегии шардирования
1. По диапазону ключей (Range-based):
Шард 1: user_id 1-1M
Шард 2: user_id 1M-2M
Шард 3: user_id 2M-3M
⚠️ Риск: неравномерное распределение (hotspot)
2. По хешу (Hash-based):
shard = hash(user_id) % num_shards
✓ Равномерное распределение
⚠️ Сложно добавить новые шарды
3. По географии:
Шард EU: Европейские пользователи
Шард US: Американские пользователи
Шард ASIA: Азиатские пользователи
✓ Низкая задержка для пользователейConsistent Hashing
Решает проблему перераспределения данных при добавлении/удалении узлов.
0°
│
┌────●────────────┐
│ ↑ Node A │
│ │
│ ●→Node B│ 90°
│ │
│ │
│ ●→Node C│
│ │
└──────────────────┘
180°
Добавление Node D:
- Перемещается только ~1/N данных (не все!)Паттерны доступа к данным
Write-Ahead Log (WAL)
1. Запись в лог (диск) → Гарантия durability
2. Подтверждение клиенту
3. Применение к данным → Асинхронно
Восстановление после сбоя:
Log: [INSERT user 1] [UPDATE user 2] [DELETE user 3]
↑
Последняя операция
перед сбоем → повторитьEvent Sourcing
Традиционный подход: Event Sourcing:
────────────────── ───────────────
Хранит текущее состояние Хранит последовательность событий
users: events:
┌─────┬───────┬────────┐ ┌──────────────────────────────┐
│ id │ name │balance │ │ UserCreated {name:"John"} │
├─────┼───────┼────────┤ │ BalanceDeposited {+1000} │
│ 1 │ John │ 800 │ │ BalanceWithdrawn {-200} │
└─────┴───────┴────────┘ │ → Текущий баланс: 800 │
└──────────────────────────────┘CQRS (Command Query Responsibility Segregation)
┌─────────────┐
Команды → │ Write Model │ → Event Store
(INSERT, │ (нормализ.) │
UPDATE) └─────────────┘
│
Events │
↓
┌─────────────┐
Запросы ← │ Read Model │ ← Проекции
(SELECT) │ (денормализ.)│
└─────────────┘Выбор БД для типичных задач
| Задача | Рекомендуемая БД | Обоснование |
|---|---|---|
| Интернет-магазин | PostgreSQL + Redis | ACID для заказов + кеш |
| Социальная сеть | PostgreSQL + Neo4j + Redis | Профили + граф связей + кеш |
| Чат-мессенджер | Cassandra + Redis | Высокая запись + real-time |
| Аналитика | ClickHouse / BigQuery | Columnar storage для агрегаций |
| Поиск | Elasticsearch | Full-text search + фильтрация |
| IoT данные | InfluxDB / TimescaleDB | Time-series оптимизация |
| Игровой сервер | Redis + MongoDB | Быстрый доступ + гибкая схема |
| Финтех | PostgreSQL | Строгий ACID, аудит |
Вопросы на собеседовании
1. Как бы вы спроектировали схему БД для Twitter?
Основные таблицы:
- users (id, username, email, created_at)
- tweets (id, user_id, content, created_at)
- follows (follower_id, following_id)
- likes (user_id, tweet_id)
Индексы:
- tweets: (user_id, created_at DESC) — лента пользователя
- follows: (follower_id), (following_id) — подписки
- likes: (tweet_id), (user_id, tweet_id) — уникальность
Денормализация для ленты:
- fan-out on write: при создании твита → записать в ленту каждого подписчика
- fan-out on read: при запросе ленты → собрать из подписок2. Как масштабировать БД с 1M до 100M пользователей?
Этап 1 (1M пользователей):
→ Одна PostgreSQL + Read replicas + Redis кеш
Этап 2 (10M пользователей):
→ Вертикальное масштабирование (больше CPU/RAM)
→ Больше read replicas
→ Вынести горячие данные в Redis
Этап 3 (100M пользователей):
→ Шардирование по user_id
→ Отдельные БД для разных сервисов
→ Cassandra для write-heavy данных
→ Elasticsearch для поиска3. Объясните разницу между оптимистичной и пессимистичной блокировкой
Пессимистичная блокировка:
─────────────────────────
SELECT * FROM products WHERE id = 1 FOR UPDATE;
-- Строка заблокирована, другие ждут
UPDATE products SET stock = stock - 1 WHERE id = 1;
COMMIT;
✓ Гарантирует отсутствие конфликтов
✗ Может вызвать deadlock, снижает параллелизм
Оптимистичная блокировка:
────────────────────────
SELECT id, stock, version FROM products WHERE id = 1;
-- version = 5, stock = 10
UPDATE products
SET stock = 9, version = 6
WHERE id = 1 AND version = 5;
-- Если version изменился → конфликт → повторить
✓ Высокий параллелизм
✗ Повторные попытки при конфликтахРезюме
┌─────────────────────────────────────────────────────────┐
│ 1. Выбирайте БД исходя из паттернов доступа к данным │
│ 2. SQL — для связей и транзакций │
│ 3. NoSQL — для масштабируемости и гибкости │
│ 4. Используйте polyglot persistence │
│ 5. Репликация — для доступности и чтения │
│ 6. Шардирование — для масштабирования записи │
│ 7. Индексы ускоряют чтение, замедляют запись │
│ 8. CAP-теорема: выбирайте два из трёх │
└─────────────────────────────────────────────────────────┘