CXPACKET 待ちは悪いことか?


 

神谷 雅紀
Escalation Engineer

 

パフォーマンスの問題が発生した場合などに、sys.dm_exec_requests.wait_type や sys.dm_os_wait_stats.wait_type を確認することは多いかと思います。これら DMV の結果に示される待機状態の中に CXPACKET というものがあります。この CXPACKET での待機時間が長いことが問題として着目される場合がありますが、その多くは、実際には問題を示してはいません。Books Online sys.dm_os_wait_stats (Transact-SQL) の説明が非常にあっさりとしていることも原因のひとつかもしれませんが、CXPACKET 待ちが悪いことのように誤解されている場面をよく見かけるため、今回は、この CXPACKET 待ちについて説明します。

 

CXPACKET とは?

CXPACKET とは、並列クエリを実行するスレッド間のデータ受け渡しに使用されるパケット (Exchange Packet) のことです。では、待機状態が CXPACKET とはどういった状態でしょうか?並列クエリを実行するスレッドが、パケットを待っている状態です。この状態には 2 種類あります。空のパケットを待っている場合とデータに入ったパケットを待っている場合です。

では、それがどのように発生するのか、具体的に見ていきましょう。

 

並列クエリにおけるスレッド間のデータ受け渡しがどのように行われるのか

この図は、Books Online から抜粋した並列クエリの処理例です。

 

parallelquery

「ワークフロー」の矢印は、データの渡される方向を示しています。データは下から上に渡され、途中、フィルタされたり、結合されたり、加工されたりしながら、最上位のオペレータである Stream Aggregate まで渡されます。Stream Aggregate の演算結果が、クエリの最終結果となります。

この中には、3 つの並列 (Parallelism) オペレータがあります。Gather Streams が 1 つと Repartition Streams が 2 つです。これらのオペレータの上下に別々のスレッドがあります。具体的には、Stream Aggregate を処理するスレッドが 1 つ (これがクエリのメインスレッドになります)、その下の Gather Streams から Repartition Streams までを処理するスレッドが 4 つ、左側の Repartition Streams 以下でテーブル 1 の検索を行っているスレッドが 4 つ、右側の Repartition Streams 以下でテーブル 2 の検索を行っているスレッドが 4 つの合計 13 スレッドです。これらのスレッドは、Gather Streams, Repartition Streams オペレータ上でデータの受け渡しを行います。

以下は、並列 (Parallelism) オペレータでのデータ受け渡しの概念図です。Parallelism オペレータでは、データを受け取る側のスレッドである Consumer スレッド (上の図では Parallelism オペレータの上側のスレッド) とデータを渡す側である Producer スレッド (下側のスレッド) によりデータの受け渡しを行われます。

 

cxpacket

Producer スレッドは、空きパケットリストから空のパケットを取り出し、そのパケットの中にデータを入れます。パケットがいっぱいになると、そのパケットを Consumer リストに入れます。Consumer スレッドは、Consumer リストから、データの入ったパケットを取り出し、パケット内のデータを上位のオペレータに渡します。パケット内のすべてのデータを処理し終わると、そのパケットを空きパケットリストへ入れます。すべてのデータが処理されるまで、これが繰り返されます。

 

CXPACKET 待ちの発生メカニズム

例えば、Producer スレッドが、空きパケットリストからパケットを取り出してパケットにデータを入れるという処理をしなくなったとします。このような状況は、ロック獲得待ちでテーブルから行が取り出せない場合などに発生します。この状態で、Consumer スレッドがデータの入ったパケットすべてを処理し、Consumer リストにはパケットがひとつもない状態になると、Consumer スレッドは CXPACKET 待ちの状態になります。

反対に、Consumer スレッドが Consumer リストからパケットを取り出して処理しなくなったとします。例えば、テーブル結合を実行していて、テーブル A での検索がロック獲得待ちになったような場合、そのテーブル A から行を取り出せなくなるため、テーブル A を検索している Producer スレッド X は、パケットにデータを入れることができなくなります。Producer スレッド X からのパケットを待つ Consumer Y スレッドは CXPACKET 待ちになります。一方で、もう片方のテーブル B のデータが入っているパケットも、それを処理する Consumer スレッド Y が待機状態に入ってしまうため、処理されなくなります。その間にもテーブル B で処理を行っている Producer スレッド Z による処理は続けられるため、すべてのパケットにデータが入れられ、空きパケットリストにパケットがない状態になります。この状態は、Producer スレッド Z の CXPACKET 待ちです。

もう一度、最初の図を見てみましょう。

クエリの実行が開始されると、まず、Stream Aggregate を実行する最上位のスレッドは、下位のスレッドからデータが来るのを待つことになります。最初のデータが到着するまでは、CXPACKET 待ちです。下位のスレッドが処理するオペレータの中には Sort があります。Sort は、データを指定された順番に並べ替えます。つまり、Sort が上位のオペレータにデータを返せるようになるのは、すべてのデータを読み取り、それを指定された順番に並び替えた後です。そのため、このクエリでは、メインスレッドが CXPACKET で待機する時間は、データ量が多ければ多いほど長くなります。また、途中に Merge Join があります。Merge Join は、Inner Join (内部結合) 操作であれば、2 つの入力を比較し、一致した行を上位のオペレータに返します。一致しなければ、キー値の小さい方の入力からデータを読み取ります。テーブル 1 側の入力には、Sort が含まれているため、テーブル 2 側のデータが Merge Join によって処理されるのは、少なくとも、テーブル 1 側のすべてのデータが読み取られ、Sort によって並び替えられた後です。そのため、その間、テーブル 2 側の Producer スレッドは、CXPACKET 待ちになります。

これらで見られる CXPACKET 待ちは、何ら異常な状態を示すものではなく、通常の正常な動作の中で見られるものです。

 

CXPACKET 待ちが問題を示しているのかどうかを判断するためには

CXPACKET 待ちが問題を示しているのかどうかを判断するためには、クエリの実行プランを確認し、そのクエリがどのように実行されているのかを把握すること、また、そのクエリを実行しているすべてのスレッドのそれぞれの状態を確認することが必要です。

wait_type が CXPACKET で wait_time が大きいということだけで、それは問題であると結論付けるのは、問題ではないものを問題として取り扱ってしまう可能性があり、その結果、意味のない無駄な作業を行ってしまう可能性があります。

Comments (0)

Skip to main content