Skip to content

Базы данных и хранение данных

Типы баз данных

Выбор базы данных — одно из ключевых архитектурных решений при проектировании системы. Каждый тип оптимален для определённых сценариев.

┌──────────────────────────────────────────────────────────┐
│              ТИПЫ БАЗ ДАННЫХ                              │
├──────────────────────────────────────────────────────────┤
│                                                          │
│  Реляционные (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 — Нет транзитивных зависимостей (неключевые
      атрибуты зависят только от первичного ключа)

Индексы

sql
-- 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-подобных документах. Гибкая схема.

javascript
// Пример документа в 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

Решает проблему перераспределения данных при добавлении/удалении узлов.



    ┌────●────────────┐
    │    ↑ 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 + RedisACID для заказов + кеш
Социальная сетьPostgreSQL + Neo4j + RedisПрофили + граф связей + кеш
Чат-мессенджерCassandra + RedisВысокая запись + real-time
АналитикаClickHouse / BigQueryColumnar storage для агрегаций
ПоискElasticsearchFull-text search + фильтрация
IoT данныеInfluxDB / TimescaleDBTime-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-теорема: выбирайте два из трёх                  │
└─────────────────────────────────────────────────────────┘