オープンソース時系列データベースの分析 - Part 4

この記事では、人気のある**オープンソースの時系列データベースエンジン**の時系列データの保存と計算能力を分析しています。

周 肇峰著

#Prometheus
オープンソースの時系列データベースの分析に関する一連の記事が完成しました。これらの記事では、HbaseベースのOpenTSDB、CassandraベースのKairosDB、BlueFloodとHeroic、そして最後にTSDBの中で1位のInfluxDBを比較・分析しています。InfluxDBは純粋に自社開発のTSDBです。その関連資料をレビューする際に、私が興味を持ったのは、LSMの考え方に基づいて時系列データのシナリオに最適化されたストレージエンジンであるボトムレイヤTSMの方でした。資料では、InfluxDBの初期使用からLevelDBからBoltDBへの置き換え、そして最終的に自社開発TSMの決定に至るまでの全過程が共有されており、各ステージの苦悩点や次のステージへの移行時に解決すべき核心的な問題点、最終的なTSMの核心的な設計思想が深く描かれています。どの技術がベストかを直接伝えるのではなく、技術の進化の全過程を一歩一歩教えてくれるこの手の共有が好きです。各段階での問題点や、最終的な技術選択の理由などを深く分析しているところが印象的で、学ぶことが多いです。

しかし、InfluxDBのTSMについてはあまり詳しく書かれておらず、どちらかというとポリシーや振る舞いの記述に重点が置かれています。最近、「[Writing a Time Series Database from Scratch](https://fabxc.org/tsdb/?spm=a2c65.11461447.0.0.7e8625b3k5C6EF)」という記事を目にしました。タイトルとはやや矛盾する内容ではありますが、TSDBのストレージエンジンの設計思想を記述した、本当に役立つスキルや体験談が紹介されています。また、このストレージエンジンはコンセプトや玩具ではなく、実際に本番に応用されています。それは、2017年11月にリリースされたPrometheus 2.0で完全に書き換えられた新しいストレージエンジンです。この新しいストレージエンジンは、「巨大なパフォーマンスの向上」をもたらしたと主張しています。


#データモデル
Prometheusは、他の主流の時系列データベース（TSDB）と同様に、データモデルの定義にメトリック名、ラベル（タグに似たもの）、およびメトリック値を使用します。すべての時系列は、メトリック名とラベルとも呼ばれるキーと値のペアのセットによって一意に識別されます。Prometheusは、単純な条件と複雑な条件を使用して、ラベルに基づいた時系列の問い合わせをサポートしています。ストレージエンジンの設計では、主にデータの保存（読み取りよりも書き込みの方が多い）、データの保持、時系列データの特性に基づくデータの問い合わせを考慮しています。Prometheusはまだデータ分析には関与していません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/4d08df91-90e4-a834-8878-9c2f696d63d2.png)


上の写真は、横軸が時間、縦軸がタイムラインで、データポイントの分布を単純に見たものです。領域内の点がデータ点です。プロメテウスはデータを取得するたびに、図のように領域内の縦線を受け取ります。各タイムラインは毎回1つのデータポイントしか生成しませんが、タイムポイントを同時に生成するタイムラインはたくさんあります。これらのデータポイントを繋げていくと、縦の線が出てきます。この機能は、データの書き込みや圧縮の最適化戦略に影響するので、非常に重要です。

#V2ストレージエンジン
本記事では、主にV2ストレージエンジンの最新版であるV3ストレージエンジンの設計思想の一部を紹介します。V2ストレージエンジンは、各タイムラインで生成されたデータポイントを別々のファイルに格納します。この設計に基づいていくつかの話題を取り上げます。

1、書き込みの最適化：HDDやSSDの書き込みパターンとしては、シーケンシャルライトやバッチライトが理想的です。上述したように、プロメテウスが毎回受信するデータは、多くのデータポイントからなる縦線です。しかし、これらのデータポイントは異なるタイムラインに属しています。現在の設計では、各タイムラインのデータは別のファイルに格納されています。そのため、毎回多くの異なるファイルにごく少量のデータを書き込む必要があります。この問題を解決するために、V2ストレージエンジンの最適化戦略は、より大きなデータの塊を順番に書き込むことです。1つのタイムラインで生成されたデータは、バッチで書き込まなければなりません。そのためには、タイムライン次元で一定期間、一定量のデータポイントを蓄積する必要があります。バッチライトに加えて、より大きなチャンクでデータを書き込むメリットとして、ホットデータクエリの効率とデータ圧縮率が最適化されることが挙げられます。V2ストレージエンジンは、Facebook Gorillaと同じ圧縮アルゴリズムを採用しており、16バイトのデータポイントを平均1.37バイトに圧縮し、メモリとディスク容量を12倍に節約しています。データをチャンクで書き込むには、データをサーバーのメモリに一定時間蓄積する必要があります。つまり、ホットデータは基本的にメモリに蓄積されるため、非常に高いクエリ効率を実現しています。

2、クエリの最適化：時系列データに対しては、様々なクエリシナリオがあります。ある時間軸の特定の時間点のデータ、複数の時間軸の複数の時間点のデータ、またはある期間の複数の時間軸のデータに対してクエリを行うことがあります。上記データモデル図に示すように、クエリは、２次元平面上の長方形のデータブロックです。SSDでもHDDでも、ディスクデータの読みやすさの最適化は、ディスク上の数箇所のランダムな場所だけを読み込んで各クエリを完了させ、大きな塊のデータを順番に読み込んでいくことを可能にする必要があります。これは、ディスク内のデータ分布と密接に関係しています。本質的には、データの書き込みに関係しています。しかし、必ずしもリアルタイムの書き込み最適化である必要はありません。後続のデータ照合によって最適化することができます。
V2ストレージエンジンは、いくつかの優れた最適化戦略を提供しており、その中には主に、より大きなチャンクでのデータ書き込みとホットデータメモリキャッシングが含まれています。これら2つの最適化戦略はV3に受け継がれます。また、V2には多くの欠点があります。

- タイムラインの数が増えるとファイルの数が増え、遅かれ早かれinodeを使い果たしてしまう可能性があります。
- チャンクベースのアプローチでも、1回の書き込みに多くのタイムラインが関与している場合、IOPSの要件は依然として高くなります。
- すべてのファイルを読み書きのために常にオープンにしておくのは非現実的です。ファイルがクエリされると、大量のファイルを開いて、関連するデータポイントを見つけてメモリに読み込んで、再び閉じなければなりません。これでは、クエリの待ち時間が長くなってしまいます。
- データ削除では、大量のファイルをスキャンし、それらのファイルにデータを書き換えなければなりません。どちらもかなり長い時間がかかります。
- データの塊は、ディスクに書き込まれる前に一定期間メモリに蓄積される必要があります。V2では、チェックポイントを定期的に書き込む仕組みを利用して、メモリ内のデータが失われないようにしています。しかし、一般に、チェックポイントを記録する時間は、通常、許容されるデータ損失の時間窓よりも長く、さらにチェックポイントを復元するために必要な時間もかなり長いです。

タイムラインインデックスについては、V2のストレージエンジンでは、ラベルとタイムラインのマッピング関係をLevelDBで格納しています。タイムラインの数がある程度増えてくると、クエリ効率が非常に悪くなります。一般的に、タイムラインのベースとなる数は比較的少ないです。なぜなら、アプリケーション環境が変わることはほとんどなく、動作が安定しているときはタイムラインベース番号が安定しているからです。しかし、不適切なラベル設定では、例えば、動的な値（プログラムのバージョン番号など）をラベルとして使用している場合、アプリケーションがバージョンアップするたびにラベルの値が変化してしまいます。時間が経つにつれて、より多くの非アクティブなタイムラインが存在するようになります（Prometheus のコンテキストでは、シリーズ・チャーンとして知られています）。タイムラインのサイズはどんどん大きくなり、インデックスクエリの効率に影響を与えます。

#V3ストレージエンジン
V3エンジンは、V2エンジンの問題点を解決するために完全に再設計されました。V3エンジンは、時系列データのシナリオに最適化されたLSMの簡易版と見ることができます。LSMの設計思想で理解することができます。まず、V3エンジンのファイルディレクトリ構造を見てみましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/9a3478a1-2d87-61ab-07a3-c35c5b55d9b4.png)


すべてのデータはデータディレクトリに格納されています。データディレクトリの最上位には、先頭に 'b-' を付けた番号付きのブロックが並んでいます。各ブロックには、インデックス、チャンクディレクトリ、meta.jsonファイルを含むファイルが格納されています。各 "chunks "ディレクトリには、様々な系列のデータポイントの生のチャンクが格納されています。V3のチャンクはV2のチャンクと同じです。唯一の違いは、v3のチャンクには1つのタイムラインだけではなく、多くのタイムラインが含まれているということです。Indexはブロックの下にあるチャンクのインデックスで、ラベルに基づいてタイムラインとデータのチャンクを素早く配置することができます。 meta.jsonは、私たちのストレージの状態とそれに含まれるデータを簡単に理解するために、ブロックに関する人間が読めるような情報を単純に保持したシンプルな記述ファイルです。V3エンジンの設計を理解するためには、いくつかの質問を理解するだけでいいのです。 1、チャンクファイルのストレージ形式とは？ 2、インデックスの格納形式と高速検索を実現するには？ 3、 最後のブロックにチャンクではなく「wal」ディレクトリが含まれているのはなぜか？

#デザインのアイデア

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/88568b20-0562-a110-6ded-7a442f09d3ac.png)

Prometheusは、データを時間次元ごとに複数の重ならないブロックに分割します。各ブロックは、その時間ウィンドウの時系列データをすべて含む完全に独立したデータベースとして機能します。データの各ブロックは、チャンクファイルにダンプされた後は不変です。新しいデータは、最新のブロックにのみ書き込むことができます。すべての新しいデータはインメモリデータベースに書き込まれます。データ損失を防ぐために、すべての入力データは一時的な書き込み先ログ(WAL)にも書き込まれます。

V3はLSMの設計思想を完全に拝借しており、時系列データの特徴に基づいていくつかの最適化を行っています。多くの利点があります。

1、時間範囲のデータを照会する際に、無関係なブロックを素早く除外することができます。各ブロックは独立したインデックスを持っているので、V2の "Series Churn "問題を効果的に解決できます。
2、メモリ内のデータはチャンクファイルにダンプされるため、より大きなチャンクのデータを効率的に連続して書き込むことができ、SSDとHDDの両方に適しています。
3、V2と同様に、直近のデータはメモリ内に保持されます。直近のデータは、アクセス頻度の高い（ホットな）データでもあります。このデータをメモリに保持することで、最も効率的なデータクエリが可能になります。
4、古いデータの削除は非常にシンプルで効率的になります。必要なのは、少数のディレクトリを削除するだけです。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/e9f4d45b-82f5-dadf-8537-276d04975fb7.png)


V3では、ブロックは2時間の適度な間隔でカットされます。この間隔は長すぎても短すぎてもいけません。間隔が長くなると、2時間以上データを保持しなければならないため、より大きなメモリが必要になります。短い間隔ではブロックが多すぎて、必要以上に多くのファイルを照会しなければなりません。2時間の間隔は、総合的に考慮して決定します。しかし、より大きな時間範囲のデータをクエリする場合、複数のファイルをクエリすることは避けられません。例えば、1週間のクエリであれば、84個のファイルをクエリする必要があるかもしれません。LSMと同様に、V3ではクエリの最適化にコンパクション戦略を採用しています。小さなブロックをより大きなブロックにマージします。また、削除されたデータを削除したり、サンプルチャンクを再構築してクエリのパフォーマンスを向上させるなど、既存のデータを途中で修正することもできます。引用された記事には、V3のコンパクションについてはあまり書かれていません。興味があれば、InfluxDBがどのようにしてこれを実現しているのかを見てみましょう。InfluxDBには様々な状況に応じて適用可能なコンパクション戦略があります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/c2f95311-93a5-1184-25c1-91b68e7798a2.png)


上の例はV3での期限切れデータ削除の模式図で、V2よりもはるかにシンプルです。データブロック全体の有効期限が切れている場合は、フォルダを直接削除することができます。しかし、部分的にしか期限切れデータがないブロックの場合、フォルダ全体を削除することはできません。すべてのデータの有効期限が切れるのを待つか、圧縮を行う必要があります。なお、データが古くなればなるほど、以前に圧縮したブロックを圧縮し続けるため、ブロックが大きくなる可能性があります。ブロックがデータベース全体にまたがって大きくならないように、上限を適用する必要があります。一般的に、この上限は保持ウィンドウに基づいて設定されます。

基本的に、プロメテウスのデータストレージの設計は以上です。比較的明確でシンプルです。他のTSDBと同様に、Prometheusにもインデックスがあります。V3のインデックス構造は比較的シンプルです。ここでは、引用記事で与えられた例を直接使ってみたいと思います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/6a2f7ed1-7b1f-64ce-93ac-67c075acdf95.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/dbabb818-f708-3a79-edd5-09cadc2bcdba.png)

その記事の記述からすると、V3はV2のようにLevelDBを使用していません。永続化されたブロックでは、インデックスは不変です。データを書き込む直近のブロックについては、V3はすべてのインデックスをメモリ内に保持し、インメモリ構造を維持します。これらのインデックスは、ブロックが閉じられたときにディスク上のファイルに永続化されます。これは非常に簡単です。タイムラインとID、ラベルとIDリストの間のマッピング関係を維持することで、高いクエリ効率を確保することができます。プロメテウスは、以下のような前提を持っています。「実世界のデータセットでは、約440万件のシリーズ、約12個のラベルがあり、それぞれのラベルが5,000個以下のユニークなラベルを持っている」と仮定しています。これらはすべて、驚くほど少量のメモリを占有します。InfluxDBも同様の戦略を採用していますが、他のTSDBはインデックスエンジンとしてElasticSearchを使用しています。

#概要
読み込みよりも書き込みの方が多い時系列データの場合、LSMストレージエンジンには多くのメリットがあります。HbaseやCassandraのようなTSDBの中には、オープンソースのLSMエンジンを直接ベースにした分散型データベースもあります。LevelDBやRocksDBをベースに開発されているものもあります。また、InfluxDBやPrometheusに代表される完全に自己開発されたTSDBもあります。これだけ多くの種類のTSDBが存在する理由は、時系列データの特定のシナリオを最適化するために、まだ多くのことができるからです。例えば、インデックスやコンパクション戦略。Prometheus V3エンジンの設計思想は、InfluxDBのそれと非常によく似ています。彼らの最適化のアイデアは非常に一貫性があります。新しい要件が出てきたら、さらに変更があるでしょう。

*本ブログは英語版からの翻訳です。オリジナルは[こちら](https://www.alibabacloud.com/blog/a-comprehensive-analysis-of-open-source-time-series-databases-4_594733?spm=a2c65.11461544.0.0.23e753559GvF6A)からご確認いただけます。一部機械翻訳を使用しております。翻訳の間違いがありましたら、ご指摘いただけると幸いです。*

*アリババクラウドは日本に２つのデータセンターを有し、世界で60を超えるアベラビリティーゾーンを有するアジア太平洋地域No.1(2019ガートナー)のクラウドインフラ事業者です。
アリババクラウドの詳細は、こちらからご覧ください。
[アリババクラウドジャパン公式ページ](https://us.alibabacloud.com/)*