Prometheus の Remote Storage とは?

この記事は [Z Lab Advent Calendar](https://qiita.com/advent-calendar/2017/zlab) の四日目の記事です. 今回も, 昨日の @tkusumi さんの[記事](https://qiita.com/tkusumi/items/fc94cab083ebe8ded9b4)に引き続き, Prometheus に関連する内容の記事となります. Z Lab では Prometheus の Remote Storage を検証している最中なので, この記事ではその Remote Storage について紹介してみたいと思います.

## どんな機能なのか?

端的にいうと Prometheus が取得したメトリクスデータを, ローカルに持つストレージだけでなく, 別のマシン上にあるストレージに格納するという機能になります.

これを実現するために, Prometheus では Remote Read/Write API の仕様を定め, Remote Storage 側で Read/Write それぞれに対しての HTTP のエンドポイントを用意することでメトリクスデータの入出力を行えるようにしています.

- **Remote Write のエンドポイント**: メトリクスデータを Remote Storage に書き込むために利用される
- **Remote Read のエンドポイント**: そのエンドポイントに対してクエリを送信することで, Remote Storage 側に格納されているメトリクスデータを取得するために利用される

## 何のために必要な機能なのか?

[公式のドキュメント](https://prometheus.io/docs/prometheus/latest/storage/#operational-aspects)にもあるように, Prometheus に障害が発生した場合にはデータを削除しなければならないケースが出てくるため, Prometheus で取得したメトリクスデータを長期保存するためには, 別の長期保存に適したストレージにデータを移す必要があります. そのための手段として Remote Storage という機能が開発されました.

## 現時点で利用可能な実装は?

現時点で Remote Storage として利用できる実装は[ここ](https://prometheus.io/docs/operating/integrations/#remote-endpoints-and-storage)にリストされています. 特に, Read と Write の両方のエンドポイントをサポートしているものは次の 4 つになります.

### Cortex

<https://github.com/weaveworks/cortex>

Cortex は [Weaveworks](https://www.weave.works/) によって開発が進められているスケーラブルな Prometheus as a service のプロジェクトです. 実装自体はオープンソースですが, 利用するためには [Weave Cloud](https://cloud.weave.works/) を利用することが前提となります.

### CrateDB

<https://crate.io/>

CrateDB はオープンソースの分散 SQL データベースの一つで, 内部の実装としてはクラスタ管理の部分などに [Elasticsearch](http://www.elastic.co/products/elasticsearch) を利用しているようです. ただし, CrateDB 自身が Prometheus の Remote Storage の機能を直接提供しているわけではなく, Prometheus と CrateDB の間に [crate_adapter](https://github.com/crate/crate_adapter) というアダプタを置くことによって, Remote Storage として利用できるようになります.

### InfluxDB

<https://www.influxdata.com/>

InfluxDB はオープンソースの純粋な Time Series Database の実装です. InfluxDB も以前までは Remote Storage として利用する際には[アダプタ](https://github.com/prometheus/prometheus/tree/master/documentation/examples/remote_storage/remote_storage_adapter) を利用する必要がありましたが, [v1.4.0](https://github.com/influxdata/influxdb/blob/master/CHANGELOG.md#v140-2017-11-13) からはアダプタを利用することなく, ネイティブな機能として Prometheus の Remote Storage としてのエンドポイントを提供するようになりました. 注意しなければならない点として, クラスタ化の機能が有償版のみでしかサポートされていないという点が上げられます.

### PostgreSQL

<https://www.postgresql.org/>

PostgreSQL は誰もが知る RDBMS の一つだと思います. Remote Storage として利用するためには, PostgreSQL に [pg_prometheus](https://github.com/timescale/pg_prometheus) という拡張機能を追加して Prometheus のメトリクスデータを取り扱えるようにした上で, [prometheus-postgresql-adapter](https://github.com/timescale/prometheus-postgresql-adapter) というアダプタを利用する必要があるようです.

## 試してみる

今回, 上述した Remote Storage の実装の中から, 簡単に構築できそうで運用のコストもあまりかからなそうな CrateDB と InfluxDB を選択して試してみました.

### CrateDB

上述したように CrateDB を Prometheus の Remote Storage として利用するためには, [crate_adapter](https://github.com/crate/crate_adapter) というアダプタを利用する必要があります. このアダプタも利用して, 今回は次のような構成で構築を行います.

まず, CrateDB は 3 つのインスタンス用意してクラスタ化します. そして, アダプタを用意し, Prometheus からの入出力はこのアダプタを通して行います.

```
             +-------------+
             | Prometheus  |
             +------+------+
                    |
                    |
                    v
            +----------------+
            | Crate Adapter  |
            +---+---+---+----+
                |   |   |
                |   |   |
                |   |   |
     +----------+   |   +----------+
     |              |              |
     |              v              |
     |         +---------+         |
     |  +------+ CrateDB +------+  |
     |  |      +---------+      |  |
     v  |                       |  v
+-------+-+                   +-+-------+
| CrateDB +-------------------+ CrateDB |
+---------+                   +---------+
```

Prometheus 側の設定は次のようにしました. `url` はアダプタのエンドポイントを指定し, `queue_config` には Remote Storage への書き込みを調整するパラメータを指定します.

```yaml
remote_write:
- url: http://{{ crate_adapter の IP アドレス }}:9268/write
  queue_config:
    capacity: 1000000
    batch_send_deadline: 10s
    max_samples_per_send: 1000
    max_shards: 3

remote_read:
- url: http://{{ crate_adapter の IP アドレス }}:9268/read
```

ここで, `queue_config` の各項目の意味は次の通りです.

| 名前 | 意味 | デフォルト値 | 指定した値 |
|---|---|---|---|
| capacity | Prometheus 側の書き込みキューの最大長 | 10000 | 1000000 |
| batch_send_deadline | 送信間隔の最大値 | 5s | 10s |
| max_samples_per_send | 一度に送信するサンプル数の最大値 | 100 | 1000 |
| max_shards | 送信の並列度 | 1000 | 3 |

デフォルトの設定のままだと, Remote Storage に対してのリクエストが細か過ぎていたようで, CrateDB が書き込みに対する負荷に耐えられず, 結果 Prometheus 側のキューが伸びてしまうようでした. CrateDB では [Bulk Inserts](https://crate.io/docs/crate/guide/en/latest/best_practices/insert_performance.html#bulk-inserts) によって書き込み性能を上げられるようになっているようなので, `batch_send_deadline` と `max_samples_per_send` を共に大きくして一度のリクエストに含めるサンプル数を比較的大きな値にすることで対処しています. これに伴いキューの最大長も大きく設定しています.

また, 送信の並列度についても CrateDB のインスタンス数と同程度にするとパフォーマンスが安定することが確認できています.

このような設定により, 5k samples/second 程度のメトリクスを, Prometheus が安定して CrateDB へ書き込めるということを確認することができました. しかしながら, CrateDB で使用するディスク容量が期待していたよりも非常に大きいようで, 1 サンプルあたり 200 bytes 程度を消費しているようでした. (Prometheus の TSDB では [1 サンプルあたり 1~2 bytes しか消費しません](https://prometheus.io/docs/prometheus/latest/storage/#operational-aspects).) これは CrateDB が純粋な TSDB ではないため, 不要なインデクスを作成してしまっていることが原因なのではないかと想像しています.

### InfluxDB

InfluxDB は Prometheus の Remote Storage としての機能をネイティブに備えているため, CrateDB の場合のようにアダプタを用意する必要がありません. そのため, 構築するシステムは次のようにシンプルなものとなります.

3 つの InfluxDB のインスタンスを用意して, Prometheus からそれぞれのインスタンスに対してメトリクスデータの入出力を行います. ここで, InfluxDB 同士をクラスタ化するためには有償のエンタープライズ版を利用する必要があるため, 今回は InfluxDB 自体のクラスタ化は行っていないことに注意してください. 各 InfluxDB インスタンスへのデータの分散化は Prometheus 側に持つ Shard のロジックによって行われます.

```
             +--------------+
             |  Prometheus  |
             +---+--+--+----+
                 |  |  |
                 |  |  |
                 |  |  |
      +----------+  |  +-----------+
      |             |              |
      |             |              |
      v             v              v
+----------+   +----------+   +----------+
| InfluxDB |   | InfluxDB |   | InfluxDB |
+----------+   +----------+   +----------+
```

Prometheus 側では次のように各 InfluxDB インスタンスのエンドポイントのリストを記述します. `queue_config` については CrateDB のときのようなパラメータの調整は不要のようだったので, 指定する必要はありません. (少なくとも数千サンプル毎秒程度の負荷では必要性は感じられませんでした.)

```yaml
remote_write:
- url: http://{{ InfluxDB の IP アドレス }}:8086/api/v1/prom/write?db={{ DB 名 }}
- url: http://{{ InfluxDB の IP アドレス }}:8086/api/v1/prom/write?db={{ DB 名 }}
- url: http://{{ InfluxDB の IP アドレス }}:8086/api/v1/prom/write?db={{ DB 名 }}

remote_read:
- url: http://{{ InfluxDB の IP アドレス }}:8086/api/v1/prom/read?db={{ DB 名 }}
- url: http://{{ InfluxDB の IP アドレス }}:8086/api/v1/prom/read?db={{ DB 名 }}
- url: http://{{ InfluxDB の IP アドレス }}:8086/api/v1/prom/read?db={{ DB 名 }}
```

このような設定で, InfluxDB についても Remote Storage としての利用が可能であることを確認できました.

気になるのは InfluxDB 自体をクラスタ化しないことによる影響ですが, Prometheus を通してメトリクスデータを確認する限りは, Prometheus 側で上手くマージしてくれているおかげで特に問題を感じることはありませんでした. ただし, Prometheus を通さず, InfluxDB の機能を存分に利用してデータを解析したい場合などには, それぞれの InfluxDB が既に Shard されたデータしか持っていないため期待する効果が得られない場合もあると思います.

## まとめ

- CrateDB と InfluxDB を Prometheus の Remote Storage として利用することができました.
- CrateDB を Remote Storage として利用する場合, データの容量が期待するよりも大きくなってしまうことが分かりました.
- InfluxDB を Remote Storage として利用する場合, Prometheus を通じてメトリクスデータを確認する限りは, 無償版の機能だけでも十分利用できることが分かりました.