Tech Learning Daily

2026-09-17 (Thu) — 第 62 号
AI が毎朝届ける、ソフトウェア技術の基礎解説
DB 🌳 応用 ⏱ 約 7 分

同じ「テーブルを割る」なのに、パーティショニングとシャーディングは何が違うのか?

テーブルが数千万行を超えて重くなってきたとき、「パーティショニングしましょう」と「シャーディングしましょう」の両方が候補に挙がったことはないだろうか。どちらも「テーブルを割る」話に聞こえるが、割る場所も、割った後にぶつかる壁もまったく別物だ。この違いを混同したまま設計すると、パーティショニングでは解決できない問題にパーティショニングで挑んで詰まることになる。

🎯 3 行まとめ
  • パーティショニングは 1 台の DB インスタンスの中でテーブルを複数の物理的な子テーブルに割ることで、クエリプランナーが不要な子テーブルの読み込みを飛ばす partition pruning によって速くなる。
  • シャーディングはその分割をさらに複数の独立したサーバーへ分散させることで、1 台のディスク I/O・CPU という物理的な上限そのものを超える手段になるが、shard key を跨ぐ JOIN やトランザクションは素朴には成立しなくなる。
  • PostgreSQL の宣言的パーティショニングでは一意制約・主キーがパーティションキーの列を含まないと張れない。各子テーブルのインデックスはそのテーブル内でしか重複を検知できないためだ。

パーティショニング — 1 台の中でテーブルを物理的に割る

テーブルが太ってくると、インデックスも肥大化し、範囲スキャンやバキュームのコストが上がる。パーティショニングは、論理的には 1 つのテーブルに見えるものを、内部では複数の子テーブル(パーティション)に分割して格納する仕組みだ。PostgreSQL の宣言的パーティショニングは range(範囲)・list(列挙)・hash(ハッシュ)の 3 方式を提供する。range は日付やID範囲のように連続した値で区切り、list は特定の値をそのまま列挙して振り分け、hash はパーティションキーのハッシュ値を指定した modulus で割った余りで振り分ける。

分割の効果は、クエリプランナーが WHERE 句からパーティションキーの条件を読み取り、該当しない子テーブルを丸ごとスキャン対象から除外する partition pruning によって生まれる。逆に言えば、パーティションキーを含まない条件で検索すると全パーティションを開くことになり、分割前より遅くなる場合すらある。

シャーディング — 複数サーバーに分散して「1 台の上限」自体を超える

パーティショニングは同じマシンの中で行を仕分けるだけなので、そのマシン 1 台が持つディスク I/O やメモリの上限までしかスケールしない。シャーディングは、その分割単位(シャード)を別々の物理サーバーに配置することで、サーバーを追加するほど書き込み・読み込みの総量をさばけるようにする。書き込みが集中して 1 台の CPU が張り付いている、というパーティショニングでは解決できない種類の問題に対する手段だ。

各行がどのシャードに属するかは shard key の値をハッシュ関数に通して決めるのが一般的で、user_id やテナント ID がよく使われる。アクセスパターンと無関係な列を shard key に選ぶと、1 件のクエリが複数シャードにまたがりやすくなり、分散した意味が薄れる。

[パーティショニング] 1台のDBの中で分割
┌───────────────────────┐
│ events(親テーブル)         │
│ ├ events_2026_08      │
│ ├ events_2026_09      │
│ └ events_2026_10      │
└───────────────────────┘

[シャーディング] 複数サーバーに分散
┌────────┐ ┌────────┐ ┌────────┐
│shard-0 │ │shard-1 │ │shard-2 │
└────────┘ └────────┘ └────────┘
🍱 たとえるなら

1 棟の倉庫を仕切り壁で区画分けするのがパーティショニングで、受付(クエリプランナー)は「今日出荷分の棚だけ見ればいい」と分かれば他の区画を無視して直行できる。シャーディングは、その倉庫自体を複数の街に建てて商品を分散させることに近い。同じ街の在庫だけで済む注文は速いが、複数の街の在庫をまたぐ注文は、街をまたいで電話をかけ合って突き合わせる手間が発生する。

クロスシャードの壁 — JOIN とトランザクションは素朴には効かない

単一の DB インスタンスなら当たり前だった「複数テーブルを JOIN する」「複数行の更新を 1 つのトランザクションでまとめる」は、行が別サーバーに散らばった瞬間に自明ではなくなる。あるシャードの在庫を減らし、別のシャードの注文テーブルに 1 行挿入する、という処理をまたいで ACID を保証するには、通常の単一 DB のトランザクションでは足りず、アプリ側で補償処理を書くか、分散トランザクションに対応した基盤を選ぶ必要がある。JOIN も同様で、shard key を跨ぐ JOIN はアプリケーション側で複数シャードに問い合わせて結果を突き合わせる形に書き直すことになりやすい。

この壁があるため、シャーディングは「クエリが遅いから」だけで採用するものではない。パーティショニングと read replica でまだ余地があるうちはそちらで粘り、書き込みの総量が 1 台の上限に達したときに初めて検討する選択肢だ。

リシャーディングはなぜ怖いのか

シャード数を後から変える作業(リシャーディング)が難しいのは、単にシャードを増やすだけでなく、既存データを新しい配置へ無停止で移動させなければならないからだ。Vitess のドキュメントは、リシャーディングを「シャード数を変え、既存シャードを分割または統合する」作業と定義したうえで、ソースからデスティネーションへのデータコピー・レプリケーションによる追従・整合性検証・トラフィックの切り替えという複数の段階を、サービスを止めずに完走させる必要があると説明している。この移動量を小さく抑えるための shard key の選び方が、コンシステントハッシングのような手法につながっていく。

💼 実務でどう出会うか

「read replica を増やしても改善しないボトルネックが書き込み側にある」と気づいた段階で、シャーディングの検討が始まりやすい。MySQL 系なら Vitess、PostgreSQL 系なら Citus のようなミドルウェアが shard key の管理・クロスシャードクエリのルーティングを肩代わりしてくれるが、それでも shard key の選定はアプリケーション側の責任として残る。一方、単に「特定期間のログだけ高速に検索・削除したい」という要求であれば、パーティショニング止まりで十分なことが多い。

⌨️ 手を動かす(5 分)

ローカルの Docker で PostgreSQL を起動し、range パーティショニングを作って、partition pruning が EXPLAIN の出力にどう現れるかを確認する。

docker run --rm -d --name pg-part -e POSTGRES_PASSWORD=pass \
  -p 5433:5432 postgres:16
sleep 3
docker exec -i pg-part psql -U postgres <<'SQL'
CREATE TABLE events (id bigint, created_at date NOT NULL, payload text)
  PARTITION BY RANGE (created_at);
CREATE TABLE events_2026_08 PARTITION OF events
  FOR VALUES FROM ('2026-08-01') TO ('2026-09-01');
CREATE TABLE events_2026_09 PARTITION OF events
  FOR VALUES FROM ('2026-09-01') TO ('2026-10-01');
EXPLAIN SELECT * FROM events WHERE created_at = '2026-09-10';
SQL
docker rm -f pg-part

EXPLAIN の結果には events_2026_09 のスキャンだけが現れ、events_2026_08 は計画にすら登場しないはずだ。これが partition pruning で、WHERE 句の日付条件だけで該当しない子テーブルを候補から外している。

🙅 よくある誤解
  • パーティショニングもシャーディングも結局テーブルを分割しているだけで、規模の違いにすぎない — パーティショニングは 1 台のマシンの中で完結する分割で、その 1 台の I/O・CPU の上限は超えられない。シャーディングは複数マシンへ分散するため、この上限そのものを超えられる点が本質的に異なる。
  • パーティションを増やせば増やすほどクエリは速くなる — WHERE 句にパーティションキーの条件が含まれない検索は partition pruning が効かず全パーティションを開くことになり、分割前より遅くなることがある。
  • シャーディングしても JOIN やトランザクションは今まで通り書ける — shard key を跨ぐ JOIN や複数シャードにまたがるトランザクションは通常の単一 DB のトランザクションでは保証できず、アプリ側の設計変更か専用ミドルウェアが要る。
📖 用語ミニ辞典
パーティショニング
1 台の DB インスタンス内でテーブルを複数の物理的な子テーブルに分割する仕組み。
シャーディング
パーティショニングの単位をさらに複数の独立したサーバーへ分散させ、1 台の上限を超える手段。
シャードキー(shard key)
各行がどのシャードに属するかを決める列。ハッシュ関数に通して振り分けるのが一般的。
パーティションプルーニング
クエリプランナーが WHERE 句からパーティションキーの条件を読み取り、不要な子テーブルをスキャン対象から除外する最適化。
リシャーディング
シャード数を変更し既存シャードを分割・統合する作業。無停止でのデータ移動と切り替えが難しい。
🔗 もっと深く
最近の記事

なぜ Web サーバーは「パブリックサブネット」に置き、DB は「プライベートサブネット」に置くのか?

パブリック/プライベートサブネットの違いはルートテーブルに0.0.0.0/0→インターネットゲートウェイの経路があるかだけで決まる。NATゲートウェイのアドレス変換の仕組みと、サブネットがAZをまたげない制約によるAZ分散設計を解説。
Infra🌿 基礎⏱ 約 7 分2026-09-16

Pod は Running なのに、なぜ Service からの応答が来たり来なかったりするのか?

livenessProbe失敗はkubeletによるコンテナ再起動、readinessProbe失敗はEndpointSliceからの除外(トラフィック遮断のみ)という違いと、startupProbeがliveness誤爆を防ぐ仕組み、minReadySecondsとローリングアップデートの関係を解説。
Container🌿 基礎⏱ 約 7 分2026-09-15

フロントとバックエンドのログはバラバラに出るのに、なぜ1つのリクエストとして追跡できるのか?

分散トレーシングは1つのリクエストが複数サービスをまたぐ間の処理単位spanをtraceとしてつなげる仕組み。W3C Trace Contextのtraceparentヘッダーによるtrace-id/parent-id伝播の流れと、head-based/tail-basedサンプリングのトレードオフを解説。
Obs🌿 基礎⏱ 約 7 分2026-09-14

kubectl apply を叩いてから、Pod が起動するまでに何が起きているのか?

kubectl applyはkube-apiserver経由でetcdに望ましい状態を書き込むだけで、Pod作成はcontroller-manager、ノード選定はkube-scheduler、実際の起動はkubeletが担う。watchによるコントロールループの仕組みを解説。
Container🌳 応用⏱ 約 7 分2026-09-13