分散結合戦略(broadcast・shuffle・sort-merge)
分散JOINが遅いのは、たいてい結合戦略の選択を誤っているからです。小表を配るブロードキャスト、両表を再分配するシャッフル、大表どうしのソートマージを、いつ何が選ばれるかの判断基準ごと原理から押さえられます。
- 1.分散結合の骨格は三つ。ブロードキャストハッシュ結合は小さい方の表を全ノードへ複製し、大表をローカルでハッシュ照合するのでシャッフルが片側で済む。両表が大きいときはシャッフル結合で結合キーのハッシュにより両表を再分配し、同じキーを同じノードへ集めてから結合する。
- 2.ソートマージ結合はシャッフル後に両表を結合キーでソートし、2本のソート済みストリームを同時走査(マージ)して照合する。ハッシュ表がメモリに載らない大規模結合やスピルに強く、多くのエンジンで大表どうしの既定になる。
- 3.戦略選択はコストベースで決まる。片方が閾値(例 Sparkの既定10MB)以下ならブロードキャスト、そうでなければシャッフル系。両表が同じキーで同じ数のバケット/分散に事前配置されていればシャッフル自体を省ける。最大の落とし穴は結合キーの偏りで、ホットキーが1ノードを潰す。
なぜ分散環境では「結合戦略」が問題になるのか
1台のDBなら、結合の主な選択肢はネステッドループ・ハッシュ結合・ソートマージ結合で、勝負はインデックスとメモリの範囲に収まります。ところがデータが数百ノードに分割された分析基盤では、話が一段変わります。同じ結合キーを持つ行が、別々のノードに散らばっているからです。ノードN1にある注文行の相手(顧客行)が、ノードN7に載っているかもしれない。この状態で結合を成立させるには、まず照合すべき行どうしを同じノードへ集める必要があり、そのデータ移動こそが分散結合のコストの本体です。
つまり分散結合の設計は、結合アルゴリズムそのものより、**「どちらの表を、どうやってノード間で動かすか」**の選択です。この移動戦略が主に三つ——ブロードキャスト・シャッフル・ソートマージ——であり、どれを選ぶかで実行時間が桁で変わります。移動を伴う再分配は、/data-engineering/mapreduce-shuffle/ で見た all-to-all のシャッフルと同じ性質を持ち、分散処理で最も高コストな操作です。
分散結合はふつう「再分配フェーズ(データを正しいノードへ動かす)」と「ローカル結合フェーズ(各ノード内で照合する)」の二段で捉えます。ローカル結合には単一ノードと同じハッシュ結合やソートマージが使われますが、その前段の再分配の設計が戦略の違いを生みます。ブロードキャストは片方だけを全ノードへ複製、シャッフルは両方をキーで再分配、そして再分配後にソートで照合するのがソートマージです。
ブロードキャストハッシュ結合:小さい方を配って片側のシャッフルを消す
一方の表が十分に小さい(マスタ表・ディメンション表など)なら、最も速いのはブロードキャストハッシュ結合(broadcast hash join)です。手順はこうです。まず小さい表を全ノードへまるごと複製して配り、各ノードのメモリ上にその表のハッシュ表を作る。次に大きい表は動かさず、各ノードが自分の持ち分の行を1件ずつハッシュ表に問い合わせて照合する。
小表S(数MB)を全ノードへ複製、各ノードでハッシュ表を構築
大表L はそのまま(再分配なし)
ノードN1: L の担当行 → メモリ上の S ハッシュ表を lookup → 出力
ノードN2: L の担当行 → 同じ S ハッシュ表を lookup → 出力
…
→ 大表 L のネットワーク移動はゼロ
肝は大表を一切シャッフルしない点です。全対全の再分配が消え、代わりに小表を配る片方向のブロードキャスト(合計で「小表サイズ×ノード数」程度)だけで済みます。大表が巨大でも小表が数MBなら、これが圧倒的に有利になります。制約は明確で、複製した小表のハッシュ表が各ノードのメモリに載りきる必要があること。だからエンジンは「小さい方のサイズが閾値以下か」を選択の第一基準にします(Sparkの spark.sql.autoBroadcastJoinThreshold は既定10MB)。
ブロードキャストの可否はオプティマイザのサイズ推定に依存します。統計が古い・フィルタ後の行数を読み違える・圧縮列の展開後サイズを過小評価する、といった理由で「小さいはず」の表が実は大きいと、全ノードのメモリを圧迫してOOMやGC地獄を招きます。逆に本当は小さいのに統計欠如で閾値超えと誤判定すると、無用なシャッフル結合に落ちて遅くなります。実務では統計の更新(ANALYZE)と、必要なら明示ヒント(broadcast(df) など)で戦略を固定するのが定石です。
シャッフルハッシュ結合:両表を結合キーで再分配する
両方の表が大きく、どちらもブロードキャストできないときの基本形がシャッフルハッシュ結合(shuffle hash join)、Hive系ではリパーティション結合とも呼ばれます。考え方は単純で、両表を結合キーのハッシュで同じ規則に従って再分配し、同じキーの行が必ず同じノードへ集まるようにしてから、各ノード内でローカルにハッシュ結合します。
両表とも partition = hash(結合キー) mod N で再分配(N はノード/パーティション数)
key=a の行 → L からも R からも同じノードNx へ
key=b の行 → L からも R からも同じノードNy へ
各ノード: 自分に集まった L 断片から一方のハッシュ表を作り、
もう一方の断片を lookup して結合
このとき決定的なのは、両表に同一のハッシュ関数と同一の分割数を適用すること。そうして初めて「hash(a) が指すノード」が両表で一致し、キー a の行が漏れなく同じノードに揃います。ローカル結合フェーズでは、集まった断片のうち小さい側でハッシュ表を作り、大きい側でプローブするのが定石です。コストの本体は、/data-engineering/spark-rdd-dag/ でいうワイド依存=シャッフル、つまり両表ぶんの全対全転送にあります。ブロードキャストが片側だけを動かすのに対し、シャッフルは両側を動かすぶん高くつきます。
ソートマージ結合:ソートしてから2本のストリームを突き合わせる
シャッフルハッシュ結合には弱点があります。ローカル結合フェーズで片方の断片からハッシュ表を作るため、その断片がノードのメモリに載らないと破綻する(スピルしても効率が落ちる)点です。結合キーが偏って一部ノードに巨大な断片が来たり、そもそも両表とも巨大だったりすると、これが起きます。そこで大規模結合の既定として広く使われるのが**ソートマージ結合(sort-merge join)**です。
手順は三段です。(1) 両表を結合キーでシャッフル再分配して同じキーを同じノードへ集める(ここまではシャッフルハッシュと同じ)。(2) 各ノードで、集まった両表の断片をそれぞれ結合キーでソートする。(3) ソート済みの2本のストリームを、2つのポインタで同時に前進させながら突き合わせる(マージ)。
ソート後(両表とも結合キー昇順):
L: a a b d e … ポインタ pL, pR を進めながら比較
R: a b b c e …
pL=a, pR=a → 一致(キーa の全組を出力)
一致キーを出し切ったら、小さい方のポインタを前進
pL の値 < pR の値 なら pL を進め、逆なら pR を進める
ソートマージの利点は、照合が両ストリームを1回ずつ順に舐めるだけで完結し、巨大なハッシュ表をメモリに常駐させずに済むこと。ソート自体はメモリを超えれば外部マージソートでディスクへスピルでき、大規模データやスキューに対して安定して動きます。さらに、結合キーでソートされた出力もソート済みになるため、後続の集約やウィンドウ関数、次の結合がそのソート順を再利用でき、追加のシャッフルを省けることがあります。Spark SQLがハッシュ結合よりソートマージを大表どうしの既定に据えているのは、この安定性とスピル耐性のためです。
| 戦略 | 動かす表 | ローカル結合の方式 | メモリ前提 | 向く条件 |
|---|---|---|---|---|
| ブロードキャストハッシュ | 小表のみ全複製(大表は不動) | メモリ上ハッシュ表へlookup | 小表のハッシュ表が全ノードに載る | 片方が閾値以下に十分小さい |
| シャッフルハッシュ | 両表をキーで再分配 | 断片からハッシュ表を作りプローブ | 各ノードの片側断片がメモリに載る | 両表が大きいがハッシュ表は収まる |
| ソートマージ | 両表をキーで再分配 | 両ソート済み列をマージ走査 | ソートはスピル可、常駐ハッシュ表不要 | 超大規模・スキュー・出力ソート順を再利用したい |
結合キーの偏り(skew):分散結合を殺す最大要因
戦略を正しく選んでも、**結合キーの偏り(data skew)**が全体を台無しにします。シャッフル系はどちらも hash(結合キー) mod N で行き先を決めるため、特定キーに行が極端に集中する(ホットキー)と、そのキーを担当する1ノードに両表の該当行が山と集まります。他ノードが早々に終わっても、その1ノードだけが巨大な結合を1台で背負い、ジョブ全体がそれを待つ——/data-engineering/mapreduce-shuffle/ のホットキー問題が、結合では両表ぶん効いてさらに悪化します。
分散結合の偏りには典型パターンがあります。第一にホットキー(人気商品ID、特定テナント、unknown などの既定値)に行が集中し、単一ノードのメモリ・時間を食い潰す。第二にNULLキーで、ON a.k = b.k で両表のNULLが同じ宛先へ集まり巨大な塊になる(結合条件上はNULL同士はマッチしないのに再分配だけされる無駄)。第三に、そのホットキーが両表に多数あると、そのノード内でローカルに近い直積(cross product)が発生し、出力行数が爆発する。対策は、ホットキーへソルト(key#0〜key#n に割ってから結合し後で戻す)を付けて複数ノードへ散らす、NULLは事前に除外またはランダム値へ振る、偏るキーだけ別処理にする、といった偏りを前提にした結合設計です。偏りは計算量ではなく再分配の分配で効くので、ノードを増やしても解決しません。
どれが選ばれるか:コストベースの判断基準
現代のエンジン(Spark SQL、各種MPP、Presto/Trino)は、これらを**コストベースオプティマイザ(CBO)**が統計を見て選びます。判断の骨子は次の順です。
- 片方がブロードキャスト閾値以下か。小さい表があり、その推定サイズが閾値(例10MB)以下ならブロードキャストハッシュ結合。全対全を回避できるので最優先。
- 両表が結合キーで事前配置済みか。両表が同じキー・同じ数のバケット(/data-engineering/partitioning-bucketing/)や、MPPの同じ分散キー(/data-engineering/mpp-data-warehouse/ のコロケーテッド結合)に配置されていれば、シャッフル自体を省いてノード内で直接結合できる。設計で稼ぐ最大の勝ち筋。
- 上のどちらでもなければシャッフル系。両表を再分配したうえで、ハッシュ表が載るならシャッフルハッシュ、超大規模・スキュー耐性・出力ソート順の再利用を狙うならソートマージ。多くのエンジンが安定性からソートマージを大表どうしの既定にする。
押さえる点。(1) 分散結合のコストは結合アルゴリズムより再分配(データ移動)で決まる。同じキーを同じノードへ集める必要があるため。(2) ブロードキャスト=小表を全ノード複製・大表は不動でシャッフルが片側で済む/シャッフルハッシュ=両表を hash(key) mod N で再分配し断片からハッシュ表/ソートマージ=再分配後に両表をソートしマージ走査。(3) ソートマージが大表の既定なのは常駐ハッシュ表が不要でスピルに強く、出力がソート済みだから。(4) 選択はコストベース——閾値以下ならブロードキャスト、事前配置(同一バケット/同一分散キー)ならシャッフル省略、他はシャッフル系。(5) 最大の落とし穴は結合キーの偏り(ホットキー・NULL・直積爆発)で、hash mod N の均等分割が一様分布を仮定するため崩れる。ノード増でなくソルト等の偏り対策で解く。「ブロードキャストは常に速い」ではなく小表のメモリ常駐が前提、という切り分けが要点。
まとめ
- 分散結合の本質は照合すべき行を同じノードへ集める再分配で、コストの大半はこのデータ移動にある。アルゴリズムは三つの再分配戦略に集約される。
- ブロードキャストハッシュ結合は小表を全ノードへ複製して大表を動かさないため、片側のブロードキャストだけで済む。前提は複製した小表のハッシュ表が各ノードのメモリに載ること(閾値判定)。
- シャッフルハッシュ結合は両表を
hash(結合キー) mod Nで再分配し、同じキーを同じノードへ集めてローカルにハッシュ結合する。両表ぶんの全対全転送が本体コスト。 - ソートマージ結合は再分配後に両表をキーでソートし、2本のソート済みストリームをマージ走査する。常駐ハッシュ表が要らずスピルに強く、出力もソート済みなので、大表どうしの既定として広く使われる。
- 戦略はコストベースで選ばれる。閾値以下ならブロードキャスト、同一バケット/同一分散キーに事前配置していればシャッフルを省略、他はシャッフル系(安定性からソートマージが既定になりやすい)。
- 最大の敵は結合キーの偏り。ホットキー・NULLキー・直積爆発が単一ノードを潰し、
hash mod Nの均等分割は一様分布の仮定に立つため現実データで崩れる。ノード増ではなくソルトやNULL除外など偏り前提の設計で解く。
データ工学 Article
分散結合戦略(broadcast・shuffle・sort-merge)を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
分散結合
比較で見る軸
難易度: advanced / カテゴリ: データ工学 / タグ数: 6
導入後に効く点
ソートマージ結合はシャッフル後に両表を結合キーでソートし、2本のソート済みストリームを同時走査(マージ)して照合する。ハッシュ表がメモリに載らない大規模結合やスピルに強く、多くのエンジンで大表どうしの既定になる。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- データ工学
- タグ数
- 6
判断チェックリスト
- 自社の用途が「分散結合 / シャッフル」に近いか確認する。
- 強みである「分散結合の骨格は三つ。ブロードキャストハッシュ結合は小さい方の表を全ノードへ複製し、大表をローカルでハッシュ照合するのでシャッフルが片側で済む。両表が大きいときはシャッフル結合で結合キーのハッシュにより両表を再分配し、同じキーを同じノードへ集めてから結合する。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。