OpenEBSのローカルストレージでpgbench走らせてみた

この記事は [PostgreSQL Advent Calendar 2021](https://qiita.com/advent-calendar/2021/postgresql) 12日目のエントリです。  

# はじめに
クラウドネイティブなストレージが注目を集める中、PostgreSQLなどのデータベースでも安定かつ高性能で利用できるストレージとして`ローカルストレージ`が注目されています。 
 
今回は分散ストレージとしても盤石な地位を築いている`OpenEBS`のローカルストレージを利用して、データベースのワークロードでのベンチマーク(pgbench)をしてみたいと思います。

# 分散ストレージとローカルストレージの違い
今回は詳しい説明を割愛しますが、分散ストレージとローカルストレージのアーキテクチャの違いは、[OpenEBSアーキテクチャのドキュメント](https://openebs.io/docs)でざっくり理解できると思います。  

分散ストレージはローカルストレージと比べて、可用性の面で圧倒的に有利なのですが、データ同期のための遅延など、性能面のデメリットを抱えています。PostgreSQLのレプリケーションなど、ソフトウェアの層で分散する場合には、ローカルストレージを選択肢として取ることができます。

# 動作環境
k8sのノードが手元にある前提になります。筆者は以下の環境で構築しています。

- Windows11
- k8s on VM(Hyper-V)
    - OS(VM): RHEL7
    - k8s : v1.22.3
- OpenEBS : 3.0.0
- PostgreSQL : v14(pgbench)

# インストール
[公式マニュアル](https://docs.openebs.io/docs/next/uglocalpv-hostpath.html)にある通りの手順でインストールしていきます。  
ローカルストレージの中にも、`hostPath, LVM, ZFS, blockdevice, rawfile`など複数の選択肢がありますが、今回は導入が簡易な`hostPath`を使っていきたいと思います。
## OpenEBSのインストール
OpenEBSのインストール自体は、以下のyamlを`apply`するだけです。

```
kubectl apply -f https://openebs.github.io/charts/openebs-operator.yaml
```

STATUSが`Running`になるまで待ちます。

```
[root@k8s-master ~]# kubectl get pods -n openebs -l openebs.io/component-name=openebs-localpv-provisioner
NAME                                           READY   STATUS    RESTARTS   AGE
openebs-localpv-provisioner-6756f57d65-572r9   1/1     Running   0          2m2s
```

## StorageClassが作成されていることの確認
`local-hostpath`と`openebs-hostpath`が表示されます。

```
[root@k8s-master ~]# kubectl get sc
NAME               PROVISIONER        RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
openebs-device     openebs.io/local   Delete          WaitForFirstConsumer   false                  2m16s
openebs-hostpath   openebs.io/local   Delete          WaitForFirstConsumer   false                  2m16s
```

## デフォルトストレージクラスの変更
デフォルトの`ストレージクラス`を変更しておきます。

```sh
[root@k8s-master ~]# kubectl patch storageclass openebs-hostpath -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'
```

## 動作確認用Pod(Container)の作成
さて、上記PVCを使用するPostgreSQLコンテナを作成するのですが、色々方法があります。

- 任意のコンテナにPostgreSQLをインストール
- [PostgreSQLのdockerイメージを使用する](https://hub.docker.com/_/postgres/)
- PostgreSQL Operatorを使用する


今回は3つめの方法で、`StackGresのPostgreSQL Operator`を使って、PostgreSQLコンテナを作成します。
[公式マニュアル](https://stackgres.io/doc/latest/demo/quickstart/)のチュートリアルに従って、数ステップで構築します。(一部今回向けに変えてます)
PVCは後述する`DBクラスタの作成`で、Operatorが作成してくれます。

### Operatorインストール
Operatorをインストール

```
kubectl apply -f https://stackgres.io/downloads/stackgres-k8s/stackgres/1.0.0/stackgres-operator-demo.yml
```

STATUSが`Running`になるまで待ちます。

```
[root@k8s-master ~]# kubectl get pods -n stackgres -l group=stackgres.io
NAME                                  READY   STATUS    RESTARTS   AGE
stackgres-operator-658b86c697-wlmxd   1/1     Running   0          3m15s
stackgres-restapi-85b5fdf794-h44zt    2/2     Running   0          3m14s
```
### インスタンスプロファイル作成
デフォルトの`インスタンスプロファイル`はCPU:1、メモリ:2GBで小さすぎるので、`インスタンスプロファイル`を別途作成します。

```
cat << 'EOF' | kubectl create -f -
apiVersion: stackgres.io/v1
kind: SGInstanceProfile
metadata:
  name: size-l
spec:
  cpu: "4"
  memory: 6Gi
EOF
```

### DBクラスタ作成
DBクラスタを作成する。今回はシングル構成とするため、`instances: 1`とします。

```
[root@k8s-master ~]# cat << 'EOF' | kubectl create -f -
apiVersion: stackgres.io/v1
kind: SGCluster
metadata:
  name: simple
spec:
  instances: 1
  postgres:
    version: 'latest'
  pods:
    persistentVolume: 
      size: '30Gi'
  sgInstanceProfile: 'size-l'
EOF
```

STATUSが`Running`になるまで待ちます。

```
[root@k8s-master ~]# kubectl get pods 
NAME                                  READY   STATUS    RESTARTS   AGE
stackgres-operator-658b86c697-wlmxd   1/1     Running   0          3m15s
stackgres-restapi-85b5fdf794-h44zt    2/2     Running   0          3m14s
```

PVCも作成される。

```
[root@k8s-master ~]# kubectl get pvc
NAME                   STATUS        VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS       AGE
simple-data-simple-0   Bound         pvc-d3c500ce-a8b2-40f2-a3e1-7c822de479a3   30Gi       RWO            openebs-hostpath   8m18s
```

### pgbenchの実行
コンテナに入って、`pgbench`を実行します。
`pgbench -i`の`-s scalefactor`は1500(25GB程度)と大きめに設定し、VMに割り当てているメモリ8GBに対して、アクセスがメモリだけに留まらないよう設定しました。

```
[root@k8s-master ~]# kubectl exec simple-0 -it -- bash
bash-4.4$ createdb testdb
bash-4.4$ pgbench -i testdb -s 1500
```

一応`shared_buffers`だけ変更しておきました。

```
bash-4.4$ psql testdb
testdb=# show shared_buffers;
 shared_buffers
----------------
 2GB
(1 row)
```
pgbenchを実行

```
bash-4.4$ pgbench -c 10 -t 1000 testdb
pgbench (14.0 OnGres Inc.)
starting vacuum...end.
transaction type: <builtin: TPC-B (sort of)>
scaling factor: 1500
query mode: simple
number of clients: 10
number of threads: 1
number of transactions per client: 1000
number of transactions actually processed: 10000/10000
latency average = 3.688 ms
initial connection time = 20.249 ms
tps = 2711.554095 (without initial connection time)
```

# 結果の比較
比較用にVM直下に`pgbench`を導入してベンチマークしてみました。
微妙にPostgreSQLのバージョンが違いますがご容赦ください。
一応どちらの環境でも、OS再起動したり、pgbenchを複数回実行してみましたが、結果(latency average, tps)は殆ど変化ありませんでした。


VM直下でのベンチマーク

```
[user01@k8s-master ~]$ /usr/pgsql-14/bin/pgbench -c 10 -t 1000 testdb
pgbench (14.1)
starting vacuum...end.
transaction type: <builtin: TPC-B (sort of)>
scaling factor: 1500
query mode: simple
number of clients: 10
number of threads: 1
number of transactions per client: 1000
number of transactions actually processed: 10000/10000
latency average = 3.467 ms
initial connection time = 20.968 ms
tps = 2884.292288 (without initial connection time)
```

#まとめ
簡易な検証ですが、OpenEBSのローカルストレージの性能検証を行ってみました。
手元の環境では、VM直下で動作するpgbenchとの性能差異はあまり見受けられませんでした。
(負荷のかけ方が正しくないのかもしれないですが、、、)
今後は分散ストレージとどの程度、性能差が生じるのか確認してみようと思います。