OrioleDB を触ってみる


この記事は [PostgreSQL Advent Calendar 2023](https://qiita.com/advent-calendar/2023/postgresql) の 12月 4日の記事です。

→ 2024年に [続きの OrioleDB記事](https://qiita.com/harukat1232000/items/c9fc8597002555ffb09f)も書きました。

## OrioleDBとは

[OrioleDB](https://github.com/orioledb/orioledb) は PostgreSQL開発者でもある Alexander Korotkov 氏が開発する新たなデータベースソフトウェアです。PostgreSQL をベースとしつつ PostgreSQL の基本アーキテクチャに起因する様々な制限や問題点を解消することを目的として「Row-level WAL」「Lock-less buffer access」「Undo log」といった大型の実装置き換えが含まれています。
その一方で OrioleDB はあくまでも PostgreSQL のテーブルアクセスメソッド等を提供する拡張モジュールという形で提供されています。現状は PostgreSQL本体への修正パッチもあるのですが、それは必要な機能を拡張モジュールとして導入できるようにするための修正であって、機能提供は拡張モジュール側に記述されています。
私は先日（2023年11月24日）開催された、[日本PostgreSQLユーザ会](https://www.postgresql.jp/)主催の [PostgreSQLカンファレンス2023](https://www.postgresql.jp/jpug-pgcon2023) に運営者として携わりまして、そこで、Alexander Korotkov 氏にこの [OrioleDB について講演](https://www.postgresql.jp/jpug-pgcon2023#K1)してもらいました。本記事では、おそらく日本であまり知られていなかったであろう OrioleDB を紹介したことの補足として、実際に触ってみる手順などを紹介します。
OrioleDB のアーキテクチャについては、配布物内のドキュメントや、[カンファレンス講演資料](https://www.postgresql.jp/sites/default/files/2023-12/K1_The_Future_Of_PostgreSQL_PGCon.jp_2023.pdf)や[氏の以前の講演資料](https://www.slideshare.net/AlexanderKorotkov/solving-postgresql-wicked-problems) 参照してください。

## インストール手順

OrioleDB のインストールは、PostgreSQL をソースコードからビルド・インストールしたり、拡張モジュールをビルド・インストールしたことがある人なら、それほど難しいところがありません。
ビルド環境で必要なものは、PostgreSQLビルドと概ね同じです。以下は真っ新な Rocky Linux 9.1 で実行したコマンドです。

~~~
# dnf install -y git gcc bison flex python perl
# dnf install -y zlib-devel readline-devel libicu-devel openssl-devel libzstd-devel lz4-devel
# dnf install -y pip  # 後で testgres を導入するため
~~~

比較用と postgresユーザを作るために通常の PostgreSQL16パッケージも導入しておきます。

~~~
# dnf install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-9-x86_64/pgdg-redhat-repo-latest.noarch.rpm
# dnf -qy module disable postgresql
# dnf install -y postgresql16-server
~~~

まず、OrioleDB用のパッチの当たった PostgreSQL を導入します。
gitリポジトリとして提供されていますが PostgreSQL全体を含んでいてサイズが大きいので最新版のスナップショットを取得すれば良いでしょう。[orioledb の README.md](https://github.com/orioledb/orioledb/blob/main/README.md) に PostgreSQLメジャーバージョン系列ごとにリンクが用意されています。以下では PostgreSQL 16.x に対してパッチバージョン18 を当てたものを導入します。

~~~
# mkdir /usr/local/pgsql
# chown postgres:postgres /usr/local/pgsql
# su - postgres
$ wget https://github.com/orioledb/postgres/archive/refs/tags/patches16_18.zip
$ unzip patches16_18.zip
$ cd postgres-patches16_18/
$ ./configure --prefix=/usr/local/pgsql/16orioledb --enable-debug --with-openssl --with-zstd --with-lz4
$ make world-bin
$ make install-world-bin
$ cd -
~~~

インストールしたパッチ付き PostgreSQLにパスを通しておきます。また、PGDATA環境変数もセットしておきます。

~~~
$ cat > 16orioledb.env <<EOF
export PATH=/usr/local/pgsql/16orioledb/bin:$PATH
export LD_LIBRARY_PATH=/usr/local/pgsql/16orioledb/lib:$LD_LIBRARY_PATH
export PGDATA=/var/lib/pgsql/16/odata
EOF
$ . 16orioledb.env
$ echo ". 16orioledb.env" >> .bash_profile
~~~

続いて、orioledb 拡張を導入します。リポジトリ最新版を使います（記事執筆時 2023年 12月 2日）。
一般的な拡張のインストール手順通りです。注意点は Makefile変数 ORIOLEDB_PATCHSET_VERSION を明示的に指定しておくことです。README.md の Installation の記述では記載は無いのですが、指定せずだと自動でうまく判断してくれない場合があるようです。

~~~
$ git clone https://github.com/orioledb/orioledb.git
$ cd orioledb
$ make USE_PGXS=1 ORIOLEDB_PATCHSET_VERSION=18
$ make USE_PGXS=1 ORIOLEDB_PATCHSET_VERSION=18 install
~~~

orioledb 拡張がインストールできたなら、initdb をして、設定を行います。

~~~
$ initdb --no-locale -E UTF8
$ vi $PGDATA/postgresql.conf
  (以下を設定)
  shared_preload_libraries = 'orioledb'
  logging_collector = on

$ pg_ctl start
~~~

orioledb 拡張を組み込んだ PostgreSQL （即ち OrioleDB）が起動したなら、リグレッションテストも実行しておきましょう。リポジトリ最新版を使うときには大事な手順です。orioledb のリグレッションテストには testgresツールが使われているため、これを pip からローカルインストールしておきます。

~~~
$ pip install testgres
$ make USE_PGXS=1 ORIOLEDB_PATCHSET_VERSION=18 installcheck
~~~

このテストはそれなりに規模が大きくて、終わるまで数分待つことになります。本環境では「All checks are successful!」で無事テストを終えることができました。

## 動かしてみる

まずは、orioledb をデフォルトのテーブルアクセスメソッドとするデータベース db1 を作ります。

~~~sql
$ psql
postgres=# CREATE DATABASE db1;
postgres=# \c db1
db1=# CREATE EXTENSION orioledb;
db1=# ALTER DATABASE db1 SET default_table_access_method TO orioledb;
db1=# \q
~~~

orioledb用の postgresql.conf設定パラメータもいろいろあるのですが、まずは何もチューニングせずに pgbench を実行してみましょう。実行したマシンは、Azure の Standard D2s v3 (2 vcpu 数、8 GiB メモリ) です。
データ読み書きしてみることと、ノーマルな PostgreSQL と単純比較する狙いです。

~~~
$ pgbench -i -s 10 db1
$ pgbench -c 16 -T 120 -s 10 db1
pgbench (16rc1)
pgbench: warning: scale option ignored, using count from pgbench_branches table (10)
starting vacuum...end.
transaction type: <builtin: TPC-B (sort of)>
scaling factor: 10
query mode: simple
number of clients: 16
number of threads: 1
maximum number of tries: 1
duration: 120 s
number of transactions actually processed: 159175
number of failed transactions: 0 (0.000%)
latency average = 12.062 ms
initial connection time = 45.113 ms
tps = 1326.491695 (without initial connection time)
~~~

3回実行して、tps = 1326.491695、1264.996140、1387.240085 。
ノーマル PostgreSQL 16 で同サーバで実行すると、tps = 1305.715921、1278.107693、1282.910015 です。
これだけの比較でノーマル PostgreSQL よりちょっと速いと言っても、あまり意味はありません。重要なことは、何にも考えず何もチューニングしなくとも同じように動作して同じ程度には性能が出るという点です。
拡張された PostgreSQL互換データベースソフトウェアが、通常の PostgreSQLよりも圧倒的に性能が上回る動作が可能であるとしも、そうするためのおぜん立てが容易ではないとなりますと使い勝手の悪い代物ととなります。通常の PostgreSQLと同じだと思って使って「ひどいことにはならない」というのは重要な利点です。




## レプリケーションしてみる

続いてストリーミングレプリケーションを構築しています。
こちらも何も考えず、通常の PostgreSQL であるかのようにコマンドを実行してみましょう。
あらかじめ、1号機の postgresql.conf で listen_addresses = '*' を設定して、pg_hba.conf で 2号機からの レプリケーション接続を trust で許すように設定しておきます。

~~~
[postgres@orioledb1 ~]$ vi $PGDATA/postgresql.conf
  （以下を設定）
   listen_addresses = '*'
   
[postgres@orioledb1 ~]$ vi $PGDATA/pg_hba.conf
  （以下を設定）
   host    replication     all             10.0.0.5/32            trust
~~~

~~~
[postgres@orioledb2 ~]$ pg_basebackup -D $PGDATA -h 10.0.0.4 -R
[postgres@orioledb2 ~]$ pg_ctl start
waiting for server to start....2023-12-03 13:58:38.378 UTC [6301] LOG:  registered custom resource manager "OrioleDB resource manager" with ID 129
2023-12-03 13:58:38.449 UTC [6301] LOG:  OrioleDB public beta 3 started
2023-12-03 13:58:38.497 UTC [6301] LOG:  redirecting log output to logging collector process
2023-12-03 13:58:38.497 UTC [6301] HINT:  Future log output will appear in directory "log".
 done
server started
~~~

ホットスタンバイ設定を自動生成する -R オプションを付けて、1号機(10.0.0.4) を -h オプションに指定して、pg_basebackup を実行します。
完了後に pg_ctl start で起動。手順は本当に通常の PostgreSQL と同じです。

しかしながら、ps コマンドでプロセスを調べると、orioledb 特有の仕組みが動作していることがわかります。「orioledb recovery worker」が並列で6つ動作しています。

~~~
[postgres@orioledb2 ~]$ ps x
    PID TTY      STAT   TIME COMMAND
   4770 pts/1    S      0:00 -bash
   6301 ?        Ss     0:00 /usr/local/pgsql/16orioledb/bin/postgres
   6302 ?        Ss     0:00 postgres: logger
   6303 ?        Ss     0:00 postgres: checkpointer
   6304 ?        Ss     0:00 postgres: background writer
   6305 ?        Ss     0:00 postgres: startup recovering 0000000100000000000000
   6306 ?        Ss     0:00 postgres: orioledb background writer
   6307 ?        Ss     0:00 postgres: orioledb recovery worker 3
   6308 ?        Ss     0:00 postgres: orioledb recovery worker 2
   6309 ?        Ss     0:00 postgres: orioledb recovery worker 1
   6310 ?        Ss     0:00 postgres: orioledb recovery worker 0
   6311 ?        Ss     0:00 postgres: orioledb recovery worker 5
   6312 ?        Ss     0:00 postgres: orioledb recovery worker 4
   6313 ?        Ss     0:00 postgres: walreceiver streaming 0/16000060
   6314 pts/1    R+     0:00 ps x
~~~

orioledb をデフォルトテーブルアクセスメソッドとしている db1 についてレプリケーション動作できいますし、

~~~sql
[postgres@orioledb1 ~]$ psql db1
db1=# UPDATE pgbench_accounts SET filler = 'test' WHERE aid = 12345;
UPDATE 1
~~~

~~~sql
[postgres@orioledb2 ~]$ psql db1
db1=# SELECT * FROM pgbench_accounts WHERE aid = 12345;
  aid  | bid | abalance |                                        filler

-------+-----+----------+-------------------------------------------------------
-------------------------------
 12345 |   1 |        0 | test

(1 row)
~~~

orioledb 拡張を使っていないデータベースについてもレプリケーションができています。

~~~sql
[postgres@orioledb1 ~]$ createdb db2
[postgres@orioledb1 ~]$ psql db2
db2=# CREATE TABLE t2 (id int primary key, v text);
CREATE TABLE
db2=# INSERT INTO t2 VALUES (1, 'foo');
INSERT 0 1
~~~

~~~sql
[postgres@orioledb2 ~]$ psql db2
db2=# SELECT * FROM t2;
 id |  v
----+-----
  1 | foo
(1 row)
~~~

通常の PostgreSQL のレプリケーションと拡張された動作とがシームレスに機能しています。


## ディレクトリを覗いてみる

OrioleDB は PostgreSQL標準のものとは全く異なるストレージエンジンですので、データベースクラスタディレクトリの内容も違っているはずです。
どうなっているでしょうか。

psql で orioledb をデフォルトテーブルアクセスメソッドにした db1 を参照すると、4つのシステムビューが自動的に作られています。テーブル圧縮の状態など、テーブル・インデックスについて orioledb固有の情報が表示されます。

~~~
[postgres@orioledb1 ~]$ psql db1
psql (16rc1)
Type "help" for help.

db1=# \d
                List of relations
 Schema |         Name         | Type  |  Owner
--------+----------------------+-------+----------
 public | orioledb_index       | view  | postgres
 public | orioledb_index_descr | view  | postgres
 public | orioledb_table       | view  | postgres
 public | orioledb_table_descr | view  | postgres
 public | pgbench_accounts     | table | postgres
 public | pgbench_branches     | table | postgres
 public | pgbench_history      | table | postgres
 public | pgbench_tellers      | table | postgres
(8 rows)
~~~

また、orioleテーブルアクセスメソッドを使ったテーブルの実行プランを調べてみますと、Custom Scan による実行プランになっていることがわかります。

~~~sql
b1=# EXPLAIN SELECT * FROM pgbench_accounts WHERE aid = 1234;
                                 QUERY PLAN
-----------------------------------------------------------------------------
 Custom Scan (o_scan) on pgbench_accounts  (cost=0.17..8.19 rows=1 width=97)
   Forward index scan of: pgbench_accounts_pkey
   Conds: (aid = 1234)
(3 rows)
~~~


データベースクラスタディレクトリはどうなっているでしょうか。
orioledb_data、orioledb_undo ディレクトリが加わっています。

~~~
$ cd $PGDATA/
$ ls
PG_VERSION        pg_commit_ts   pg_replslot   pg_twophase
base              pg_dynshmem    pg_serial     pg_wal
current_logfiles  pg_hba.conf    pg_snapshots  pg_xact
global            pg_ident.conf  pg_stat       postgresql.auto.conf
log               pg_logical     pg_stat_tmp   postgresql.conf
orioledb_data     pg_multixact   pg_subtrans   postmaster.opts
orioledb_undo     pg_notify      pg_tblspc     postmaster.pid

$ ls orioledb_data/
0000000000.xidmap  1  16388  6.xid  control

$ ls orioledb_data/16388/
16462        16477-5.tmp  16482-7.map  16488-6.map  16489-7.tmp  16498
16476        16477-6.map  16483        16488-7.evt  16495        16498-5.map
16476-3.map  16477-6.tmp  16483-5.tmp  16488-7.map  16496        16498-6.map
16476-6.map  16482        16483-6.map  16489        16496-5.map
16476-7.evt  16482-3.map  16483-6.tmp  16489-6.map  16496-6.map
16476-7.map  16482-6.map  16488        16489-6.tmp  16496-7.evt
16477        16482-7.evt  16488-3.map  16489-7.map  16496-7.map

$ ls orioledb_undo/
0000000002  0000000003  0000000004
~~~

orioledb_data がデータのディレクトリです。
データベースごとのサブディレクトリ内にテーブルの格納データファイルがあります。
orioledb_undo は UNDOログのディレクトリです。更新ワークロードをかけるとこのファイルが増えていきローテーションします。


## ここまでのまとめ

本記事としては以上とさせていただきます。
いかがでしたでしょうか。
通常の PostgreSQL と同じ使い勝手でありながら、異なるアーキテクチャで置き換えが施されたが新たなデータベースソフトウェア使える、という OrioleDB のすばらしさが伝わりましたでしょうか。

OrioleDB の各固有機能や性能向上の実力、また、それらを効果的に使う方法、といったことにつきましては、別の機会に記事にさせていただけましたらと思います。