TL

分散結合とシャッフル・ブロードキャスト・コロケーション

分散DBの結合が遅いのは、たいていネットワーク越しの行移動が原因です。シャッフル・ブロードキャスト・コロケートの転送量を見積もれれば、シャードキー設計とプラン選択で結合を桁で速くできます。

応用分散システムJoinシャーディングクエリ最適化MPPデータウェアハウス最終更新: 2026-06-21
TL;DR要点だけ先に
  • 1.結合キーがシャードキーと一致すれば、行を移動せず各ノードで局所結合できる(コロケート結合、ネットワーク転送ゼロ)。一致しないと両表を結合キーで再分配する shuffle が必要になり、ほぼ全データがネットワークを流れる。
  • 2.片方の表が十分小さければ、その小表を全ノードへ複製する broadcast 結合が有利。転送量は『小表サイズ × ノード数』で、大表 N を動かす shuffle の転送量を下回るときにプランナが選ぶ。
  • 3.分散プランナはコストを『ネットワーク転送量+局所結合コスト』で見積もり、コロケート> broadcast(小表が小さいとき)> shuffle の順に安いものを選ぶ。基数推定の誤りで小表が実は大きいと broadcast が破綻する。

分散結合で本当に効くのはネットワーク

単一ノードの結合では I/O と CPU が支配項でした(結合アルゴリズム参照)。ところが MPP データウェアハウスや分散SQLでは、表が複数ノードにシャード(水平分割)されているため、結合の前にまず「結合相手の行を同じノードへ集める」必要があります。このノード間のデータ移動がボトルネックです。ネットワーク帯域はメモリ・ローカルディスクより桁で遅く、転送量がそのまま実行時間を支配します。

前提を固定します。ノード数を P、左表 R の総行数を M、右表 S の総行数を N とし、両表は何らかのキーで各ノードへ均等に分散済みとします。結合は等値結合 R.k = S.k を考えます。問題は「結合キー k が、その表のシャードキーと一致しているか」です。一致の有無で、行を動かさずに済むか、動かすなら誰をどれだけ動かすかが決まります。

コロケート結合:行を1バイトも動かさない

両表が同じ結合キー k で同じ方式(同じハッシュ関数・同じノード割当)でシャードされているなら、R.k = S.k を満たす行は定義上必ず同じノードに同居しています。したがって各ノードは自分が持つ RS の断片だけをローカルに結合すればよく、ノード間転送は発生しません。これが**コロケート結合(colocated / local join)**です。

-- 各ノード i で独立に実行(通信なし)
for node i in 0..P-1:
    local_join(R_i, S_i)        -- R_i, S_i は node i 上の断片
emit ∪ 各ノードの結果

正しさの核心は、結合キーが等しい行は同じハッシュ値を持ち、同じノードへ割り当てられるという点です。異なるノードにまたがる一致ペアは存在しえないので、ローカル結合だけで完全な結果が得られます。ネットワーク転送量は 0。これが分散結合の理想形であり、スキーマ設計で「よく結合する表を同じキーでシャードする」ことの最大の動機です。多くの分散DB(Citus の create_distributed_table(..., colocate_with=...)、CockroachDB の interleaved/locality、Spanner の親子テーブル)はこの同居を明示的に作る機能を持ちます。

コロケートが効く設計

ファクト表とディメンション表、あるいは注文と注文明細のように「常に同じ外部キーで結合する」関係は、その外部キーをシャードキーに揃えると恒久的にコロケートできます。逆に同じ表でも結合キー k がシャードキーと違えば、たとえ同居していてもキーが合わず shuffle が要ります。詳しくはシャーディング方式を参照。

シャッフル結合:両表を結合キーで配り直す

結合キーがどちらのシャードキーとも一致しないとき(例:Ruser_idSproduct_id でシャードされ、結合は別の order_id で行う)、行は同居していません。そこで**両表を結合キー k のハッシュで全ノードへ再分配(repartition)し、同じ k の行を同じノードへ集めてから局所結合します。これがシャッフル結合(shuffle / repartition join)**で、単一ノードのグレース/ハイブリッドハッシュ結合を分散に拡張したものです。

-- フェーズ1: 再分配(ネットワーク転送が発生)
for each row r in R:  送る → node[ h(r.k) mod P ]
for each row s in S:  送る → node[ h(s.k) mod P ]

-- フェーズ2: 各ノードで局所ハッシュ結合
for node i in 0..P-1:
    build hashtable from received R rows
    probe with received S rows → emit

転送量の見積もりが要点です。各行は自分の h(k) が指すノードへ送られます。すでに正しいノードにある行(確率 1/P)は送らずに済みますが、残りの (P-1)/P の行はネットワークを渡ります。よって転送される行数はおおむね (M + N) × (P-1)/PP が大きければ実質 M + N のほぼ全行が一度ネットワークを流れます。両表が大きいほど、これが分散結合で最も重いプランになります。

シャッフルはデータ偏りに弱い

h(k) の分布が偏る(特定の k 値が大量にある、いわゆるスキュー)と、一部のノードに行が集中します。シャッフルは「均等分配」を前提にコストを見積もるため、偏ると一部ノードだけ局所結合が肥大し、全体がその遅いノードに律速されます(落伍者・straggler 問題)。NULL 相当の既定値や巨大顧客IDが典型例です。

ブロードキャスト結合:小さい表を全員に配る

片方の表(仮に S)が十分小さいなら、S を再分配するのでなく**全ノードへ丸ごと複製(broadcast / replicate)**し、R は1行も動かさない方が安くなります。各ノードは手元の R 断片と、複製された S 全体を局所結合します。

-- S 全体を全ノードへ複製(R は動かさない)
broadcast(S) → 全ノードが S のフルコピーを持つ

-- 各ノードで局所結合(R は移動なし)
for node i in 0..P-1:
    build hashtable from full S
    probe with local R_i → emit

転送量は N × P(小表 SN 行を P ノードへ)です。これをシャッフルの M + N(厳密には (M+N)(P-1)/P)と比べると、ブロードキャストが有利になる条件は N × PM + N を下回るとき、つまり SR に対して桁違いに小さいときです。大表 R を動かさずに済むのが効きます。スター型スキーマの「巨大ファクト表 × 小さいディメンション表」が典型的な適用先で、ディメンション表を各ノードへ撒けば大表のシャッフルを完全に回避できます。

方式ネットワーク転送量効く条件前提
コロケート結合0両表が結合キーで同居結合キー = 両表のシャードキー
ブロードキャスト結合小表 N × P片方が十分小さい(N×P < M+N)小表全体が各ノードのメモリに載る
シャッフル結合約 (M+N)×(P−1)/P両表が大きく同居もしないどんなキー・サイズでも適用可能(汎用)

分散プランナの選択原理

分散オプティマイザは、各プランの総コストをネットワーク転送量+各ノードの局所結合コストとして見積もり、最小のものを選びます。判定はおおむね次の優先順位です。

if 結合キーが両表のシャードキーと一致:
    → コロケート結合(転送 0、最優先)
elif 片方の表が小さい(推定サイズ × P が他方を下回る):
    → ブロードキャスト結合(小表のみ複製)
else:
    → シャッフル結合(両表を再分配・最も汎用だが最も重い)

この判定が基数推定(行数・サイズの見積もり)に強く依存する点が実務上の急所です(基数推定参照)。「小さいはず」とブロードキャストした表が、選択述語の見積もり誤りで実は巨大だと、N × P の転送とメモリ確保が膨張し、各ノードでハッシュテーブルが作業メモリを溢れてスピルし、シャッフルより遥かに遅くなります。逆に小表を過大評価してシャッフルを選ぶと、避けられたはずの大表移動を払うことになります。

判定の早見

コロケート可否は「結合キー=両表のシャードキー」かどうかだけで決まる(サイズ無関係、転送ゼロ)。コロケートできないときの broadcast と shuffle の分かれ目は転送量比較で、目安は「小表サイズ × ノード数」対「大表サイズ」。小表が小さいほど broadcast 有利、両表とも大きいほど shuffle に倒れる、と覚える。

多段結合とコストの伝播

3表以上の結合では、ある結合の出力がそのまま次の結合の入力になります。ここでコロケーションは伝播し、また失われます。たとえば ABk でコロケート結合した結果は、依然 k でシャードされた状態でノードに残るので、続けて Ck で結合すれば再びコロケートできます。しかし BC を別キー j で結合すると、A⋈B の結果を j で再シャッフルしてからでないと C と結合できません。

つまり結合順序(結合順序の列挙参照)は、単に局所コストだけでなく「中間結果がどのキーでシャードされて出てくるか」まで含めて評価されます。良い分散プランナは、再シャッフルの回数を減らすよう結合順序とパーティション方式を同時に最適化します。中間結果の分布(distribution / partitioning property)を物理プロパティとして持ち回り、不要な再分配を**交換演算子(exchange / motion ノード)**として明示的にプランへ挿入するのが現代の実装の定石です。

実務での読み方

分散DBの実行計画には、ノード間データ移動を表す専用ノードが現れます。Greenplum/Redshift 系の Redistribute MotionBroadcast Motion、Spark の Exchange (hashpartitioning)BroadcastExchange、Citus の repartition がそれです。結合の直下にこれらの Motion/Exchange が無ければコロケートが効いており理想的。Broadcast が出ていれば小表複製、Redistribute/hashpartitioning Exchange が両側に出ていればフルシャッフルです。チューニングの定石は次の順です。

  • 頻出の結合キーで両表のシャードキーを揃え、コロケートに持ち込む(設計段階が最も効く)。
  • 小表側に確実なサイズ上限があるならブロードキャストを誘導(多くのエンジンに broadcast ヒントがある)。ただし基数推定が外れると逆効果。
  • シャッフルが避けられないなら、スキューの解消(偏ったキーの分離・salting)でストラグラーを潰す。
  • 中間結果の分布を意識し、再シャッフルの段数を結合順序で最小化する。

母集合そのものを絞るパーティショニングや、列指向のデータウェアハウス設計と組み合わせると、転送量をさらに下げられます。

まとめ

  • 分散結合のコストはネットワーク転送量が支配項。コロケート(0)<ブロードキャスト(N×P)<シャッフル(約 M+N)の順に重い。
  • コロケート結合は結合キー=両表のシャードキーのとき成立し、行を動かさず各ノードで局所結合する。設計で作り込むのが最善手。
  • ブロードキャスト結合は小表を全ノードへ複製し大表を動かさない。N×PM+N を下回る、小表が十分小さいときに有利。
  • シャッフル結合は両表を結合キーで再分配する汎用手段だが、ほぼ全行がネットワークを流れ最も重い。スキューに弱い。
  • プランナは基数推定に基づきコスト最小を選ぶ。推定誤りでブロードキャストが破綻すると最悪化する。多段結合では中間結果の分布を持ち回り、再シャッフルを最小化する。

データベース Article

分散結合とシャッフル・ブロードキャスト・コロケーションを実務で読む

TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。

解決すること

分散システム

比較で見る軸

難易度: advanced / カテゴリ: データベース / タグ数: 6

導入後に効く点

片方の表が十分小さければ、その小表を全ノードへ複製する broadcast 結合が有利。転送量は『小表サイズ × ノード数』で、大表 N を動かす shuffle の転送量を下回るときにプランナが選ぶ。

先に潰すリスク

用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。

数字・仕様の読み方
難易度
advanced
カテゴリ
データベース
タグ数
6

判断チェックリスト

  • 自社の用途が「分散システム / Join」に近いか確認する。
  • 強みである「結合キーがシャードキーと一致すれば、行を移動せず各ノードで局所結合できる(コロケート結合、ネットワーク転送ゼロ)。一致しないと両表を結合キーで再分配する shuffle が必要になり、ほぼ全データがネットワークを流れる。」が本当に評価軸になるか確認する。
  • 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
  • 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
  • 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
  • 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。

次に確認する観点

分散システムJoinシャーディングクエリ最適化MPP分散システムJoinシャーディング