ディスクへのスピル¶
概要¶
メモリ負荷の高い操作の場合、Prestoは中間操作の結果をディスクにオフロードできます。このメカニズムの目的は、クエリごとまたはノードごとの制限を超えるメモリ量を必要とするクエリのの処理を可能にすることです。
このメカニズムは、OSレベルのページスワップに似ています。ただし、Prestoの特定のニーズに対応するためにアプリケーションレベルで実装されています。
スピルに関連するプロパティについては、スピルプロパティで説明しています。
メモリ管理とスピル¶
デフォルトでは、クエリ処理によって要求されたメモリがセッションプロパティquery_max_memory
またはquery_max_memory_per_node
を超えた場合、Prestoはクエリを強制終了します。このメカニズムは、クエリへのメモリの割り当てにおける公平性を確保し、メモリ割り当てによって引き起こされるデッドロックを防ぎます。クラスターに多数の小さなクエリがある場合は効率的ですが、制限内に収まらない大きなクエリは強制終了されます。
この非効率性を克服するために、解放可能メモリの概念が導入されました。クエリは制限にカウントされないメモリを要求できますが、このメモリはメモリマネージャーによっていつでも解放される可能性があります。メモリが解放されると、クエリランナーは中間データをメモリからディスクにスピルし、後で処理を続行します。
実際には、クラスターがアイドル状態で、すべてのメモリが使用可能な場合、メモリ負荷の高いクエリはクラスター内のすべてのメモリを使用する可能性があります。一方、クラスターに空きメモリがあまりない場合、同じクエリは中間データのストレージとしてディスクを使用することを強制される可能性があります。ディスクへのスピルを強制されるクエリは、メモリ内で完全に実行されるクエリよりも桁違いに処理時間が長くなる可能性があります。
より大きなクエリがディスクへのスピルを利用できるようにしながら、より一貫したクエリパフォーマンスを実現するために、experimental.query-limit-spill-enabled
をtrue
に設定できます。このプロパティは、メモリプールが満杯でない場合でも、メモリ使用量がノードごとの合計メモリ制限を超えると、クエリのスピルをトリガーします。詳細については、スピルプロパティを参照してください。
ディスクへのスピルを有効にしても、すべてのメモリ負荷の高いクエリの処理が保証されるわけではありません。まだスピルをサポートしていないメモリ負荷の高い操作がいくつかあります。また、クエリランナーが中間データをすべてのチャンクがメモリに収まるほど小さく分割できない場合があり、ディスクからデータを読み込んでいるときにメモリ不足
エラーが発生する可能性があります。
解放可能メモリと予約プール¶
予約メモリプールと解放可能メモリの両方は、低メモリ状態に対処するように設計されています。ユーザーメモリプールが使い果たされると、単一のクエリが予約プールに昇格されます。その場合、そのクエリのみが進捗できるため、クラスターの同時実行性が低下します。解放可能メモリは、スピルをトリガーすることによってそれを防ごうとします。予約プールはquery.max-total-memory-per-node
サイズです。query.max-total-memory-per-node
がノードで使用可能な合計メモリと比較して大きい場合、一般的なメモリプールには大きなクエリを実行するのに十分なメモリがない可能性があります。スピルが有効になっている場合、ノードごとに大量のメモリを消費するクエリで過剰なスピルが発生します。これらのクエリは、予約プールで実行されるため、スピルが無効になっている場合のはるかに早く終了します。ただし、そうすることで、クラスターの同時実行性が大幅に低下する可能性もあります。このような状況では、experimental.reserved-pool-enabled
設定プロパティを使用して予約メモリプールを無効にすることをお勧めします。
スピルディスク容量¶
中間結果をディスクにスピルして戻すことは、IO操作の点でコストがかかります。したがって、スピルを使用するクエリは、ディスクによってスロットルされる可能性が高くなります。クエリのパフォーマンスを向上させるには、スピル用に別々のローカルデバイスに複数のパスを提供することをお勧めします(スピルプロパティのプロパティspiller-spill-path
)。
システムドライブは、特にJVMが実行され、ログが書き込まれているドライブには、スピルに使用しないでください。そうすると、クラスターが不安定になる可能性があります。さらに、構成されたスピルパスのディスク飽和を監視することをお勧めします。
Prestoはスピルパスを独立したディスクとして扱います(JBODを参照)ので、スピルにRAIDを使用する必要はありません。
スピル圧縮¶
スピル圧縮が有効になっている場合(スピルプロパティのspill-compression-enabled
プロパティ)、スピルされたページは、十分に圧縮可能な場合、exchange 圧縮と同じ実装を使用して圧縮されます。この機能を有効にすると、スピルされたページを圧縮および解凍するための追加のCPU負荷を犠牲にして、ディスクIOの量を削減できます。
スピル暗号化¶
スピル暗号化が有効になっている場合(スピルプロパティのspill-encryption-enabled
プロパティ)、スピルコンテンツはランダムに生成された(スピルファイルごと)秘密鍵で暗号化されます。これを有効にすると、ディスクへのスピル パフォーマンスは低下しますが、ディスクに書き込まれたファイルからスピルされたデータが回復されるのを防ぐことができます。
**注**:Javaの一部のディストリビューションには、使用できる暗号鍵の強度を制限するポリシーファイルが付属しています。スピル暗号化では256ビットAES鍵を使用するため、正しく動作するには、無制限強度のJCEポリシーファイルが必要になる場合があります。
サポートされる操作¶
すべての操作がディスクへのスピルをサポートしているわけではなく、それぞれがスピルを異なる方法で処理します。現在、このメカニズムは次の操作に実装されています。
結合¶
結合操作中に、結合されるテーブルの1つがメモリに格納されます。このテーブルはビルドテーブルと呼ばれます。他のテーブルの行はストリーミングされ、ビルドテーブルの行と一致する場合、次の操作に渡されます。結合の最もメモリ負荷の高い部分は、このビルドテーブルです。
タスクの同時実行数が1より大きい場合、ビルドテーブルはパーティション分割されます。パーティションの数は、task.concurrency
設定パラメータの値と同じです(タスクのプロパティを参照)。
ビルドテーブルがパーティション分割されている場合、ディスクへのスピル機構は、結合操作に必要なピークメモリ使用量を削減できます。クエリがメモリ制限に近づくと、ビルドテーブルのパーティションのサブセットが、同じパーティションに属する他のテーブルの行とともにディスクにスピルされます。スピルされるパーティションの数は、必要なディスク容量に影響します。
その後、スピルされたパーティションは、結合操作を完了するために1つずつ読み戻されます。
このメカニズムにより、結合演算子によって使用されるピークメモリを、最大のビルドテーブルパーティションのサイズまで削減できます。データの偏りがないと仮定すると、これはビルドテーブル全体のサイズの1 / task.concurrency
倍になります。
集計¶
集計関数は、値のグループに対して操作を実行し、1つの値を返します。集計対象のグループの数が多い場合、大量のメモリが必要になることがあります。ディスクへのスピルが有効になっている場合、メモリが不足すると、中間的な累積集計結果がディスクに書き込まれます。メモリが使用可能になると、それらはロードされてマージされます。
ウィンドウ¶
ウィンドウ関数は、行のグループに対して操作を実行し、行ごとに1つの値を返します。ウィンドウ内の行の数が多い場合、大量のメモリが必要になることがあります。ディスクへのスピルが有効になっている場合、メモリが不足すると、中間結果がディスクに書き込まれ、各ウィンドウが処理される際に読み戻されます。1つのウィンドウが大きすぎる場合、クエリはメモリ不足になる可能性があります。
順序付け¶
ソートする必要がある行が多い場合、順序付けには大量のメモリが使用される可能性があります。ディスクへのスピルが有効になっている場合、メモリが不足すると、ソートされた行がディスクに書き込まれ、後でメモリ内でマージされます。