各々のワーカーは完了すべきプランのパラレル部分を実行するので、単に通常のクエリプランを適用し、複数のワーカーを使って実行することはできません。 それぞれのワーカーが結果セットの全体のコピーを生成するので、クエリは通常よりも決して速くなりませんし、不正な結果を生成してしまいます。 そうではなくて、プランのパラレル部分は、クエリオプティマイザの内部で部分プランとして知られているものでなくてはなりません。 すなわち、プランを実行する各プロセスが、要求される個々の出力行が、協調動作するプロセスの正確に1個だけによって生成されることが保証されているような方法で、出力行の一部だけを生成します。 一般に、これはクエリの処理対象のテーブルに対するスキャンは、パラレル対応のスキャンでなければならないことを意味します。
今のところ、次に示すパラレル対応のテーブルスキャンがサポートされています。
パラレルシーケンシャルスキャンでは、テーブルのブロックは、協調するプロセスに分割して割り当てられます。 ブロックは一度に1個ずつ処理され、テーブルへのアクセスは逐次のままです。
パラレルビットマップヒープスキャンでは、一つのプロセスがリーダーに選ばれます。 そのプロセスは、一つ以上のインデックスをスキャンし、アクセスする必要のあるブロックを示すビットマップを作成します。 次にこれらのブロックは、パラレルシーケンシャルスキャン同様、協調するプロセスに割り当てられます。 つまり、ヒープスキャンは並列であるものの、対応するインデックスのスキャンは並列ではありません。
パラレルインデックススキャンあるいはパラレルインデックスオンリースキャンでは、協調するプロセスは、交代でインデックスからデータを読み込みます。 今のところ、パラレルインデックススキャンは、btreeインデックスのみでサポートされています。 個々のプロセスは単一のインデックスブロックを要求し、スキャンしてそのブロックから参照されているすべてのタプルを返却します。 他のプロセスは同時に他のインデックスからタプルを返却することができます。 並列btreeスキャンの結果は、ワーカー内におけるソート順の結果で返却されます。
btree以外のインデックススキャンのような他のスキャンタイプは、将来パラレルスキャンをサポートするかもしれません。
非パラレルプランと同様、処理対象のテーブルは、1個以上の他のテーブルとネステッドループ、ハッシュ結合、マージ結合で結合することができます。 結合の内側は、パラレルワーカー中で実行しても安全だという条件下で、プランナがサポートするどのような非パラレルプランであっても構いません。 結合タイプによっては内側がパラレルプランであってもよいです。
ネステッドループ結合では、内側は常に非パラレルです。 外側タプルとこのようなインデックスで値を探すループは共同するプロセス間で分割されるので、全体で実行されても内側がインデックススキャンであるなら、これは効率的です。
マージ結合では、内側は常に非パラレルプランで、それゆえに全体で実行されます。 特にソート実行を要する場合、全ての共同プロセスで処理と結果データが重複するので、これは非効率的と考えられます。
(parallelが付かない)ハッシュ結合では、内側は全ての共同プロセスがハッシュテーブルの同じコピーを作ることで、全体で実行されます。 ハッシュテーブルが大きかったり、そのプランが高価である場合、これは非効率的と考えられます。 パラレルハッシュ結合では、内側は共有ハッシュテーブルの構築処理を共同プロセス間で分割するパラレルハッシュです。
PostgreSQLは、ふたつのステージで集約処理を行うことによってパラレル集約処理をサポートします。
まず、クエリのパラレル部分に参加している個々のプロセスが集約ステップを実行し、それぞれのプロセスが認識しているグループに対する部分的な結果を生成します。
これはPartial Aggregate
ノードとしてプラン中に反映されています。
次に、Gather
またはGather Merge
ノードを通じて部分的な結果がリーダーに転送されます。
最後に、リーダーは、すべてのワーカーにまたがる結果を再集約して、最終的な結果を生成します。
これは、Finalize Aggregate
ノードとしてプラン中に反映されています。
Finalize Aggregate
ノードはリーダープロセスで実行されるので、入力行数の割には、比較的多数のグループを生成するクエリは、クエリプランナはあまり好ましくないものとして認識します。
たとえば最悪の場合、Finalize Aggregate
ノードが認識するグループ数は、Partial Aggregate
ですべてのワーカープロセスが認識する入力行数と同じだけの数になります。
こうした場合には、明らかにパラレル集約を利用する性能上の利点がないことになります。
クエリプランナはプラン処理中にこれを考慮するので、このシナリオでパラレル集約を採用することはまずありません。
どんな状況でもパラレル集約がサポートされているわけではありません。
個々の集約は並列処理安全で、結合関数(combine function)を持っていなければなりません。
その集約がinternal
型の遷移状態を持っているならば、シリアライズ関数とデシリアライズ関数を持っていなければなりません。
更なる詳細はCREATE AGGREGATEをご覧ください。
パラレル集約は、集約関数呼び出しがDISTINCT
あるいはORDER BY
句を含む場合、また 順序集合集約、あるいはクエリがGROUPING SETS
を実行する場合にはサポートされません。
パラレル集約は、クエリの中で実行されるすべての結合が、プラン中の並列実行部分の一部であるときにのみ利用できます。
PostgreSQLが複数のソースから一つの結果セットへの行の連結を必要とするときはいつでも、Append
またはMergeAppend
プランノードが使われます。
これは一般にUNION ALL
を実施するときや、パーティションテーブルをスキャンするときに発生します。
他のプランと同様にこのようなノードをパラレルプランで使うことができます。
しかしながら、パラレルプランではプランナは代わりにParallel Append
ノードを使ってもよいです。
Append
ノードがパラレルプランで使われるとき、各プロセスは子プランをそれらの出現順に実行します。そのため、全ての参加しているプロセスは共同して最初の子プランを完了するまで実行して、その後、一斉に次プランに移ります。
代わりにParallel Append
が使われるときには、エクゼキュータは逆に参加しているプロセスを各子プランにできるだけ均等に分散させます。そのため、複数の子プランは同時並行に実行されます。
これは競合を回避し、また、プランを実行することのないプロセスで子プランの開始コストが生じることも回避します。
また、パラレルプランの中で使われるときだけ部分的な子プランを持てる、通常のAppend
ノードと違い、Parallel Append
ノードは部分的、非部分的のどちらの子プランも持つことができます。
複数回のスキャンは重複した結果をもたらすため、非部分的な子プランは単一プロセスのみからスキャンされます。
複数の結果セットの連結に関わるプランは、効率的なパラレルプランが不可能なときでも、それゆえ粗い並列性を実現できます。
例えば、パラレルスキャンをサポートしないインデックスを使うことでのみ効率的に実行できるパーティションテーブルに対する問い合わせを考えてください。
プランナは通常のIndex Scan
プランのParallel Append
を選ぶことができます。
個々のインデックススキャンは単一プロセスで最後まで実行しなければなりませんが、別のスキャンは同時に別プロセスで実行することができます。
本機能を無効にするためにenable_parallel_appendを使用できます。
パラレルプランを生成すると期待していたクエリがそうならない場合には、parallel_setup_costまたはparallel_tuple_costを減らしてみてください。 もちろん、このプランは結局のところ、プランナが選択した順次実行プランよりも遅いということもあり得ますが、いつもそうだとは限りません。 これらの設定値を非常に小さく(つまり両方とも0に)したにも関わらずパラレルプランを得られない場合、あなたのクエリのためにクエリプランナがパラレルプランを生成できない何か理由があるのかもしれません。 そうしたケースに該当しているかどうかを、15.2と15.4を参照して確認してください。
パラレルプランを実行する際には、EXPLAIN (ANALYZE, VERBOSE)
を使って個々のプランノードに対するワーカーごとの状態を表示することができます。
これは、すべてのプランノードに均等に仕事が分散されているかどうかを確認すること、そしてもっと一般的には、プランの性能特性を理解するのに役に立つかもしれません。