これは、なにをしたくて書いたもの?
Prometheusのストレージまわりの、お勉強に、と。
Prometheusのデータ(TSDB)のSnapshotを取得して、リストアまで - CLOVER🍀
こちらの続きで、今度はストレージのドキュメントを読み、オプションについて見ていこうと思います。
対象とするPrometheusのバージョンは、2.9.2とします(ドキュメントは2.9)。
ちょっと調べてみると、過去の情報と現在のドキュメントの内容はそれなりに異なる箇所が多そうなので、その時点の
バージョンのドキュメントをちゃんと確認した方が良いと思います。
あくまで、現時点(2.9.2)での話として。
Prometheusのストレージ
Prometheusのストレージまわりのドキュメントは、こちらです。
見ていくと、ざっくりこんな感じのことが書かれています。
- Prometheusには、ローカルディスク上に持つ時系列データベースが含まれている
- オプションで、リモートのストレージシステムと統合することもできる
リモートとのストレージシステムと統合することについては、また機会を改めて見ていきましょう。
ここからは、ローカルストレージについて見ていきます。
ローカルストレージについて
Prometheusのローカルの時系列データベースは、データを独自のフォーマットでディスクに保存します。
ディスク上のレイアウトについてですが、以下のような要素で構成されるようです。
- 取得したデータは、2時間ごとのブロックにまとめられる
- 2時間ごとの各ブロックは、以下の内容を含む
また、Prometheusは収集したデータをすぐに永続化するのではなく、まずメモリ上に保持されています。
Prometheusがクラッシュしたりして再起動した場合は、WAL(Write Ahead Log)を使うことでクラッシュから保護されています。
WALは、以下の特徴を持ちます。
- 「wal」ディレクトリに128MBごとに保存されている
- 「wal」ディレクトリに含まれるファイルは、まだ圧縮されていないRawデータが含まれている
- このため、通常のブロックファイルよりかなり大きい
- Prometheusは最低3つのWALファイルを保持している
- 高トラフィックなサーバーでは、少なくとも2時間分のRawデータを保存する必要があるため、3つを超えるWALファイルができることがある
で、ドキュメントに書かれているディレクトリ構造がこちら。ここまで登場した要素が書かれている感じですね。
./data/01BKGV7JBM69T2G1BGBGM6KB12 ./data/01BKGV7JBM69T2G1BGBGM6KB12/meta.json ./data/01BKGTZQ1SYQJTR4PB43C8PD98 ./data/01BKGTZQ1SYQJTR4PB43C8PD98/meta.json ./data/01BKGTZQ1SYQJTR4PB43C8PD98/index ./data/01BKGTZQ1SYQJTR4PB43C8PD98/chunks ./data/01BKGTZQ1SYQJTR4PB43C8PD98/chunks/000001 ./data/01BKGTZQ1SYQJTR4PB43C8PD98/tombstones ./data/01BKGTZQ1HHWHV8FBJXW1Y3W0K ./data/01BKGTZQ1HHWHV8FBJXW1Y3W0K/meta.json ./data/01BKGV7JC0RY8A6MACW02A2PJD ./data/01BKGV7JC0RY8A6MACW02A2PJD/meta.json ./data/01BKGV7JC0RY8A6MACW02A2PJD/index ./data/01BKGV7JC0RY8A6MACW02A2PJD/chunks ./data/01BKGV7JC0RY8A6MACW02A2PJD/chunks/000001 ./data/01BKGV7JC0RY8A6MACW02A2PJD/tombstones ./data/wal/00000000 ./data/wal/00000001 ./data/wal/00000002
より詳細な内容を知りたければ、TSDBのドキュメントへ。
tsdb/README.md at v0.7.1 · prometheus/tsdb · GitHub
ちなみに、起動直後のdataディレクトリの中身は、こんな感じです。
$ find data -type f data/wal/00000000 data/lock
lockというファイルは出てきていませんでしたね。
$ ls -l data/lock -rw-r--r-- 1 xxxxx xxxxx 0 May 2 10:51 data/lock
0バイトのファイルです。
このファイルは、文字通り排他に使うファイルのようで、すでにPrometheusが起動した状態で、リッスンポートを
変えて起動したりしようとすると、ロックが取れずに起動に失敗します。
$ ./prometheus --web.listen-address="0.0.0.0:9091" ... level=error ts=2019-05-02T10:53:06.753Z caller=main.go:717 err="opening storage failed: lock DB directory: resource temporarily unavailable"
先に進みましょう。
最初の2時間のブロックは、最終的にはバックグラウンドでより長いブロックに圧縮されます。
ローカルストレージの制限
ローカルストレージの制限は、クラスタ化もされておらず、またレプリカも持たないことです。
なので、ディスクやノード障害対して、耐性がありません。スケーラブルでも耐久性もない、直近の短命な
スライディングウィンドウデータとして扱われるべきです。
耐久性の要件が厳しくなければ、ローカルストレージでも最大数年のデータを保存できるかもしれない、くらいのことが
書かれています…。
このあたりのことから、ローカルストレージのスケーラビリティと耐久性についての課題を解決するために、リモートの
ストレージシステムと統合する仕組みがある、ということみたいです。
ローカルストレージに関するオプション
ストレージに関するドキュメントの以下の部分に、ストレージに関して指定できるオプションが書かれています。
Prometheus自身のヘルプから、「storage」を含むオプションを表示してみると、
$ ./prometheus -h 2>&1 | grep storage --storage.tsdb.path="data/" Base path for metrics storage. --storage.tsdb.retention=STORAGE.TSDB.RETENTION storage. This flag has been deprecated, use "storage.tsdb.retention.time" instead --storage.tsdb.retention.time=STORAGE.TSDB.RETENTION.TIME How long to retain samples in storage. When "storage.tsdb.retention". If neither this flag nor "storage.tsdb.retention" nor "storage.tsdb.retention.size" is set, the --storage.tsdb.retention.size=STORAGE.TSDB.RETENTION.SIZE --storage.tsdb.no-lockfile --storage.tsdb.allow-overlapping-blocks --storage.remote.flush-deadline=<duration> --storage.remote.read-sample-limit=5e7 --storage.remote.read-concurrent-limit=10
「--storage.remote.〜」については、リモートストレージシステムとの統合に関するオプションなので、ここでは対象外。
その他のオプションについて、説明を見てみます。
--storage.tsdb.path="data/" Base path for metrics storage. --storage.tsdb.retention=STORAGE.TSDB.RETENTION [DEPRECATED] How long to retain samples in storage. This flag has been deprecated, use "storage.tsdb.retention.time" instead --storage.tsdb.retention.time=STORAGE.TSDB.RETENTION.TIME How long to retain samples in storage. When this flag is set it overrides "storage.tsdb.retention". If neither this flag nor "storage.tsdb.retention" nor "storage.tsdb.retention.size" is set, the retention time defaults to 15d. --storage.tsdb.retention.size=STORAGE.TSDB.RETENTION.SIZE [EXPERIMENTAL] Maximum number of bytes that can be stored for blocks. Units supported: KB, MB, GB, TB, PB. This flag is experimental and can be changed in future releases. --storage.tsdb.no-lockfile Do not create lockfile in data directory. --storage.tsdb.allow-overlapping-blocks [EXPERIMENTAL] Allow overlapping blocks which in-turn enables vertical compaction and vertical query merge
すでにDeprecatedなオプションは無視します。
- --storage.tsdb.path … Prometheusがデータを保存する際のベースパス。デフォルトは、「data」
- --storage.tsdb.retention.time … 古いデータをいつ削除するかを指定する。デフォルトは、「15d」
- --storage.tsdb.retention.size … (実験的)ストレージブロックが最大で使用できるサイズを指定する
- --storage.tsdb.no-lockfile … dataディレクトリ内に、ロックファイルを作成しない
- --storage.tsdb.allow-overlapping-blocks … (実験的)オーバーラップするブロックを許可する。圧縮、クエリーの縦方向のマージが可能になる
「--storage.tsdb.retention.time」のデフォルト値は15日で、フォーマットにはy、w、d、h、m、s、msが利用できます。
prometheus/db.go at v2.9.2 · prometheus/prometheus · GitHub
データの保持期間に関するリテンションポリシーは時間とサイズの2つがありますが、使われるのは最初に動作した方だとか。
※サイズはまだ実験的なので、スルーしましたが
「--storage.tsdb.allow-overlapping-blocks」については、まだ実験的な感じがするので、スルーします…。
Implement vertical query merging and compaction · Issue #90 · prometheus/tsdb · GitHub
なお、2時間ごとにブロックにまとめられる、というのは、このあたりから来ている気がします。
https://github.com/prometheus/prometheus/blob/v2.9.2/vendor/github.com/prometheus/tsdb/db.go#L51
ところでですね、PrometheusのWeb Consoleから「Status」→「Command-Line Flags」を見ると、もう少し指定できそうな
オプションが多いように見えるのですが…。
今回は、とりあえず深追いしない…。
ローカルストレージで必要とするディスク容量
Prometheusは、ひとつのサンプルあたり、1〜2バイトほどを使用するそうです。
なので、Prometheusが要求するサーバーのディスク容量は、以下の計算式で算出できます、と。
needed_disk_space = retention_time_seconds * ingested_samples_per_second * bytes_per_sample 必要なディスクサイズ = 保持期間(秒) × 秒あたりの取得サンプル数 * サンプルあたりのバイト数
1秒あたりに取得するサンプルを小さくするには、取得する時系列データの数を減らす(収集するターゲットを減らす、
ターゲットあたりの取得項目を減らす)か、取得間隔を長く設定するかのどちらかです。
ドキュメントによると、取得したデータは圧縮されるため、取得する項目数を減らす方が効果的だとか。
なお、ローカルストレージが破損した場合は、以下の対処になるとか。
- Prometheusをシャットダウンして、データディレクトリを削除する(これが最善らしい)
- 当然、全データを失う
- 個々のブロックディレクトリを削除する
- 削除したブロックに含まれる、2時間のデータは失う
どちらにせよ、Prometheusのローカルストレージは長期間のデータ保存を意図していないことがドキュメントでは強調されています。
データの保持期間を指定してみる
それでは、データの保持期間を短めにして、動作を確認してみましょう。
以下のような、データの取得間隔を短めにしたPrometheusの設定ファイルを用意し、起動。
prometheus.yml
global: scrape_interval: 1s # Set the scrape interval to every 15 seconds. Default is every 1 minute. evaluation_interval: 1s # Evaluate rules every 15 seconds. The default is every 1 minute. scrape_configs: - job_name: 'prometheus' static_configs: - targets: ['localhost:9090']
データの取得対象は、デフォルト(自分自身)のままです。
起動オプションとして、早めに消えることを確認したくて「--storage.tsdb.retention.time」を3分に設定しました。
あと、「--storage.tsdb.min-block-duration」を指定していますが、なんでこれを付けたかはまた後で…。
$ ./prometheus --storage.tsdb.retention.time 3m --storage.tsdb.min-block-duration 1m level=info ts=2019-05-02T14:09:07.183Z caller=main.go:321 msg="Starting Prometheus" version="(version=2.9.2, branch=HEAD, revision=d3245f15022551c6fc8281766ea62db4d71e2747)"
Web Consoleで、起動オプションを認識していることを確認。
とりあえず、Web ConsoleでCPU使用時間(process_cpu_seconds_total)を表示。10分間の表示で見ています。
1分経過すると、こんなログが出力され
level=info ts=2019-05-02T14:10:44.219Z caller=compact.go:499 component=tsdb msg="write block" mint=1556806153112 maxt=1556806200000 ulid=01D9WE45RQ73CF18Y6J2BPMN1Q duration=100.343317ms level=info ts=2019-05-02T14:10:44.237Z caller=head.go:540 component=tsdb msg="head GC completed" duration=1.785374ms
ブロックができます。
$ find data -type f data/wal/00000000 data/lock data/01D9WE45RQ73CF18Y6J2BPMN1Q/meta.json data/01D9WE45RQ73CF18Y6J2BPMN1Q/index data/01D9WE45RQ73CF18Y6J2BPMN1Q/tombstones data/01D9WE45RQ73CF18Y6J2BPMN1Q/chunks/000001
このログが4回出力されるまでは、ブロックディレクトリが追加されていきます。
level=info ts=2019-05-02T14:10:44.219Z caller=compact.go:499 component=tsdb msg="write block" mint=1556806153112 maxt=1556806200000 ulid=01D9WE45RQ73CF18Y6J2BPMN1Q duration=100.343317ms level=info ts=2019-05-02T14:10:44.237Z caller=head.go:540 component=tsdb msg="head GC completed" duration=1.785374ms level=info ts=2019-05-02T14:11:30.196Z caller=compact.go:499 component=tsdb msg="write block" mint=1556806200000 maxt=1556806260000 ulid=01D9WE5JP7Y6FFGZMKKS1KK2AD duration=77.507689ms level=info ts=2019-05-02T14:11:30.217Z caller=head.go:540 component=tsdb msg="head GC completed" duration=1.549613ms level=info ts=2019-05-02T14:12:30.229Z caller=compact.go:499 component=tsdb msg="write block" mint=1556806260000 maxt=1556806320000 ulid=01D9WE7D97CMB7R8RS12G9E9YW duration=109.711593ms level=info ts=2019-05-02T14:12:30.251Z caller=head.go:540 component=tsdb msg="head GC completed" duration=1.421509ms level=info ts=2019-05-02T14:13:30.197Z caller=compact.go:499 component=tsdb msg="write block" mint=1556806320000 maxt=1556806380000 ulid=01D9WE97W9WEKZCQ0957KVH2GX duration=75.948389ms level=info ts=2019-05-02T14:13:30.215Z caller=head.go:540 component=tsdb msg="head GC completed" duration=1.492116ms
こんな感じに。
$ find data -type f data/wal/00000000 data/01D9WE7D97CMB7R8RS12G9E9YW/meta.json data/01D9WE7D97CMB7R8RS12G9E9YW/index data/01D9WE7D97CMB7R8RS12G9E9YW/tombstones data/01D9WE7D97CMB7R8RS12G9E9YW/chunks/000001 data/lock data/01D9WE45RQ73CF18Y6J2BPMN1Q/meta.json data/01D9WE45RQ73CF18Y6J2BPMN1Q/index data/01D9WE45RQ73CF18Y6J2BPMN1Q/tombstones data/01D9WE45RQ73CF18Y6J2BPMN1Q/chunks/000001 data/01D9WE5JP7Y6FFGZMKKS1KK2AD/meta.json data/01D9WE5JP7Y6FFGZMKKS1KK2AD/index data/01D9WE5JP7Y6FFGZMKKS1KK2AD/tombstones data/01D9WE5JP7Y6FFGZMKKS1KK2AD/chunks/000001 data/01D9WE97W9WEKZCQ0957KVH2GX/meta.json data/01D9WE97W9WEKZCQ0957KVH2GX/index data/01D9WE97W9WEKZCQ0957KVH2GX/tombstones data/01D9WE97W9WEKZCQ0957KVH2GX/chunks/000001
ここで、5回目のコンパクションが起こると
level=info ts=2019-05-02T14:10:44.219Z caller=compact.go:499 component=tsdb msg="write block" mint=1556806153112 maxt=1556806200000 ulid=01D9WE45RQ73CF18Y6J2BPMN1Q duration=100.343317ms level=info ts=2019-05-02T14:10:44.237Z caller=head.go:540 component=tsdb msg="head GC completed" duration=1.785374ms level=info ts=2019-05-02T14:11:30.196Z caller=compact.go:499 component=tsdb msg="write block" mint=1556806200000 maxt=1556806260000 ulid=01D9WE5JP7Y6FFGZMKKS1KK2AD duration=77.507689ms level=info ts=2019-05-02T14:11:30.217Z caller=head.go:540 component=tsdb msg="head GC completed" duration=1.549613ms level=info ts=2019-05-02T14:12:30.229Z caller=compact.go:499 component=tsdb msg="write block" mint=1556806260000 maxt=1556806320000 ulid=01D9WE7D97CMB7R8RS12G9E9YW duration=109.711593ms level=info ts=2019-05-02T14:12:30.251Z caller=head.go:540 component=tsdb msg="head GC completed" duration=1.421509ms level=info ts=2019-05-02T14:13:30.197Z caller=compact.go:499 component=tsdb msg="write block" mint=1556806320000 maxt=1556806380000 ulid=01D9WE97W9WEKZCQ0957KVH2GX duration=75.948389ms level=info ts=2019-05-02T14:13:30.215Z caller=head.go:540 component=tsdb msg="head GC completed" duration=1.492116ms level=info ts=2019-05-02T14:14:30.248Z caller=compact.go:499 component=tsdb msg="write block" mint=1556806380000 maxt=1556806440000 ulid=01D9WEB2F4SWD5FDP5EBV7BD0E duration=132.373486ms level=info ts=2019-05-02T14:14:30.270Z caller=head.go:540 component=tsdb msg="head GC completed" duration=1.201336ms
ブロックディレクトリが増えなくなります。「01D9WE45RQ73CF18Y6J2BPMN1Q」という名前のディレクトリがいなくなりました。
$ find data -type f data/01D9WEB2F4SWD5FDP5EBV7BD0E/meta.json data/01D9WEB2F4SWD5FDP5EBV7BD0E/index data/01D9WEB2F4SWD5FDP5EBV7BD0E/tombstones data/01D9WEB2F4SWD5FDP5EBV7BD0E/chunks/000001 data/wal/00000000 data/01D9WE7D97CMB7R8RS12G9E9YW/meta.json data/01D9WE7D97CMB7R8RS12G9E9YW/index data/01D9WE7D97CMB7R8RS12G9E9YW/tombstones data/01D9WE7D97CMB7R8RS12G9E9YW/chunks/000001 data/lock data/01D9WE5JP7Y6FFGZMKKS1KK2AD/meta.json data/01D9WE5JP7Y6FFGZMKKS1KK2AD/index data/01D9WE5JP7Y6FFGZMKKS1KK2AD/tombstones data/01D9WE5JP7Y6FFGZMKKS1KK2AD/chunks/000001 data/01D9WE97W9WEKZCQ0957KVH2GX/meta.json data/01D9WE97W9WEKZCQ0957KVH2GX/index data/01D9WE97W9WEKZCQ0957KVH2GX/tombstones data/01D9WE97W9WEKZCQ0957KVH2GX/chunks/000001
この結果、最初の時間のデータがなくなります。「01D9WE45RQ73CF18Y6J2BPMN1Q」というのは、最初に作られた
ディレクトリでした。
というわけで、「--storage.tsdb.retention.time」で指定した保持期間を越えたデータは削除されていることが確認できました。
ところで、今回、起動オプションに「--storage.tsdb.retention.time」だけではなくて「--storage.tsdb.min-block-duration」も
指定しました。
$ ./prometheus --storage.tsdb.retention.time 3m --storage.tsdb.min-block-duration 1m
「--storage.tsdb.min-block-duration」を指定したことで、ブロックの範囲が1分になっています。
最初、以下のように「--storage.tsdb.retention.time」を指定しただけだとデータがまったく消えず、「もしかしてWALにいる間の
データは対象に入らないのでは?」と思い、変更に至りました。
$ ./prometheus --storage.tsdb.retention.time 3m
つまり、長時間待つのが嫌で、極端に短い時間をretentionに設定したのが完全に裏目に出たようです。
実際、削除対象はブロック単位のようです。
https://github.com/prometheus/prometheus/blob/v2.9.2/vendor/github.com/prometheus/tsdb/db.go#L670
ブロックディレクトリ内のmeta.jsonを見ると、minTimeとmaxTimeで、どの時間の範囲のデータが含まれているか確認することが
できます。
data/01D9WEEQN5CH9F53GAFCN3G14E/meta.json
{ "ulid": "01D9WEEQN5CH9F53GAFCN3G14E", "minTime": 1556806500000, "maxTime": 1556806560000, "stats": { "numSamples": 27120, "numSeries": 452, "numChunks": 452, "numBytes": 77220 }, "compaction": { "level": 1, "sources": [ "01D9WEEQN5CH9F53GAFCN3G14E" ] }, "version": 1 }
ここからわかることは、「--storage.tsdb.min-block-duration」を設定することはそうないかもしれませんが、
「--storage.tsdb.retention.time」はブロックディレクトリで持つ範囲(デフォルト2時間)の倍数でなければ意味がない
ということですね。
覚えておきましょう。
いい確認になりました。