ストーリー
「ログテーブルが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分