Что такое горизонтальный шардинг?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Горизонтальный шардинг
Горизонтальный шардинг (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 гарантии
Когда использовать
- Очень большие объёмы данных (больше одного сервера)
- Высокая нагрузка на запись
- Данные естественно разделяются
- Готовность жертвовать сложностью ради масштабируемости
Итоги
Горизонтальный шардинг — мощный инструмент для масштабирования больших систем. Однако он добавляет значительную сложность в управление БД и логику приложения. Перед внедрением нужно тщательно оценить требования и выбрать правильную стратегию шардирования.