シャーディング
水平分割によるスケールアウト
シャーディングはデータを複数のデータベースに水平分割する手法。 書き込みスケール、大量データの分散、レイテンシの地理的最適化を実現します。
シャーディング vs パーティショニング
シャーディング
複数の異なるサーバーにデータを分散。 水平スケールを実現。
パーティショニング
同一サーバー内でデータを分割。 クエリ性能向上、メンテナンス容易化。
シャーディング戦略
レンジシャーディング
キーの範囲でシャードを決定。例: user_id 1-1000万 → Shard1
Shard1: 1-10M
Shard2: 10M-20M
Shard3: 20M-30M
利点: 範囲クエリが効率的 / 欠点: ホットスポットが発生しやすい
ハッシュシャーディング
キーのハッシュ値でシャードを決定。例: hash(user_id) % N
hash % 3 = 0
hash % 3 = 1
hash % 3 = 2
利点: 均等に分散 / 欠点: 範囲クエリが非効率、リシャーディングが困難
ディレクトリシャーディング
ルックアップテーブルでキーとシャードのマッピングを管理。
利点: 柔軟な配置、リシャーディング容易 / 欠点: ルックアップのオーバーヘッド
コンシステントハッシュ
ノード追加/削除時に再配置されるデータを最小化。 分散キャッシュ(Memcached等)で広く使用。
シャードキーの選定
シャードキーの選定は最も重要な設計判断。後から変更が困難。
良いシャードキー
- • カーディナリティが高い(多様な値)
- • 書き込みが均等に分散
- • クエリパターンに沿う(同一シャードで完結)
- • 例: user_id, tenant_id
避けるべきシャードキー
- • 単調増加(created_at, auto_increment)→ ホットスポット
- • カーディナリティが低い(status, country)
- • 頻繁に変更される値
クロスシャードクエリ
複数シャードにまたがるクエリは性能が大幅に低下。設計で回避する。
問題となる操作
- • シャードキー以外でのJOIN
- • 全シャードでのCOUNT、SUM
- • グローバルな ORDER BY + LIMIT
対策
- • データを非正規化してシャード内で完結
- • 集計は別途バッチ処理 or OLAP DB
- • アプリケーション側で結果をマージ
実装パターン
1 # シャードキーによるルーティング(Pythonの例) 2 3 def get_shard(user_id: int, num_shards: int = 4) -> str: 4 """ハッシュベースのシャード決定""" 5 shard_id = hash(user_id) % num_shards 6 return f"shard_{shard_id}" 7 8 def get_connection(shard_name: str): 9 """シャードに対応するDB接続を返す""" 10 shard_configs = { 11 "shard_0": {"host": "db-shard-0.example.com", "port": 5432}, 12 "shard_1": {"host": "db-shard-1.example.com", "port": 5432}, 13 # ... 14 } 15 return create_connection(shard_configs[shard_name]) 16 17 # 使用例 18 user_id = 12345 19 shard = get_shard(user_id) 20 conn = get_connection(shard) 21 conn.execute("SELECT * FROM users WHERE id = %s", [user_id])
ミドルウェア/プロキシ
- • Vitess: MySQL用のシャーディングミドルウェア(YouTube由来)
- • Citus: PostgreSQL拡張でシャーディング
- • ProxySQL: クエリルーティング、接続プール
SRE/インフラ観点
導入の判断
- • シャーディングは最後の手段(複雑性が大幅増加)
- • まずは垂直スケール、読み取りレプリカ、キャッシュを検討
- • データ量がTB級、書き込みがボトルネックの場合に検討
運用の課題
- • リシャーディング(シャード追加/再分散)は非常に困難
- • バックアップ・リストアの複雑化
- • スキーママイグレーションの調整
- • 各シャードの監視とアラート設定