Prometheus 2.0 ストレージのCompactionの動きを確認してみる

# # Prometheus Advent Calendar 2017 3日目

の記事です。

本記事では、Prometheus 2.0 のストレージについて、[公式のドキュメントの内容](https://prometheus.io/docs/prometheus/latest/storage/)+機能を検証してみたことをまとめます。


# # 環境
- prometheus server 2.00
- centos7.4

この記事では Prometheus 2.0 を対象にします。

先日2系がリリースされましたが、2系のデータフォーマットは1系のそれとは完全に違うものになりました。かつ、互換性のない変更になっているので、1.8のPrometheusは2.0が生成したデータを扱うことはできません。大きな変更です。

Prometheusのストレージに関する記事は検索すればいくつかでてきますが、現時点では多くが１系のPrometheusを対象にしたものです。ネットで検索した情報がどのバージョンの記事なのかは確認したほうが良いと思います。また、[公式のドキュメント](
 https://prometheus.io/docs/prometheus/latest/storage/) で説明されていることがあっさりしすぎています。1.8のドキュメントと比較すると情報量の差が歴然です。

## ## 1.8と2.0のデータ互換について
先にも書きましたが、2系のデータフォーマットは1系のそれとは完全に違うものになりました。かつ、互換性はありません。しかしながら、1.8ですでに収集済みのデータが存在する、かつ移行が難しいなどの場合は、1.8と2.0のPrometheusを並行稼動させて2.0のPrometheusから `remote read` 設定で1.8のPrometheusに対してpullしてデータを持ってくることで対応します。

具体的には、1.8のPrometheusに以下のフラグを起動しておきます

```bash
$ ./prometheus-1.8.1.linux-amd64/prometheus -web.listen-address ":9094" -config.file old.yml
```

つぎに、2.0のPrometheusを起動しますが、特にフラグは必要なく、config.fileを指定すればオッケー。

```bash
$ ./prometheus-2.0.0.linux-amd64/prometheus --config.file prometheus.yml
```

Prometheus 2.0が読み込むconfig.fileの中で、`remote_read`設定を追加しておきます。remote_readする先として、同じマシン上で起動しているPrometheus 1.8がlistenしているポートとapiを指定します。

```prometheus.yml
remote_read:
  - url: "http://localhost:9094/api/v1/read"
```

# # ストレージのレイアウト
Prometheus 2.0 のストレージはMacro Designという構造をとります。

## ## マクロデザインとは？
Prometheusが保管するすべてのデータが、ツリー状のディレクトリによって管理されたレイアウトのことです。

以下のようなイメージで表現できます。

```bash
$ tree ./data
./data
├── lock (text file)
├── block (directory)
│   ├── chunks (directory)
│   │   ├── 000001(chunk binary file)
│   │   ├── 000002
│   │   └── 000003
│   ├── index (binary file)
│   └── meta.json
├── block
│   ├── chunks
│   │   ├── 000001
│   │   ├── 000002
│   │   └── 000003
│   ├── index
│   └── meta.json
└── wal (directory)
    ├── 000001 (wal binary file)
    └── 000002
```
 
## ## 用語
ストレージに関する用語と意味

- data point: 
timestampとvalueのタプル
- series: 
data pointの連続。時系列。
- block:
ディレクトリ。頭に必ず"b"がつく。indexファイルとchunkディレクトリが格納される。
- chunk:
ディレクトリ。chunkディレクトリ内のchunkファイルはバイナリデータ。複数のseriesのdata pointを格納する。
- index: 
バイナリデータ。chunk内のseriesにメトリック名とラベルを紐付けるファイル。
- meta.json:
meta.json。該当ブロックの状態（圧縮階層や、chunkの数などの情報）を記録したファイル。json形式なので参照可能。
- tombstone:
APIで削除された時系列データ。削除処理後に直ちに削除されるのではなく、tombstoneファイルとして保管される。

## ## ストレージオプション
Prometheusnのストレージオプションとして利用可能なものは以下になります。

- --storage.tsdb.path
データを保管するパスを指定します。デフォルトではprometheusのバイナリと同じ階層にdataというディレクトリが作成されます。systemdでサービス化しているなどの場合は、このオプションで指定しておかないと/直下にdataディレクトリが作成されてしまいます。
- --storage.tsdb.retention
データを保管する期間を指定します。デフォルトは15日です。
- --storage.tsdb.min-block-duration
ブロックの最小期間を指定します。デフォルトは2時間です。このオプションで設定した間隔でメモリからデータがディスクにブロックとして書き出されます。
- --storage.tsdb.max-block-duration
束ねられたブロックの最大の期間を設定します。デフォルトは36時間です。この値はデフォルトのretention期間である15日の10%になっています。

# # TSDBを構成している要素

- Block 小さなデータベース
- mmap　
- Compaction　ブロックのコンパクト処理
- Retention データ保管
- Index　

## ## Block
マクロデザインの一番上の階層に位置するブロックは、それ自体が独立したデータベースのように振舞います。各ブロックは、そのブロックの時間範囲(time window)内に収集したchunkファイルのセットとindexを持ちます。

Prometheusが収集するデータは、メモリ上に記録されるのと同時に、テンポラリファイルとしてwalディレクトリに書き出されています。メモリに記録しているデータの収集期間が`--storage.tsdb.min-block-duration`で設定した時間を迎えると、blockとしてディスクに書き出されます。一度blockとして書き出されたものの中身は変更することはできません。最新のデータは常にメモリ上およびwalディレクトリ内に生データとして記録されます。walディレクトリに記録されるデータは、prometheusの再起動時などにメモリ上のデータをロスしないために使用されます。blockに書き出されると、テンポラリファイルであるwalの中身はクリアされ、次のblockになる最新のサンプルが書き込まれていきます。

クエリ処理はblock単位を対象に行います。クエリ処理で指定した時間範囲(１日分、１週間分、いつからいつまでとか）に該当するすべてのblockが処理対象になり、複数のblockに対してクエリを投げて各blockから返ってくる結果をマージして一つの結果として表示します。

データはブロック単位で保管され、ブロック単位で削除されます。削除する場合はblock=ディレクトリを削除するだけなので、一瞬で処理が完了します。Zabbixのように、housekeeper処理に要する負荷を気にする、ということもなくなります。

## ## mmap


## ## Compaction
blockのCompact化する処理について。

メモリ上に大量のデータを蓄積しないように、デフォルトで２時間という単位でblockを作成するようにしています。この仕様のままだと一日で12blockが生成されることになり、例えば１週間単位でのクエリ操作を実行した場合は、単純計算で84個のblockを対象にクエリを実行し、各blockから取得した結果をマージする必要があります。マージをするにもコストがかかります。

そこで、blockのCompactionを行います。複数のblockを1つのblockにまとめ、より大きな時間範囲をカバーするblockにします。Compaction処理によって1つのblockがカバーする時間範囲は伸びていきますが、無尽蔵に伸びていくのではなく、`--storage.tsdb.max-block-duration`で設定した範囲を限度にCompactionされます。デフォルトで36時間まで。この36時間という値は、デフォルトのretention期間15日の10%という基準です。Compactionの際に、削除済みのデータ(tombstone)をドロップし、クエリパフォーマンス向上のためにchunkのリストラクチャを行います。





## ## Retention
blockベースデザインで、古いデータはどうやって削除していくか。

極めて単純な思想で、設定したretention期間（保管期間）外のblockのディレクトリを削除するだけ。先のcompaction処理によって、blockは古くなればそれだけサイズも大きくなりますが、blockの期間の上限が設定できるので、データベースを埋めるほど増加することはありません。

## ## Index


# # チューニング
チューニングする観点は以下の2つです。

- ターゲットを減らす
- 取得間隔を伸ばす

## ## ターゲットを減らす
ターゲットを減らすには、

- 不要なホストを取得対象から除外する
- ジョブが取得するメトリクスを減らす
例えば、node_exporterであれば、`param > collcter` の設定で、このジョブはこのcollecterが収取するmetricだけを対象に保管する。など。

## ## 取得間隔を伸ばす
スクレイプする間隔を増加させることで実現します。

Prometheusのconfig.fileで、グローバル単位、あるいは、job単位で、`scrape_interval`値を調整します。


# # Compactionの動きを確認してみた
コンパクト処理の動きを確認してみました。

確認しやすくするために、`--storage.tsdb.min-block-duration`を5分[5m]に設定しています。

起動直後のdataディレクトリ。lockファイルとwalディレクトリが生成されています。ちなみに16:18に起動しました。

```bash
ls -lh /usr/local/prometheus/data/
total 4.0K
-rw-------. 1 root root  5 Dec  1 16:18 lock
drwxr-xr-x. 2 root root 20 Dec  1 16:18 wal

# ls -lh /usr/local/prometheus/data/wal/
total 256M
-rw-r--r--. 1 root root 256M Dec  1 16:22 000001
```

5分たって、ブロックが１つ生成されました。

```bash
# ls -lh /usr/local/prometheus/data
total 4.0K
drwxr-xr-x. 3 root root 68 Dec  1 16:22 01C08F92RNPS54R981GXQQANWT ←これができた
-rw-------. 1 root root  5 Dec  1 16:18 lock
drwxr-xr-x. 2 root root 20 Dec  1 16:18 wal
```

できあがったブロックディレクトリの中身を確認します。chunkディレクトリとその中身（生データ）、インデックスファイル、メタファイルがあります。

```bash
# ls -lh /usr/local/prometheus/data/01C08F92RNPS54R981GXQQANWT/
total 308K
drwxr-xr-x. 2 root root   20 Dec  1 16:22 chunks
-rw-r--r--. 1 root root 299K Dec  1 16:22 index
-rw-r--r--. 1 root root  277 Dec  1 16:22 meta.json
-rw-r--r--. 1 root root    9 Dec  1 16:22 tombstones

# ls -lh /usr/local/prometheus/data/01C08F92RNPS54R981GXQQANWT/chunks/
total 64K
-rw-r--r--. 1 root root 64K Dec  1 16:22 000001
```

meta.jsonの中身を見てみます。

statsの中に、このブロックが保管しているサンプル数、時系列数、チャンク数が確認できます。また、compactionには該当ブロックのコンパクト化された状態が表示されています。まだ1個目のブロックなのでlevel=1, sourcesは自分自身のブロック名。となっています。

```bash
{
        "version": 1,
        "ulid": "01C08F92RNPS54R981GXQQANWT",
        "minTime": 1512112500000,
        "maxTime": 1512112800000,
        "stats": {
                "numSamples": 15364,
                "numSeries": 2103,
                "numChunks": 2103
        },
        "compaction": {
                "level": 1,
                "sources": [
                        "01C08F92RNPS54R981GXQQANWT"
                ]
        }
}
```

さらに五分後。もう一つのブロックができました。

```bash
# ls -lh data/
total 4.0K
drwxr-xr-x. 3 root root 68 Dec  1 16:22 01C08F92RNPS54R981GXQQANWT ←1個目
drwxr-xr-x. 3 root root 68 Dec  1 16:27 01C08FJ7QNBM5SX8Z3WRZ5JA7C ←2個目
-rw-------. 1 root root  5 Dec  1 16:18 lock
drwxr-xr-x. 2 root root 20 Dec  1 16:18 wal
```

２個目のブロックのチャンクディレクトリの中身です。

```bash
# ls -lh data/01C08FJ7QNBM5SX8Z3WRZ5JA7C/chunks/
total 88K
-rw-r--r--. 1 root root 85K Dec  1 16:27 000001
```

1個目のブロックのmeta.jsonとほぼ同じです。

```bash
# cat data/01C08FJ7QNBM5SX8Z3WRZ5JA7C/meta.json
{
        "version": 1,
        "ulid": "01C08FJ7QNBM5SX8Z3WRZ5JA7C",
        "minTime": 1512112800000,
        "maxTime": 1512113100000,
        "stats": {
                "numSamples": 41991,
                "numSeries": 2101,
                "numChunks": 2101
        },
        "compaction": {
                "level": 1,
                "sources": [
                        "01C08FJ7QNBM5SX8Z3WRZ5JA7C"
                ]
        }
}
```

さらに5分後。もともとあった2つのブロックが消え、別のブロックが一つできあがりました。

```bash
# ls -lh data/
total 4.0K
drwxr-xr-x. 3 root root 68 Dec  1 16:32 01C08FVCRVVPTPSP6ESA7WF0GH
-rw-------. 1 root root  5 Dec  1 16:18 lock
drwxr-xr-x. 2 root root 20 Dec  1 16:18 wal
```

新しくできていたブロックのmeta.jsonを確認します。

```bash
{
        "version": 1,
        "ulid": "01C08FVCRVVPTPSP6ESA7WF0GH",
        "minTime": 1512112500000,
        "maxTime": 1512113400000,
        "stats": {
                "numSamples": 99375,
                "numSeries": 2108,
                "numChunks": 6305
        },
        "compaction": {
                "level": 2,
                "sources": [
                        "01C08F92RNPS54R981GXQQANWT",　←1個目のブロック
                        "01C08FJ7QNBM5SX8Z3WRZ5JA7C",　←２個目のブロック
                        "01C08FVCPN1FA903ZVZPE7Y6EW"　　←３個目のブロック
                ]
        }
```

compactionのlevelが2になり、sourcesに3つのブロック名が表示されています。

sourcesに表示されている3つのブロックのうち、上の2つは先ほどまで存在していたブロックです。つまり、このブロックは、3つめのブロックを生成するタイミングですでに存在していたブロックと新たなブロックを束ねて作られたものだということがわかります。

statsを見ると、numChunksがだいたい1ブロック*3の値になっていることからも、単純に3つのブロックを束ねらものだとわかります。

levelが1から2になったのは、このブロックが一度compaction処理されたものだということを示しています。

さらに5分後。新しいブロックができました。

```bash
# ll data/
total 4
drwxr-xr-x. 3 root root 68 Dec  1 16:32 01C08FVCRVVPTPSP6ESA7WF0GH　←これがレベル２
drwxr-xr-x. 3 root root 68 Dec  1 16:37 01C08G4HNN7SH8C08N39AE9QH0　←これがレベル１
-rw-------. 1 root root  5 Dec  1 16:18 lock
drwxr-xr-x. 2 root root 20 Dec  1 16:18 wal
```

ここまできて、次の5分後にはあたらしいlevel=1のブロックができあがり、さらに5分後にできたブロックによってlevel=2にcompactionされたブロックが出来上がるだろうと予想できます。compaction処理については、level=1のブロックが何個集まったらlevel=2にレベルアップするのかなど、未調査なのでそこも後々確認したいと思います。

5分後の状態。level=1のブロックが追加されました。

```bash
# ls -lh data/
total 4.0K
drwxr-xr-x. 3 root root 68 Dec  1 16:32 01C08FVCRVVPTPSP6ESA7WF0GH　←レベル2
drwxr-xr-x. 3 root root 68 Dec  1 16:37 01C08G4HNN7SH8C08N39AE9QH0　←レベル１
drwxr-xr-x. 3 root root 68 Dec  1 16:42 01C08GDPMNVAH1AFVNCWXAM97S　←レベル１
-rw-------. 1 root root  5 Dec  1 16:18 lock
drwxr-xr-x. 2 root root 20 Dec  1 16:18 wal
```

さらに5分後。予想通り、level=2のブロックが新しく出来上がりました。

```bash
# ls -lh data/
total 4.0K
drwxr-xr-x. 3 root root 68 Dec  1 16:32 01C08FVCRVVPTPSP6ESA7WF0GH　←前からあるレベル2
drwxr-xr-x. 3 root root 68 Dec  1 16:47 01C08GPVNR8WSV2VZYMS5C2EJJ　←あたらしくできたレベル２
-rw-------. 1 root root  5 Dec  1 16:18 lock
drwxr-xr-x. 2 root root 20 Dec  1 16:18 wal
```

新しく出来上がったlevel=2のブロックのmeta.jsonを確認してみます。前回のcompactionと同じ動きをしていることが確認できました。

```bash
# cat data/01C08GPVNR8WSV2VZYMS5C2EJJ/meta.json
{
        "version": 1,
        "ulid": "01C08GPVNR8WSV2VZYMS5C2EJJ",
        "minTime": 1512113400000,
        "maxTime": 1512114300000,
        "stats": {
                "numSamples": 126060,
                "numSeries": 2101,
                "numChunks": 6303
        },
        "compaction": {
                "level": 2,
                "sources": [
                        "01C08G4HNN7SH8C08N39AE9QH0",　←すでにあったレベル1
                        "01C08GDPMNVAH1AFVNCWXAM97S",　←すでにあったレベル1
                        "01C08GPVKN0VNVQVK4752277WA"　
                ]
        }
}
```

さらに進めます。level=1のブロックが2つ追加されました。

```bash
# ls -lh data/
total 4.0K
drwxr-xr-x. 3 root root 68 Dec  1 16:32 01C08FVCRVVPTPSP6ESA7WF0GH　←レベル2
drwxr-xr-x. 3 root root 68 Dec  1 16:47 01C08GPVNR8WSV2VZYMS5C2EJJ　←レベル2
drwxr-xr-x. 3 root root 68 Dec  1 16:52 01C08H00JNJTDWZ9QDYV0Q57GC　←レベル1
drwxr-xr-x. 3 root root 68 Dec  1 16:57 01C08H95HNNKYC5EZ0R6ND7Q6S　←レベル1
-rw-------. 1 root root  5 Dec  1 16:18 lock
drwxr-xr-x. 2 root root 20 Dec  1 16:18 wal
```

この次の生成のタイミングで、レベル２が３つできました。

```bash
# ls -lh data/
total 4.0K
drwxr-xr-x. 3 root root 68 Dec  1 16:32 01C08FVCRVVPTPSP6ESA7WF0GH　←レベル２
drwxr-xr-x. 3 root root 68 Dec  1 16:47 01C08GPVNR8WSV2VZYMS5C2EJJ←レベル２
drwxr-xr-x. 3 root root 68 Dec  1 17:02 01C08HJAJS6D3A93MZ24YDM2EE←レベル２
-rw-------. 1 root root  5 Dec  1 16:18 lock
drwxr-xr-x. 2 root root 20 Dec  1 16:18 wal
```

あたらしくできたlevel=2のmeta.json

```bash
# cat data/01C08HJAJS6D3A93MZ24YDM2EE/meta.json
{
        "version": 1,
        "ulid": "01C08HJAJS6D3A93MZ24YDM2EE",
        "minTime": 1512114300000,
        "maxTime": 1512115200000,
        "stats": {
                "numSamples": 126076,
                "numSeries": 2102,
                "numChunks": 6304
        },
        "compaction": {
                "level": 2,
                "sources": [
                        "01C08H00JNJTDWZ9QDYV0Q57GC",
                        "01C08H95HNNKYC5EZ0R6ND7Q6S",
                        "01C08HJAGNWG11TJ9CWC465PSR"
                ]
        }
}
```

level=2がもう一個作られるまで放置していたらlevel=3のブロックが生成されました。

```bash
# ls -lh data/
total 4.0K
drwxr-xr-x. 3 root root 68 Dec  1 16:32 01C08FVCRVVPTPSP6ESA7WF0GH　←level=2
drwxr-xr-x. 3 root root 68 Dec  1 17:17 01C08JDSJGK6WFRCQFYDE4QZED　←level=3
-rw-------. 1 root root  5 Dec  1 16:18 lock
drwxr-xr-x. 2 root root 20 Dec  1 16:18 wal
```

レベル3のブロックのmeta.jsonを見てみます。sourcesに計9個のブロックが表示されています。いずれもlevel=1のブロックです。

```bash
# cat data/01C08JDSJGK6WFRCQFYDE4QZED/meta.json
{
        "version": 1,
        "ulid": "01C08JDSJGK6WFRCQFYDE4QZED",
        "minTime": 1512113400000,
        "maxTime": 1512116100000,
        "stats": {
                "numSamples": 378273,
                "numSeries": 2103,
                "numChunks": 18914
        },
        "compaction": {
                "level": 3,
                "sources": [
                        "01C08G4HNN7SH8C08N39AE9QH0",
                        "01C08GDPMNVAH1AFVNCWXAM97S",
                        "01C08GPVKN0VNVQVK4752277WA",
                        "01C08H00JNJTDWZ9QDYV0Q57GC",
                        "01C08H95HNNKYC5EZ0R6ND7Q6S",
                        "01C08HJAGNWG11TJ9CWC465PSR",
                        "01C08HVFFNM8E37KZHCS6R50YC",
                        "01C08J4MENJ4DR6KDR6BB4CTMK",
                        "01C08JDSDN6GSRDXW0Q1364S0R"
                ]
        }
}
```

という感じで、Prometheusのcompaction処理の動きを実際に確認してみました。