はじめに
SnowPro Advanced Architectの勉強中、問題集にデータの偏りによるパフォーマンスの低下についての記述がありました。
特定のマイクロパーティションにデータが集中したり、特定のパーティションだけサイズが肥大化すると、並列処理の利点は失われます。
今回は、参考ページのクエリに沿って、意図的に「重いパーティション」を作り、それが処理速度にどう影響するか、実機で確かめてみました。
1. パーティションの粒度と並列化の制約
Snowflakeには 「1つのマイクロパーティションを同時にスキャンできるのは1つのスレッド(ワーカーコア)のみ」 という物理的な制約があります。そのため、データ量が多くてもパーティションが適切に分割されていなければ、並列処理は行われません。
1.1 検証データの作成
100要素の配列を100万行持つテーブルを作成します。論理的には1億要素ですが、行数が比較的少ないため、データは少数のパーティションに凝縮されます。
-- 100要素の配列を100万行格納
create or replace transient table t1_arr (idx int, arr array) as
select seq4(), (select array_agg(seq4()) from table (generator(rowcount => 100)))
from table(generator(rowcount => 1000000));
-- パーティション数を確認
explain select * from t1_arr;
【EXPLAIN結果】
- partitionsTotal: 1
- partitionsAssigned: 1
1.2 データの展開(Flatten)による物理分割
データを展開(Flatten)して別テーブルに再保存することで、物理的なパーティションを分割させます。
-- 展開してテーブル化(1億行へ増幅)
create or replace transient table t1_flattened (idx int, elem string) as
select t1_arr.idx, f.value::string
from t1_arr, lateral flatten(t1_arr.arr) f;
-- パーティション数を確認
explain select * from t1_flattened;
【EXPLAIN結果】
- partitionsTotal: 9
- partitionsAssigned: 9
1.3 性能検証:直列処理 vs 並列処理
「配列のまま展開してソート」する場合と、「展開済みテーブルをソート」する場合で、処理時間を比較します。
-- A: 配列のまま展開してソート(実行時に展開)
select f.value from t1_arr, lateral flatten(t1_arr.arr) f order by f.value;
-- B: 展開済みテーブルをソート
select elem from t1_flattened order by elem;
【検証結果1】
| 検証対象 | 実行時間 | 挙動の解説 |
|---|---|---|
| t1_arr (単一パーティション) | 5m46s | 単一スレッドでスキャンと展開を実行 |
| t1_flattened (複数パーティション) | 9.6s | 複数スレッドによる並列処理 |
1.4 スケールアップによる影響の確認
ウェアハウスを拡張しても、パーティションが分割されていない場合に性能が向上しない現象を確認します。
-- Largeサイズ(8ノード/64スレッド)に拡張して実行
ALTER WAREHOUSE <ウェアハウス名> SET WAREHOUSE_SIZE = 'LARGE';
ALTER SESSION SET USE_CACHED_RESULT = FALSE;
select f.value from t1_arr, lateral flatten(t1_arr.arr) f order by f.value;
【スケールアップ検証結果】
| ウェアハウスサイズ | ノード数 | 合計スレッド数 | 実行時間 |
|---|---|---|---|
| X-Small | 1 | 8 | 5m46s |
| Large | 8 | 64 | 5m42s |
考察
データが単一のパーティションに保持されている場合、「1つのパーティションは同時に1スレッドでしか処理できない」という制約がボトルネックとなります。
スキャン工程が単一スレッドに限定されるため、後続のソート工程に十分なスレッドが割り当てられていても、データの供給が追いつかず、ウェアハウスの拡張が性能向上に寄与しません。
2. データスキュー(特定スレッドへの負荷集中)
次に、パーティションは分かれているものの、特定のパーティションのみデータサイズが極端に大きい状態(データスキュー)の影響を検証します。
2.1 検証データの作成
1億行のランダム文字列を生成し、特定の文字 'z' を含むデータのみサイズを100倍に肥大化させます。
-- 1億行を生成(ソートして物理配置を固める)
create or replace transient table t_skew (s varchar) as
select randstr(1, random()) s
from table(generator(rowcount => 100000000))
order by s;
-- 'z' の行だけデータサイズを増幅
update t_skew set s = repeat('z', 100) where s >= 'z';
2.2 物理配置の確認
特定の範囲にデータが集中していることをEXPLAINで確認します。
-- 肥大化したデータを含む範囲
explain select * from t_skew where s >= 's'; -- partitionsAssigned:1
-- 通常のデータ範囲
explain select * from t_skew where s <= 'r'; -- partitionsAssigned:7
2.3 性能検証:データ偏りの解消策
データの物理配置を最適化することで、処理時間がどのように変化するかを確認します。
-- ① 偏りがある状態(t_skew)
select hash_agg(repeat(s, 1000)) from t_skew;
-- ② 物理的な再配置(再クラスタリング)
create or replace transient table t_dist (s varchar) as
select * from t_skew order by s;
select hash_agg(repeat(s, 1000)) from t_dist;
-- ③ 実行時のランダム分散
select hash_agg(repeat(s, 1000)) from (select * from t_skew order by random());
【検証結果2】
| パターン | 実行時間(X-Small) | 実行時間(Large) |
|---|---|---|
| ① t_skew (偏りあり) | 3m52s | 2m38s |
| ② t_dist (再配置済み) | 3m1s | 27s |
| ③ random()分散 | 3m13s | 29s |
※②の再配置の際、ウェアハウスがx-smallの状態でCREATEすると8パーティションのままテーブルが作成されました。その後Largeに変更してCREATEすると64パーティションになりました。CTASでは出来上がるパーティション数が実行するウェアハウスのスレッド数にも依存するようです。
3. まとめ
今回の検証を通じて、データの偏りが並列処理に与える影響と、その解消の重要性が確認できました。
-
パーティションの適切な分割(集中の回避)
データが特定のパーティションに集中していると、どれだけウェアハウスを拡張しても並列処理の利点を得られません。行数が少なく、1行あたりのデータサイズが極端に大きい場合は、事前にデータを展開(FLATTEN)して保存するなど、物理的なパーティションを分割しておく必要があります。 -
データスキューの解消(肥大化の回避)
特定のパーティションだけサイズが肥大化している場合、そのパーティションを担当するスレッドの処理が終わるまでクエリ全体が完了しません。適切なクラスタリングや、クエリ実行時のランダムソートによる分散処理など、スレッド間の負荷を均一化することが、スケールアップを有効に機能させるための前提条件となります。
参考