← Назад к вопросам

Что такое шардирование?

1.7 Middle🔥 171 комментариев
#SQL и базы данных#Архитектура и проектирование

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Шардирование: Горизонтальное Масштабирование Базы Данных

Шардирование (Sharding) — это техника горизонтального масштабирования, при которой данные распределяются между несколькими независимыми базами данных (шардами) на основе значения ключа шардирования. Каждый шард содержит подмножество данных и может работать независимо на отдельном сервере.

Основной Принцип

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

ОД большая БД:           Шардированная система:
┌──────────────┐         ┌──────────┐  ┌──────────┐  ┌──────────┐
│  1M records  │   →     │  Shard 0 │  │  Shard 1 │  │  Shard 2 │
│   (slow)     │         │ 333k rec │  │ 333k rec │  │ 333k rec │
└──────────────┘         └──────────┘  └──────────┘  └──────────┘

Стратегии Шардирования

1. Range-Based (Диапазонное) Шардирование

def get_shard_by_range(user_id, num_shards=4):
    """
    Разделяет пользователей по диапазонам ID
    """
    # User ID 1-250k → Shard 0
    # User ID 250k-500k → Shard 1
    # User ID 500k-750k → Shard 2
    # User ID 750k-1M → Shard 3
    
    range_size = 1_000_000 // num_shards
    shard_id = user_id // range_size
    return min(shard_id, num_shards - 1)

print(get_shard_by_range(100_000))   # Shard 0
print(get_shard_by_range(400_000))   # Shard 1
print(get_shard_by_range(800_000))   # Shard 3

Преимущества: Просто реализовать, легко находить диапазон Недостатки: Может быть несбалансированное распределение если ID неполные

2. Hash-Based (Хеш) Шардирование

import hashlib

def get_shard_by_hash(user_id, num_shards=4):
    """
    Использует хеш для равномерного распределения
    """
    hash_value = int(hashlib.md5(str(user_id).encode()).hexdigest(), 16)
    return hash_value % num_shards

print(get_shard_by_hash("user-001"))  # Shard 2
print(get_shard_by_hash("user-002"))  # Shard 0
print(get_shard_by_hash("user-003"))  # Shard 3

Преимущества: Хорошее распределение, масштабируемость Недостатки: Сложнее обработать добавление новых шардов (rehashing)

3. Directory-Based (На основе таблицы маршрутизации)

# Таблица маршрутизации
SHARD_MAPPING = {
    'user-001': 0,
    'user-002': 1,
    'user-003': 2,
    'user-004': 3,
    # ...
}

def get_shard_directory(user_id):
    return SHARD_MAPPING.get(user_id)

Преимущества: Максимальная гибкость, легко переехать данные Недостатки: Требует отдельное хранилище маршрутизации

Реализация Шардирования

На Уровне Приложения (Application-Level Sharding)

from sqlalchemy import create_engine

class ShardedDatabase:
    def __init__(self, shard_configs):
        # shard_configs = {
        #     0: 'postgresql://localhost/db_shard_0',
        #     1: 'postgresql://localhost/db_shard_1',
        #     2: 'postgresql://localhost/db_shard_2',
        # }
        self.engines = {
            shard_id: create_engine(config)
            for shard_id, config in shard_configs.items()
        }
    
    def _get_shard_id(self, user_id):
        return hash(user_id) % len(self.engines)
    
    def get_user(self, user_id):
        shard_id = self._get_shard_id(user_id)
        engine = self.engines[shard_id]
        
        with engine.connect() as conn:
            result = conn.execute(
                f"SELECT * FROM users WHERE id = {user_id}"
            )
            return result.fetchone()
    
    def create_user(self, user_id, name, email):
        shard_id = self._get_shard_id(user_id)
        engine = self.engines[shard_id]
        
        with engine.connect() as conn:
            conn.execute(
                f"INSERT INTO users (id, name, email) VALUES ({user_id}, '{name}', '{email}')"
            )
            conn.commit()

# Использование
db_shards = {
    0: 'postgresql://localhost/db_shard_0',
    1: 'postgresql://localhost/db_shard_1',
    2: 'postgresql://localhost/db_shard_2',
}
db = ShardedDatabase(db_shards)

db.create_user('user-001', 'Alice', 'alice@example.com')  # → Shard 0
db.get_user('user-001')  # Обращение к Shard 0

На Уровне БД с Отдельными Таблицами

-- Создание шарда 0
CREATE TABLE users_shard_0 (
    user_id UUID PRIMARY KEY,
    name VARCHAR(255),
    email VARCHAR(255),
    created_at TIMESTAMP
);

-- Создание шарда 1
CREATE TABLE users_shard_1 (
    user_id UUID PRIMARY KEY,
    name VARCHAR(255),
    email VARCHAR(255),
    created_at TIMESTAMP
);

-- Представление для прозрачного доступа
CREATE VIEW users AS
SELECT * FROM users_shard_0
UNION ALL
SELECT * FROM users_shard_1;

Распределённые Системы Шардирования

Для Production используют специализированные решения:

  1. MySQL Proxy / Vitess — Разработан Google, используется в large-scale systems
# Vitess автоматически маршрутизирует запросы
VTGATE_HOST = 'vitess-gateway.example.com'
connection = mysql.connector.connect(
    host=VTGATE_HOST,
    user='root',
    database='keyspace:shard'
)
  1. PostgreSQL с Citus — Native шардирование в PostgreSQL
SELECT * from create_distributed_table('users', 'user_id');
  1. MongoDB Sharding — Встроенное шардирование
sh.shardCollection("mydb.users", { "user_id": "hashed" })

Проблемы и Решения

Проблема: Горячие Шарды (Hot Shards)

Если один шард получает много больше нагрузки:

# Решение: Compound Shard Key
def get_shard_compound(user_id, timestamp):
    # Вместо только user_id используем комбинацию
    key = f"{user_id}_{timestamp}"
    return hash(key) % num_shards

Проблема: JOIN Между Шардами

def cross_shard_query(user_ids):
    """
    Запрос к нескольким шардам одновременно
    """
    results = {}
    shard_map = {}
    
    # Группируем user_ids по шардам
    for user_id in user_ids:
        shard_id = get_shard_id(user_id)
        if shard_id not in shard_map:
            shard_map[shard_id] = []
        shard_map[shard_id].append(user_id)
    
    # Запрашиваем каждый шард отдельно
    for shard_id, ids in shard_map.items():
        engine = self.engines[shard_id]
        result = engine.execute(f"SELECT * FROM users WHERE id IN {tuple(ids)}")
        results.update(result)
    
    return results

Проблема: Resharding (Добавление нового шарда)

Это сложная операция, требует переезда данных:

def add_new_shard():
    # 1. Создать новый шард
    # 2. Скопировать данные из существующих шардов
    # 3. Обновить функцию хеширования
    # 4. Постепенно переводить трафик на новый шард
    # 5. Удалить старые данные после валидации
    pass

Когда Использовать Шардирование

✅ База данных > 500 GB ✅ > 10,000 QPS ✅ Данные неограниченно растут ✅ Одна таблица > 1 млрд строк ✅ Нужно масштабировать writes

Когда НЕ Использовать

❌ База < 100 GB ❌ Нужны частые cross-shard операции ❌ Много аналитических запросов ❌ Можно использовать read replicas

Альтернативы

  • Vertical Scaling — Больше памяти/CPU для одного сервера
  • Read Replicas — Масштабирование чтения
  • Caching Layer — Redis, Memcached
  • Data Warehouse — Snowflake, BigQuery для аналитики

Шардирование — это последний шаг оптимизации, когда другие методы исчерпаны. Используй только когда действительно нужно.