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

Что такое горизонтальный шардинг?

1.0 Junior🔥 191 комментариев
#DevOps и инфраструктура#Django

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

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

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

Горизонтальный шардинг

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

Основная идея

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

Оригинальная таблица Users (1 млрд записей)
│
├─ Shard 1 (user_id % 4 == 0): 250M записей
├─ Shard 2 (user_id % 4 == 1): 250M записей
├─ Shard 3 (user_id % 4 == 2): 250M записей
└─ Shard 4 (user_id % 4 == 3): 250M записей

Каждый шард:

  • Имеет тот же схему данных
  • Содержит разное подмножество строк
  • Может быть на отдельном сервере
  • Работает независимо от других

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

1. Range-based Sharding (диапазон значений)

class RangeBasedSharding:
    """Шардирование на основе диапазонов"""
    
    def __init__(self, shards_config):
        # shards_config: {(0, 1000): 'shard1', (1001, 2000): 'shard2', ...}
        self.shards_config = shards_config
    
    def get_shard(self, user_id: int) -> str:
        """Определить шард по user_id"""
        for (min_id, max_id), shard_name in self.shards_config.items():
            if min_id <= user_id <= max_id:
                return shard_name
        raise ValueError(f"No shard found for user_id {user_id}")

# Использование
config = {
    (0, 999999): 'shard_1',
    (1000000, 1999999): 'shard_2',
    (2000000, 2999999): 'shard_3',
    (3000000, 3999999): 'shard_4',
}

sharding = RangeBasedSharding(config)
print(sharding.get_shard(500000))  # shard_1
print(sharding.get_shard(1500000))  # shard_2

2. Hash-based Sharding (хеш функция)

class HashBasedSharding:
    """Шардирование на основе хеша"""
    
    def __init__(self, num_shards: int):
        self.num_shards = num_shards
    
    def get_shard(self, key: str) -> int:
        """Определить номер шарда по ключу"""
        hash_value = hash(key)
        return hash_value % self.num_shards
    
    def get_shard_name(self, key: str) -> str:
        shard_num = self.get_shard(key)
        return f"shard_{shard_num}"

# Использование
sharding = HashBasedSharding(num_shards=4)

users = ['user_1', 'user_2', 'user_3', 'user_4']
for user in users:
    shard = sharding.get_shard_name(user)
    print(f"{user} -> {shard}")

3. Directory-based Sharding (справочник)

class DirectoryBasedSharding:
    """Шардирование с использованием справочника"""
    
    def __init__(self):
        # Справочник: user_id -> shard_id
        self.shard_map = {}
        # Кэш для быстрого доступа
        self.cache = {}
    
    def add_mapping(self, user_id: int, shard_id: str):
        """Добавить маппинг user_id -> shard_id"""
        self.shard_map[user_id] = shard_id
        self.cache[user_id] = shard_id
    
    def get_shard(self, user_id: int) -> str:
        """Получить шард по user_id"""
        # Проверить кэш
        if user_id in self.cache:
            return self.cache[user_id]
        
        # Проверить справочник
        if user_id in self.shard_map:
            shard = self.shard_map[user_id]
            self.cache[user_id] = shard
            return shard
        
        raise ValueError(f"User {user_id} not found in directory")

# Использование
sharding = DirectoryBasedSharding()
sharding.add_mapping(1, 'shard_1')
sharding.add_mapping(2, 'shard_2')
sharding.add_mapping(3, 'shard_1')

print(sharding.get_shard(1))  # shard_1
print(sharding.get_shard(2))  # shard_2

Практический пример: система пользователей

from typing import Optional, List
from dataclasses import dataclass

@dataclass
class User:
    user_id: int
    name: str
    email: str
    created_at: str

class UserShardingService:
    """Сервис для работы с пользователями в шардированной БД"""
    
    def __init__(self, num_shards: int = 4):
        self.num_shards = num_shards
        # Имитация БД для каждого шарда
        self.shards = {f"shard_{i}": {} for i in range(num_shards)}
    
    def get_shard_name(self, user_id: int) -> str:
        """Определить имя шарда по user_id"""
        shard_num = user_id % self.num_shards
        return f"shard_{shard_num}"
    
    def create_user(self, user: User) -> bool:
        """Создать пользователя"""
        shard_name = self.get_shard_name(user.user_id)
        shard = self.shards[shard_name]
        
        if user.user_id in shard:
            raise ValueError(f"User {user.user_id} already exists")
        
        shard[user.user_id] = user
        print(f"Created user {user.user_id} in {shard_name}")
        return True
    
    def get_user(self, user_id: int) -> Optional[User]:
        """Получить пользователя по ID"""
        shard_name = self.get_shard_name(user_id)
        shard = self.shards[shard_name]
        return shard.get(user_id)
    
    def update_user(self, user: User) -> bool:
        """Обновить пользователя"""
        shard_name = self.get_shard_name(user.user_id)
        shard = self.shards[shard_name]
        
        if user.user_id not in shard:
            raise ValueError(f"User {user.user_id} not found")
        
        shard[user.user_id] = user
        print(f"Updated user {user.user_id} in {shard_name}")
        return True
    
    def delete_user(self, user_id: int) -> bool:
        """Удалить пользователя"""
        shard_name = self.get_shard_name(user_id)
        shard = self.shards[shard_name]
        
        if user_id not in shard:
            raise ValueError(f"User {user_id} not found")
        
        del shard[user_id]
        print(f"Deleted user {user_id} from {shard_name}")
        return True
    
    def get_shard_stats(self) -> dict:
        """Получить статистику по шардам"""
        stats = {}
        for shard_name, shard_data in self.shards.items():
            stats[shard_name] = len(shard_data)
        return stats

# Использование
service = UserShardingService(num_shards=4)

# Создание пользователей
service.create_user(User(1, "Alice", "alice@example.com", "2024-01-01"))
service.create_user(User(5, "Bob", "bob@example.com", "2024-01-02"))
service.create_user(User(9, "Charlie", "charlie@example.com", "2024-01-03"))
service.create_user(User(2, "Diana", "diana@example.com", "2024-01-04"))

# Получение пользователя
user = service.get_user(5)
print(f"Found user: {user.name}")  # Bob

# Статистика
stats = service.get_shard_stats()
print(f"Shard stats: {stats}")
# Shard stats: {'shard_0': 1, 'shard_1': 2, 'shard_2': 0, 'shard_3': 1}

Проблемы горизонтального шардинга

1. Hot Shards (перегруженные шарды)

# Проблема: некоторые шарды получают больше трафика
class ShardingWithMonitoring:
    def __init__(self, num_shards: int):
        self.num_shards = num_shards
        self.shard_loads = {f"shard_{i}": 0 for i in range(num_shards)}
    
    def get_shard(self, user_id: int) -> str:
        shard_name = f"shard_{user_id % self.num_shards}"
        self.shard_loads[shard_name] += 1
        return shard_name
    
    def get_hot_shards(self) -> List[str]:
        """Получить перегруженные шарды"""
        avg_load = sum(self.shard_loads.values()) / len(self.shard_loads)
        return [
            shard for shard, load in self.shard_loads.items()
            if load > avg_load * 1.5
        ]

2. Cross-shard queries (кросс-шардовые запросы)

class CrossShardQuery:
    """Обработка запросов, затрагивающих несколько шардов"""
    
    def __init__(self, num_shards: int):
        self.num_shards = num_shards
        self.shards = {f"shard_{i}": {} for i in range(num_shards)}
    
    def query_all_shards(self, condition) -> List:
        """Выполнить запрос на все шарды"""
        results = []
        for shard_name, shard_data in self.shards.items():
            # Выполнить запрос на каждом шарде
            shard_results = [
                item for item in shard_data.values()
                if condition(item)
            ]
            results.extend(shard_results)
        return results

3. Resharding (переперераспределение)

class ReshardingService:
    """Перераспределение данных при добавлении нового шарда"""
    
    def __init__(self, initial_shards: int):
        self.num_shards = initial_shards
        self.shards = {f"shard_{i}": {} for i in range(initial_shards)}
    
    def add_shard(self):
        """Добавить новый шард и переперераспределить данные"""
        new_shard_num = self.num_shards
        new_shard_name = f"shard_{new_shard_num}"
        
        # Создать новый шард
        self.shards[new_shard_name] = {}
        old_num_shards = self.num_shards
        self.num_shards += 1
        
        # Перераспределить все данные
        all_data = []
        for shard in self.shards.values():
            all_data.extend(shard.items())
        
        # Очистить все шарды
        for shard in self.shards.values():
            shard.clear()
        
        # Переопределить данные по новой схеме
        for key, value in all_data:
            shard_num = hash(key) % self.num_shards
            self.shards[f"shard_{shard_num}"][key] = value
        
        print(f"Resharding completed. Total shards: {self.num_shards}")

Преимущества и недостатки

Преимущества:

  • Масштабируемость — можно добавлять новые шарды
  • Параллелизм — каждый шард может работать независимо
  • Снижение нагрузки — данные распределены по серверам
  • Улучшение производительности — каждый шард работает с меньшим объёмом

Недостатки:

  • Hot shards — некоторые шарды могут быть перегружены
  • Cross-shard queries — сложные запросы требуют обращения к нескольким шардам
  • Resharding — переперераспределение данных сложное и дорогое
  • Распределённые транзакции — сложно обеспечить ACID гарантии

Когда использовать

  • Очень большие объёмы данных (больше одного сервера)
  • Высокая нагрузка на запись
  • Данные естественно разделяются
  • Готовность жертвовать сложностью ради масштабируемости

Итоги

Горизонтальный шардинг — мощный инструмент для масштабирования больших систем. Однако он добавляет значительную сложность в управление БД и логику приложения. Перед внедрением нужно тщательно оценить требования и выбрать правильную стратегию шардирования.

Что такое горизонтальный шардинг? | PrepBro