varbitチャンクを使用すべきとき(とそうでないとき)
2016年5月8日筆者: Björn “Beorn” Rabenstein
Prometheusサーバーに組み込まれている時系列データベース (TSDB) は、各時系列の生サンプルデータを、一定の1024バイトサイズのチャンクに整理します。生サンプルデータに加えて、チャンクにはいくつかのメタデータが含まれており、これにより各チャンクに異なるエンコーディングを選択できます。最も基本的な区別はエンコーディングのバージョンです。新規作成されるチャンクのバージョンは、コマンドラインフラグ-storage.local.chunk-encoding-version
で選択します。これまでは、元のデルタエンコーディング用のバージョン0と、改良されたダブルデルタエンコーディング用のバージョン1の2つしかサポートされていませんでした。リリース0.18.0で、バージョン2が追加されました。これもダブルデルタエンコーディングの一種です。チャンク内のサンプルごとに可変ビット幅を伴うため、varbitエンコーディングと呼んでいます。バージョン1はほぼすべての点でバージョン0よりも優れていますが、バージョン1と2の間には真のトレードオフがあります。このブログ記事は、その決定を下すのに役立ちます。バージョン1は引き続きデフォルトのエンコーディングであるため、この記事を読んだ後でバージョン2を試したい場合は、コマンドラインフラグで明示的に選択する必要があります。切り替えを繰り返しても問題ありませんが、既存のチャンクは一度作成されるとエンコーディングバージョンが変更されないことに注意してください。ただし、これらのチャンクは設定された保持期間に従って徐々に廃止され、コマンドラインフラグで指定されたエンコーディングを持つチャンクに置き換えられます。
varbitエンコーディングとは?
当初から、チャンク化されたサンプルストレージは新しいエンコーディングを簡単に追加できるように設計されていました。Facebookが彼らのインメモリTSDB Gorillaに関する論文を公開したとき、独立して開発されたGorillaとPrometheusのアプローチの間に多くの類似点があることに興味をそそられました。しかし、多くの根本的な違いもあり、Prometheusを改善するためにGorillaからインスピレーションを得られるかどうかを詳しく検討しました。
まれに自由な週末が目の前にあったとき、私は試してみることにしました。コーディングに没頭し、後に(かなりのテストとデバッグを経て)varbitエンコーディングとなるものを実装しました。
今後のブログ記事で、エンコーディングの技術的な詳細について説明します。今のところ、新しいvarbitエンコーディングと従来のダブルデルタエンコーディングのどちらを選ぶかについて、いくつかの特徴だけを知っておく必要があります。(後者を今後は単に「ダブルデルタエンコーディング」と呼びますが、varbitエンコーディングもダブルデルタを使用しますが、その方法は異なります。)
varbitエンコーディングの利点とは?
簡単に言えば、はるかに優れた圧縮率を提供します。ダブルデルタエンコーディングが実データセットでサンプルあたり約3.3バイトを必要とするのに対し、varbitエンコーディングはSoundCloudの典型的な大規模本番サーバーでサンプルあたり1.28バイトまで削減されました。これはほぼ3倍のスペース効率であり、(Gorillaで報告されたサンプルあたり1.37バイトよりもわずかに優れていますが、SoundCloudの典型的なデータセットはFacebookの典型的なデータセットとは異なる可能性があるため、これは控えめに見てください)。
その意味を考えてみてください。RAMに3倍のサンプル、ディスクに3倍のサンプル、ディスク操作は3分の1。そして、ディスク操作が現在の取り込み速度のボトルネックであるため、取り込み速度も3倍速くなります。実際、最近報告された毎秒800,000サンプルの新しい取り込み記録は、varbitチャンクとSSDがなければ不可能でした。スピニングディスクではボトルネックがはるかに早く到達するため、3倍の利益はさらに重要になります。
これらすべては、あまりにも良い話に聞こえます...
では、落とし穴はどこにあるのでしょうか?
一つには、varbitエンコーディングはより複雑です。そのため、値をエンコードおよびデコードするための計算コストが多少増加し、サンプルデータを書き込むまたは読み取るすべてのものに根本的に影響します。幸いなことに、これは通常、操作の総コストのごく一部にしか寄与しないものの比例的な増加に過ぎません。
varbitエンコーディングのもう一つの特性は、潜在的により重要です。varbitチャンク内のサンプルは順次アクセスしかできませんが、ダブルデルタエンコードされたチャンク内のサンプルはインデックスによるランダムアクセスが可能です。Prometheusでの書き込みは追加のみであるため、異なるアクセスパターンはサンプルデータの読み取りにのみ影響します。実際の効果は、元のPromQLクエリの性質に大きく依存します。
比較的無害なケースは、時間間隔内のすべてのサンプルを取得する場合です。これは、範囲セレクターを評価したり、スクレイピング頻度と類似した解像度でダッシュボードをレンダリングしたりする場合に発生します。Prometheusストレージエンジンは、間隔の開始点を見つける必要があります。ダブルデルタチャンクではバイナリ検索を実行できますが、varbitチャンクでは順次スキャンを行う必要があります。ただし、開始点が見つかれば、間隔内の残りのすべてのサンプルはとにかく順次デコードする必要があり、varbitエンコーディングではわずかにコストが増加するだけです。
チャンクから隣接しない少数のサンプルを取得する場合や、いわゆるインスタントクエリで単一のサンプルを平易に取得する場合、トレードオフは異なります。ストレージエンジンは、返すべき少数のサンプルを見つけるために、多くのサンプルを繰り返し処理する必要がある可能性があります。幸いなことに、インスタントクエリの最も一般的なソースは、関係する各時系列の最新のサンプルを参照するルール評価です。偶然ではないですが、私は最近、時系列の最新サンプルの取得を改善しました。基本的に、時系列に追加された最後のサンプルはキャッシュされるようになりました。時系列の最新サンプルのみが必要なクエリは、もはやチャンク層に到達せず、その場合、チャンクエンコーディングは関係ありません。
インスタントクエリが過去のサンプルを参照し、チャンク層にヒットする必要がある場合でも、ほとんどの場合、インデックスルックアップのようなクエリの他の部分が合計クエリ時間を支配します。しかし、varbitチャンクが必要とするシーケンシャルアクセスパターンが非常に重要になる実際のクエリも存在します。
varbitチャンクにとって最悪のクエリとは?
varbitチャンクにとって最悪のケースは、非常に長い時系列の各チャンクの途中から1つのサンプルだけが必要な場合です。残念ながら、これには実際のユースケースがあります。時系列が十分にうまく圧縮され、各チャンクが約8時間持続すると仮定しましょう。これは1日あたり約3チャンク、1ヶ月あたり約100チャンクです。過去1ヶ月の時系列を100データポイントの解像度で表示するダッシュボードがある場合、そのダッシュボードは100個の異なるチャンクから単一のサンプルを取得するクエリを実行します。それでも、チャンクエンコーディングの違いは、クエリ実行時間の他の部分によって支配されます。状況にもよりますが、私の推測では、そのクエリはダブルデルタエンコーディングでは50ミリ秒、varbitエンコーディングでは100ミリ秒かかるかもしれません。
しかし、ダッシュボードのクエリが単一の時系列だけでなく、数千の時系列を統合する場合、アクセスするチャンクの数がそれに応じて倍増し、シーケンシャルスキャンのオーバーヘッドが支配的になります。(このようなクエリは好ましくないとされており、ダッシュボードなどで頻繁に使用される種類のクエリには記録ルールを使用することを通常推奨しています。) しかし、ダブルデルタエンコーディングでは、クエリ時間はまだ許容範囲内であったかもしれません(例えば1秒程度)。varbitエンコーディングに切り替えた後、同じクエリが数十秒かかる可能性があり、これはダッシュボードで望むものではありません。
経験則とは?
できるだけ簡単に言えば、ディスク容量にもディスク操作にも制限がない場合は、心配せずに従来のダブルデルタエンコーディングのデフォルトに固執してください。
しかし、より長い保持期間を希望する場合や、現在ディスク操作がボトルネックになっている場合は、新しいvarbitエンコーディングを試してみることをお勧めします。-storage.local.chunk-encoding-version=2
でPrometheusサーバーを起動し、varbitエンコーディングで十分な新しいチャンクが作成され、その効果を検証できるまでしばらく待ちます。クエリが許容できないほど遅くなっている場合は、記録ルールを使用して速度を向上できるかどうかを確認してください。おそらく、これらのクエリは古いダブルデルタエンコーディングでも大幅に改善されるでしょう。
varbitエンコーディングの内部動作に興味がある方は、遠くない将来の別のブログ記事にご期待ください。