LESSON 30分

ストーリー

「ログテーブルが10億行を超えた。インデックスを貼ってもVACUUMが追いつかない」

高橋アーキテクトが深刻な顔で言う。

「単一テーブルの限界だ。テーブルを物理的に分割する必要がある。パーティショニングとシャーディング、この2つの分割戦略を使い分けるんだ」


パーティショニングとは

1つの論理テーブルを複数の物理テーブル(パーティション)に分割する技法。アプリケーションからは1つのテーブルとして見える。

パーティショニングの種類

種類分割基準適用場面
Range値の範囲(日付など)時系列データ、ログ
List特定の値リスト地域、カテゴリ
Hashハッシュ値均等分散

Range パーティショニング

-- 月別パーティション(PostgreSQL)
CREATE TABLE logs (
  id BIGSERIAL,
  message TEXT NOT NULL,
  created_at TIMESTAMPTZ NOT NULL,
  level VARCHAR(10) NOT NULL
) PARTITION BY RANGE (created_at);

-- 月別パーティションを作成
CREATE TABLE logs_2024_01 PARTITION OF logs
  FOR VALUES FROM ('2024-01-01') TO ('2024-02-01');
CREATE TABLE logs_2024_02 PARTITION OF logs
  FOR VALUES FROM ('2024-02-01') TO ('2024-03-01');
CREATE TABLE logs_2024_03 PARTITION OF logs
  FOR VALUES FROM ('2024-03-01') TO ('2024-04-01');

-- クエリ: パーティションプルーニングが効く
SELECT * FROM logs
WHERE created_at >= '2024-02-01'
  AND created_at < '2024-03-01';
-- → logs_2024_02 のみスキャンされる

List パーティショニング

-- 地域別パーティション
CREATE TABLE customers (
  id SERIAL,
  name VARCHAR(100) NOT NULL,
  region VARCHAR(20) NOT NULL,
  email VARCHAR(255) NOT NULL
) PARTITION BY LIST (region);

CREATE TABLE customers_jp PARTITION OF customers
  FOR VALUES IN ('japan');
CREATE TABLE customers_us PARTITION OF customers
  FOR VALUES IN ('usa');
CREATE TABLE customers_eu PARTITION OF customers
  FOR VALUES IN ('uk', 'germany', 'france');
CREATE TABLE customers_other PARTITION OF customers
  DEFAULT;

Hash パーティショニング

-- ユーザーIDでHash分割(均等分散)
CREATE TABLE user_activities (
  id BIGSERIAL,
  user_id INT NOT NULL,
  activity_type VARCHAR(50) NOT NULL,
  created_at TIMESTAMPTZ NOT NULL
) PARTITION BY HASH (user_id);

CREATE TABLE user_activities_0 PARTITION OF user_activities
  FOR VALUES WITH (MODULUS 4, REMAINDER 0);
CREATE TABLE user_activities_1 PARTITION OF user_activities
  FOR VALUES WITH (MODULUS 4, REMAINDER 1);
CREATE TABLE user_activities_2 PARTITION OF user_activities
  FOR VALUES WITH (MODULUS 4, REMAINDER 2);
CREATE TABLE user_activities_3 PARTITION OF user_activities
  FOR VALUES WITH (MODULUS 4, REMAINDER 3);

パーティション管理の自動化

// パーティション自動作成スクリプト
async function createMonthlyPartition(
  client: any,
  tableName: string,
  year: number,
  month: number
): Promise<void> {
  const startDate = new Date(year, month - 1, 1);
  const endDate = new Date(year, month, 1);

  const partitionName = `${tableName}_${year}_${String(month).padStart(2, '0')}`;

  const start = startDate.toISOString().split('T')[0];
  const end = endDate.toISOString().split('T')[0];

  await client.query(`
    CREATE TABLE IF NOT EXISTS ${partitionName}
    PARTITION OF ${tableName}
    FOR VALUES FROM ('${start}') TO ('${end}')
  `);

  console.log(`Created partition: ${partitionName}`);
}

// 3ヶ月先まで自動作成
async function ensureFuturePartitions(client: any): Promise<void> {
  const now = new Date();
  for (let i = 0; i <= 3; i++) {
    const target = new Date(now.getFullYear(), now.getMonth() + i, 1);
    await createMonthlyPartition(
      client,
      'logs',
      target.getFullYear(),
      target.getMonth() + 1
    );
  }
}

シャーディングとは

パーティショニングが1つのDB内での分割なのに対し、シャーディングは複数のDBサーバーにデータを分散する技法。

パーティショニング:
  [DB Server]
  ├── partition_1
  ├── partition_2
  └── partition_3

シャーディング:
  [DB Server 1] ── shard_1
  [DB Server 2] ── shard_2
  [DB Server 3] ── shard_3

シャーディングキーの選択

シャーディングキーメリットデメリット
user_idユーザー単位で完結偏りが出る可能性
tenant_idマルチテナントに最適テナントサイズの差
hash(id)均等分散範囲クエリが困難
regionデータの地理的分散地域間クエリが複雑

シャーディングの課題

課題説明
クロスシャードクエリ複数シャードにまたがるJOIN
リバランシングシャード間のデータ移動
トランザクション分散トランザクションの複雑さ
一意性保証グローバルなユニークID生成

パーティショニング vs シャーディング

項目パーティショニングシャーディング
分割先同一DB内複数DBサーバー
透過性アプリに透過的アプリの対応が必要
スケール垂直スケール水平スケール
複雑さ低い高い
適用目安数億行数十億行以上

いつ分割すべきか

テーブル行数:
  ~100万行: インデックス最適化で十分
  100万~10億行: パーティショニング検討
  10億行~: シャーディング検討

判断フロー:
  1. まずインデックスとクエリを最適化
  2. それでも遅い → パーティショニング
  3. 単一サーバーの限界 → シャーディング

まとめ

ポイント内容
パーティショニング同一DB内でのテーブル分割
Range日付範囲で分割(最も一般的)
List特定値リストで分割
Hashハッシュ値で均等分散
シャーディング複数DBサーバーへの分散
優先順位インデックス最適化 → パーティショニング → シャーディング

理解度チェックリスト

  • パーティショニングの3種類を説明できる
  • パーティションプルーニングの仕組みを理解している
  • パーティショニングとシャーディングの違いを説明できる
  • 分割すべきタイミングを判断できる

次のステップ

次は演習:スロークエリを改善しよう。実際のスロークエリを分析し、ここまで学んだ技法を使って改善に挑戦する。


推定読了時間: 30分