次の方法で共有


Azure Data Lake Tools for Visual Studio を使用して Azure Data Lake Analytics のデータ スキューの問題を解決する

重要

Azure Data Lake Analytics は 2024 年 2 月 29 日に廃止されました。 このお知らせで詳細を学びましょう

データ分析の場合、組織は Azure Synapse Analytics または Microsoft Fabric を使用できます。

データ スキューとは

簡潔に説明すると、データ スキューは過剰に表現された値です。 税申告を監査するために 50 人の税務調査官を割り当て、米国の州ごとに 1 人の審査官を割り当てたとします。 ワイオミング州の審査官は、人口が少ないため、ほとんど行う必要はありません。 しかし、カリフォルニア州では、州の人口が多いため、審査官は忙しい状態が続いています。

データの大部分がカテゴリ間で均等に分散されるのではなく、2 つの列にグループ化されていることを示すサンプル縦棒グラフ。

このシナリオでは、データはすべての税務調査官に均等に分散されません。つまり、一部の審査官は他の審査官よりも多くの作業を行う必要があります。 自分の仕事では、ここで税務調査官の例のような状況が頻繁に発生します。 より技術的な用語では、1つのヴァーテックスが他のピアよりもはるかに多くのデータを取得します。これは、そのヴァーテックスが他よりも多くの作業をすることになり、最終的にはジョブ全体の速度が低下する状況です。 さらに悪いことに、頂点には 5 時間のランタイム制限や 6 GB のメモリ制限があるため、ジョブが失敗する可能性があります。

データ スキューの問題の解決

Azure Data Lake Tools for Visual Studio と Visual Studio Code は、ジョブにデータ スキューの問題があるかどうかを検出するのに役立ちます。

問題が存在する場合は、このセクションの解決策を試して解決できます。

解決策 1: テーブルのパーティション分割を改善する

オプション 1: 偏ったキー値を事前にフィルター処理する

ビジネス ロジックに影響しない場合は、より高い頻度の値を事前にフィルター処理できます。 たとえば、列 GUID に 000-000-000 が多い場合は、その値を集計したくない場合があります。 集計する前に、"WHERE GUID != "000-000-000" を記述して、高頻度の値をフィルター処理できます。

オプション 2: 別のパーティションまたは分散キーを選択する

前の例では、国/地域全体で税監査ワークロードのみを確認する場合は、キーとして ID 番号を選択してデータの分布を改善できます。 別のパーティションまたは分散キーを選択すると、データがより均等に分散される場合がありますが、この選択がビジネス ロジックに影響しないようにする必要があります。 たとえば、州ごとの税合計を計算するには、パーティション キーとして State を指定できます。 この問題が引き続き発生する場合は、オプション 3 を使用してみてください。

オプション 3: パーティション キーまたはディストリビューション キーを追加する

パーティション キーとして State のみを使用する代わりに、パーティション分割に複数のキーを使用できます。 たとえば、データ パーティション のサイズを減らし、データをより均等に分散するために、別のパーティション キーとして ZIP コード を追加することを検討してください。

オプション 4: ラウンドロビン方式を使用する

パーティションとディストリビューションに適切なキーが見つからない場合は、ラウンドロビン分散を使用してみてください。 ラウンドロビン分散では、すべての行が均等に処理され、ランダムに対応するバケットに配置されます。 データは均等に分散されますが、ローカリティ情報が失われます。これは、一部の操作のジョブ パフォーマンスを低下させる可能性がある欠点です。 さらに、すでに傾斜したキーを対象に集計している場合でも、データスキューの問題は続きます。 ラウンド ロビン分散の詳細については、「 CREATE TABLE (U-SQL): スキーマを使用したテーブルの作成」の「U-SQL テーブルディストリビューション」セクションを参照してください。

解決策 2: クエリ プランを改善する

オプション 1: CREATE STATISTICS ステートメントを使用する

U-SQL では、テーブルに対して CREATE STATISTICS ステートメントが提供されます。 このステートメントは、テーブルに格納されているデータ特性 (値の分布など) に関する詳細情報をクエリ オプティマイザーに提供します。 ほとんどのクエリでは、クエリ オプティマイザーによって、高品質のクエリ プランに必要な統計が既に生成されています。 場合によっては、CREATE STATISTICS を使用して統計を作成したり、クエリの設計を変更したりして、クエリのパフォーマンスを向上させる必要がある場合があります。 詳細については、 CREATE STATISTICS (U-SQL) ページを参照してください。

コード例:

CREATE STATISTICS IF NOT EXISTS stats_SampleTable_date ON SampleDB.dbo.SampleTable(date) WITH FULLSCAN;

統計情報は自動的には更新されません。 統計を再作成せずにテーブル内のデータを更新すると、クエリのパフォーマンスが低下する可能性があります。

オプション 2: SKEWFACTOR を使用する

各州の税を合計する場合は、GROUP BY 状態を使用する必要があります。これは、データ スキューの問題を回避しないアプローチです。 ただし、クエリにデータ ヒントを指定して、オプティマイザーが実行プランを準備できるように、キーのデータ スキューを識別できます。

通常、パラメーターを 0.5 と 1 に設定できます。0.5 は大きな傾斜ではなく、1 つは大きな傾斜を意味します。 ヒントは、現在のステートメントとすべてのダウンストリーム ステートメントの実行計画の最適化に影響するため、キーごとの集計が偏っている可能性がある前にヒントを必ず追加してください。

SKEWFACTOR (columns) = x

指定された列に 0 (スキューなし) から 1 (大きなスキュー) までのスキュー係数 x があることを示すヒントを提供します。

コード例:

//Add a SKEWFACTOR hint.
@Impressions =
    SELECT * FROM
    searchDM.SML.PageView(@start, @end) AS PageView
    OPTION(SKEWFACTOR(Query)=0.5)
    ;
//Query 1 for key: Query, ClientId
@Sessions =
    SELECT
        ClientId,
        Query,
        SUM(PageClicks) AS Clicks
    FROM
        @Impressions
    GROUP BY
        Query, ClientId
    ;
//Query 2 for Key: Query
@Display =
    SELECT * FROM @Sessions
        INNER JOIN @Campaigns
            ON @Sessions.Query == @Campaigns.Query
    ;

オプション 3: ROWCOUNT を使用する

SKEWFACTOR に加えて、特定の傾斜キー結合ケースに対して、他の結合された行セットが小さいことがわかっている場合は、JOIN の前に U-SQL ステートメントに ROWCOUNT ヒントを追加してオプティマイザーに指示できます。 これにより、オプティマイザーは、パフォーマンスの向上に役立つブロードキャスト参加戦略を選択できます。 ROWCOUNT ではデータ スキューの問題は解決されませんが、追加のヘルプが提供される可能性があることに注意してください。

OPTION(ROWCOUNT = n)

推定整数行数を指定して、JOIN の前に小さな行セットを識別します。

コード例:

//Unstructured (24-hour daily log impressions)
@Huge   = EXTRACT ClientId int, ...
            FROM @"wasb://ads@wcentralus/2015/10/30/{*}.nif"
            ;
//Small subset (that is, ForgetMe opt out)
@Small  = SELECT * FROM @Huge
            WHERE Bing.ForgetMe(x,y,z)
            OPTION(ROWCOUNT=500)
            ;
//Result (not enough information to determine simple broadcast JOIN)
@Remove = SELECT * FROM Bing.Sessions
            INNER JOIN @Small ON Sessions.Client == @Small.Client
            ;

解決策 3: ユーザー定義レジューサーとコンバイナーを改善する

複雑なプロセス ロジックを処理するユーザー定義演算子を記述できることがあり、適切に記述されたレジューサーとコンバイナーによって、場合によってはデータ スキューの問題が軽減される場合があります。

オプション 1: 可能な場合は再帰的なリデューサーを使用する

既定では、ユーザー定義レジューサーは非回復モードで実行されます。つまり、キーの reduce 処理は 1 つの頂点に分散されます。 ただし、データが偏っている場合は、巨大なデータ セットが 1 つの頂点で処理され、長時間実行される可能性があります。

パフォーマンスを向上させるために、コードに属性を追加して、再帰モードで実行するレジューサーを定義できます。 その後、膨大なデータ セットを複数の頂点に分散し、並列で実行できるため、ジョブが高速化されます。

非回復性レジューサーを再帰的に変更するには、アルゴリズムが連想的であることを確認する必要があります。 たとえば、合計は結合的であり、中央値は結合的ではありません。 また、レジューサーの入力と出力が同じスキーマを保持していることを確認する必要があります。

再帰リデューサーの属性:

[SqlUserDefinedReducer(IsRecursive = true)]

コード例:

[SqlUserDefinedReducer(IsRecursive = true)]
public class TopNReducer : IReducer
{
    public override IEnumerable<IRow>
        Reduce(IRowset input, IUpdatableRow output)
    {
        //Your reducer code goes here.
    }
}

オプション 2: 可能な場合は行レベルコンバイナー モードを使用する

特定のスキューキー結合ケースの ROWCOUNT ヒントと同様に、コンバイナー モードでは、作業を同時に実行できるように、巨大な傾斜キー値セットを複数の頂点に分散しようとします。 コンバイナー モードではデータ スキューの問題を解決できませんが、巨大なスキュー キー値セットに対して追加のヘルプを提供できます。

既定では、コンバイナー モードは Full です。つまり、左側の行セットと右の行セットを区切ることはできません。 モードを Left/Right/Inner に設定すると、行レベルの結合が有効になります。 システムは、対応する行セットを分離し、並列で実行される複数の頂点に分散します。 ただし、コンバイナー モードを構成する前に、対応する行セットを分離できるように注意してください。

次の例は、左に区切られた行セットを示しています。 各出力行は左側の 1 つの入力行に依存し、同じキー値を持つ右側のすべての行に依存する可能性があります。 コンバイナー モードを左に設定すると、システムは巨大な左行セットを小さな行に分割し、複数の頂点に割り当てます。

左と右のデータセットを表す 2 列の行。左側のデータ セットの最初のグループに移動されている右側のデータ セットの行がいくつか表示されます。

間違ったコンバイナー モードを設定すると、組み合わせの効率が低下し、結果が間違っている可能性があります。

コンバイナー モードの属性:

  • SqlUserDefinedCombiner(Mode=CombinerMode.Full): すべての出力行は、同じキー値を持つ左右のすべての入力行に依存する可能性があります。

  • SqlUserDefinedCombiner(Mode=CombinerMode.Left): すべての出力行は、左側の 1 つの入力行 (および同じキー値を持つ右側のすべての行) に依存します。

  • qlUserDefinedCombiner(Mode=CombinerMode.Right): すべての出力行は、右側の 1 つの入力行 (および同じキー値を持つ左側のすべての行) に依存します。

  • SqlUserDefinedCombiner(Mode=CombinerMode.Inner): すべての出力行は、同じ値を持つ左と右の 1 つの入力行に依存します。

コード例:

[SqlUserDefinedCombiner(Mode = CombinerMode.Right)]
public class WatsonDedupCombiner : ICombiner
{
    public override IEnumerable<IRow>
        Combine(IRowset left, IRowset right, IUpdatableRow output)
    {
    //Your combiner code goes here.
    }
}