Где уместнее использовать шардирование БД?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Где уместнее использовать шардирование БД
Шардирование (sharding) — это техника горизонтального масштабирования базы данных. Это сложная тема, поэтому разберу её с учётом реальных сценариев и trade-off.
Что такое шардирование
Шардирование — это разделение данных таблицы по нескольким физическим базам данных (или серверам) так, чтобы каждый сервер хранил только подмножество данных.
Без шардирования:
┌─────────────────────────────────┐
│ PostgreSQL (1 сервер) │
│ users: 1 млн записей │
│ orders: 10 млн записей │
└─────────────────────────────────┘
С шардированием (по user_id):
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ Shard 1 │ │ Shard 2 │ │ Shard 3 │
│ users 1-333k │ │ users 334k-666k │ │ users 667k-1M │
│ orders (их) │ │ orders (их) │ │ orders (их) │
└──────────────────┘ └──────────────────┘ └──────────────────┘
Стратегии шардирования
1. Range-based (диапазонное)
def get_shard_id(user_id):
# Распределяем по диапазонам
if user_id < 334_000:
return 1
elif user_id < 667_000:
return 2
else:
return 3
Минусы: неравномерное распределение нагрузки (hot spot)
2. Hash-based (хеширование)
def get_shard_id(user_id, num_shards=3):
return hash(user_id) % num_shards
Плюс: равномерное распределение. Минус: перешардирование при добавлении шардов
3. Directory-based (справочник)
# Отдельная таблица mapping
# user_id → shard_id
shard_id = shard_directory.lookup(user_id)
Плюс: гибкость. Минус: extra hop для каждого запроса
Когда использовать шардирование
ИСПОЛЬЗУЙ ШАРДИРОВАНИЕ, ЕСЛИ:
1. Размер данных превышает возможности одного сервера
Реальные примеры:
- Twitter: миллиарды твитов
- Facebook: петабайты пользовательских данных
- Uber: миллионы поездок в день
Примерно: >100 ГБ активных данных, растущих быстро
2. Нагрузка на один сервер неприемлема
# Пример нагрузки
requests_per_second = 50_000
db_connections_per_request = 1
total_connections_needed = requests_per_second * db_connections_per_request
# PostgreSQL обычно держит max 200-500 соединений комфортно
# Если нужно 50k коннектов — нужно шардирование
3. Необходимо горизонтальное масштабирование
Вертикальное масштабирование имеет потолок:
- Самый мощный сервер имеет конечные ресурсы
- Очень дорого
- Требует простоя
Шардирование = горизонтальное масштабирование
- Добавляем новые серверы
- Масштабируется линейно
НЕ ИСПОЛЬЗУЙ ШАРДИРОВАНИЕ, ЕСЛИ:
1. Данные легко помещаются на один сервер
Хорошие кандидаты на single database:
- Стартап < 10 млн записей
- SaaS внутри компании
- B2B сервис с ограниченным числом клиентов
- Проект < 1 млн DAU
2. Большинство запросов требуют данных из всех шардов
# Плохо для шардирования:
SELECT COUNT(*) FROM orders; # Нужно запросить все 3 шарда
SELECT * FROM orders WHERE created_at > '2024-01-01'; # Span query
# Хорошо для шардирования:
SELECT * FROM orders WHERE user_id = 123; # Один шард
SELECT * FROM users WHERE id = 456; # Один шард
3. Требуются JOIN'ы между таблицами на разных шардах
# Проблема: user_id из shard 1, order_id из shard 3
SELECT u.name, o.amount
FROM users u
JOIN orders o ON u.id = o.user_id
WHERE u.id = 123 AND o.id = 456;
# JOIN работает, только если оба на одном шарде
# Иначе нужна app-level логика
Альтернативы шардированию
Перед тем, как выбрать шардирование, рассмотри:
1. Вертикальное масштабирование
- Больше CPU/RAM для сервера
- Проще в управлении
- Дешевле на начальном этапе
2. Репликация (Replication)
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Primary DB │───▶│ Replica 1 │───▶│ Replica 2 │
│ (write) │ │ (read) │ │ (read) │
└──────────────┘ └──────────────┘ └──────────────┘
Масштабируем только читает, пишет в Primary
3. Кеширование (Caching)
# Redis cache
@cache.cached(timeout=3600)
def get_user(user_id):
return db.query(User).filter(User.id == user_id).first()
Снижает нагрузку на БД на 70-90% в типичных сценариях.
4. Денормализация и документные БД
# Вместо JOIN'ов - денормализованные документы
# MongoDB: user_id + user_data + orders в одном документе
{
"user_id": 123,
"name": "Alice",
"orders": [
{"id": 1, "amount": 100},
{"id": 2, "amount": 200}
]
}
Практический пример: Uber-like сервис
# Uber шардирует по city_id + vehicle_id
# Потому что:
# 1. Размер данных огромный (млн поездок в день)
# 2. Большинство запросов: "покажи поездки в моём городе"
# 3. Горячие данные (active rides) относительно небольшие
def get_shard(city_id, vehicle_type):
shards_per_city = {
'moscow': [1, 2, 3, 4],
'spb': [5, 6, 7, 8],
'nyc': [9, 10, 11, 12],
}
city_shards = shards_per_city[city_id]
return city_shards[hash(vehicle_type) % len(city_shards)]
# Большинство запросов:
# SELECT * FROM rides WHERE city_id = 'moscow' AND vehicle_id = 456
# -> Обращаемся к одному шарду
Инструменты для шардирования
- Citus (PostgreSQL) — встроенное шардирование в PostgreSQL
- Vitess (MySQL) — middleware для шардирования MySQL
- Django-sharding — для приложений на Django
- MongoDB — встроенное шардирование
Вывод: процесс принятия решения
- Стартуем с single database — достаточно для 99% проектов
- Добавляем репликацию — когда нагрузка читает растёт
- Добавляем кеш — Redis/Memcached для горячих данных
- Шардируем — только если предыдущие меры исчерпаны
Шардирование — это последний шаг в масштабировании, потому что оно добавляет огромную сложность в операционном плане.