pg_simula - 志村ー、SELECT、SELECT！

# はじめに
**そのタイトルで書きたかっただけだろー！**

先日、[澤田さん](https://twitter.com/sawada_masahiko)が面白い拡張機能を公開したようなので、ちょっと触ってみた。

# pg_simulaとは
簡単に言うと、PostgreSQL障害系試験のためのツールっぽい。

澤田さんのツイート
> [開発中の機能のテスト用に、PostgreSQLの世界で任意の障害を発生させるツール作ってみた。](https://twitter.com/sawada_masahiko/status/927563760167825409)

githubで公開されている。
https://github.com/MasahikoSawada/pg_simula

今のところ、PostgreSQL 10のみ対応っぽい。

## 利用しているHOOK
HOOKインタフェースとしては、

* 実行計画生成時に通過する、planner_hook
* DML以外のSQLコマンドが通過する、ProcessUtility_hook
* セッション開始時の認証完了直前に通過する、ClientAuthentication_hook

の3種類使っているようだ。

# つかってみた
## インストール
フツーにgit cloneしてmake USE_PGXS=1, make USE_PGXS=1 install。特に問題なし。
(CentOS7, PostgresQL 10で実施)

現状はとくにregression testは組み込まれていないもよう。

```
[nuko@localhost pg_simula]$ make USE_PGXS=1 installcheck
make: `installcheck' に対して行うべき事はありません.
[nuko@localhost pg_simula]$ 
```

## セットアップ
postgresql.conf に ```shared_preload_libraries = 'pg_simula'``` を追記するだけ。
きちんとHOOKの前処理・後処理を行っているので、（たぶん）他のEXTENSIONと併用しても問題ないはず。

## カスタムパラメータ
カスタムパラメータは2種類。README.mdにも書いてあるけどこっちにも書いとく。

|変数名|型|デフォルト値|パラメータコンテキスト|説明|
|:--|:--|:--|:--|:--|
|enable 	|boolean	|false|PGC_USERSET|この拡張機能の有効化/無効化を制御。trueのときに有効|
|connection_refuse|boolean|false|PGC_USERSET|trueだとすべての新しい接続をすべて拒否する。なるほどー、このためにClientAuthentication_hookを使ってるのね。|

まあ、大抵の場合はpostgresql.confに設定せずに、必要なときにSET文で有効化すれば十分なのかも。両方のパラメータともにコンテキストはPGC_USERSETなので、一般ユーザでもSET文で任意のタイミングで指定可能だし。

## EXTENSION登録
後述するように、SQL関数を使ってイベントを登録するため、```CREATE EXTENSION```を使って、SQL関数を試験をしたいデータベースに登録する必要がある。
どのデータベースでもこの関数を使わせるなら、template1 データベースにでも登録しておけばOK。
今回の検証では、simulaというデータベースに登録しておく。

```
[nuko@localhost ~]$ psql -U postgres simula -c "CREATE EXTENSION pg_simula"
CREATE EXTENSION
[nuko@localhost ~]$ psql -U postgres simula
psql (10.0)
Type "help" for help.

simula=# \dx
                        List of installed extensions
   Name    | Version |   Schema   |               Description               
-----------+---------+------------+-----------------------------------------
 pg_simula | 1.0     | public     | Database system failure simulation tool
 plpgsql   | 1.0     | pg_catalog | PL/pgSQL procedural language
(2 rows)

simula=# \df
                              List of functions
 Schema |       Name       | Result data type | Argument data types |  Type  
--------+------------------+------------------+---------------------+--------
 public | add_simula_event | boolean          | text, text, integer | normal
 public | clear_all_events | boolean          |                     | normal
(2 rows)
```

## 障害シミュレーション設定
pg_simulaは pg_simula.operation をtrueにするだけでは何も動作しない。
動作させるためには、以下のSQL関数を使って、障害を発生させるイベントを登録する必要がある。

### bool add_simula_event(text operation, text action, sec int)
* operation : コマンドタグを設定する（ex. 'INSERT' , 'SELECT', 'CREATE VIEW'）。
    * なお、PostgreSQL文書には、コマンドタグ一覧とか別に書かれていない。
    * (コマンドタグとか)全然わからない。俺たちは雰囲気でPostgreSQLを使っている。
![雰囲気でポスグレをやっている.png](https://qiita-image-store.s3.amazonaws.com/0/32626/c5d4a780-8774-4577-722f-0d52ed081ea7.png)

    * コマンドタグを確認したいなら、log_line_prefix に```%i```を設定して見たいコマンドを叩くと、サーバログにコマンドタグが出力されて捗る。
* action : 'ERROR', 'PANIC', 'WAIT' の何れかの文字列をセットする。
    * それ以外の文字列をセットしてもチェックはしていないので、たぶん無効な設定がSimulation Event Tableに追加されるだけっぽい。
    * 'FATAL'指定がないのは、セッションアボートするとpsqlで入りなおす必要があるし、障害系試験という意味ではトランザクションアボートでも十分だから、なのかな？かな？
* sec : 秒数を整数で指定する。actionに'WAIT'を指定したときのみ有効。
    * 指定した秒数だけwaitする。その後は、何事もなかったかのように正常にコマンドを実行させる。
    * 内部的には(Linux上では)nanosleep()システムコールを使う。なので、負数を設定するとnanosleep()システムコール発行時にエラーになるはずだが、特にエラーハンドリングはしてないと思うので、0を指定したのと同じ挙動になりそう。（面倒なので確認してはいない）
* add_simula_event()で登録されたイベントは通常テーブル simula_events に永続的に格納される。

### bool clear_all_events()
* add_simula_event()で登録されたイベントをクリアしたいときに使う。
  * simula_events テーブルをSPI関数経由でTRUNCATEしている。

## イベントの登録
以下のようなイベントを登録してみる。

* SELECT文は3秒待たせる。
* INSERT文は無慈悲に全てエラーにする。
* CREATE VIEWコマンドはカジュアルにPostgreSQLサーバをクラッシュさせる。

```
simula=# SELECT add_simula_event('SELECT', 'WAIT', 3);
 add_simula_event 
------------------
 t
(1 row)

simula=# SELECT add_simula_event('INSERT', 'ERROR', 0);
 add_simula_event 
------------------
 t
(1 row)

simula=# SELECT add_simula_event('CREATE VIEW', 'PANIC', 0);
 add_simula_event 
------------------
 t
(1 row)
```

この状態で、simula_events テーブルを覗いてみるとこんな感じになる。

```
simula=# TABLE simula_events;
  operation  | action | sec 
-------------+--------+-----
 SELECT      | WAIT   |   3
 INSERT      | ERROR  |   0
 CREATE VIEW | PANIC  |   0
(3 rows)
```

この状態で、pg_simulaの機能を有効化する。

```
simula=# SET pg_simula.enabled = true;
SET
simula=#
```

### SELECT/INSERT/CREATE VIEWを実行する。
まず、SELECTを実行。

```
simula=# SELECT now();SELECT 1;SELECT now();
              now              
-------------------------------
 2017-11-07 19:41:34.703786+09
(1 row)

 ?column? 
----------
        1
(1 row)

              now              
-------------------------------
 2017-11-07 19:41:40.713972+09
(1 row)

simula=#
```
おわかりいただけたであろうか。
3つのSELECTのそれぞれがだいたい3秒待たされている。

次はINSERT。

```
simula=# INSERT INTO 
information_schema.  pg_toast.            simula_events
pg_catalog.          pg_toast_temp_1.     
pg_temp_1.           public.              
simula=# INSERT INTO simula_events VALUES ('UPDATE', 'ERROR', 0);
2017-11-07 19:47:16.518 JST [58057] ERROR:  simulation of ERROR by pg_simula
2017-11-07 19:47:16.518 JST [58057] STATEMENT:  INSERT INTO simula_events VALUES ('UPDATE', 'ERROR', 0);
ERROR:  simulation of ERROR by pg_simula
simula=# 
```

SELECTにwait指定かけちゃうと、psqlのタブ補完の関係で動作するSELECT文も情け容赦なく待たされるので、嫌がらせにはもってこいですねｗ
で、INSERT文そのものは情け容赦なくERRORになっていまいます。

最後にCREATE VIEWを実行。

```
simula=# CREATE VIEW simula_events_view AS SELECT * FROM simura_events;
2017-11-07 19:49:45.190 JST [58057] PANIC:  simulation of PANIC by pg_simula
2017-11-07 19:49:45.190 JST [58057] STATEMENT:  CREATE VIEW simula_events_view AS SELECT * FROM simura_events;
PANIC:  simulation of PANIC by pg_simula
server closed the connection unexpectedly
	This probably means the server terminated abnormally
	before or while processing the request.
The connection to the server was lost. Attempting reset: 2017-11-07 19:49:46.239 JST [57910] LOG:  server process (PID 58057) was terminated by signal 6: Aborted
（以下略）
```

CREATE VIEW背景で、SELECT文がまず動作するので、そこで3秒待たされてから、PANICが発生して、PostgreSQLサーバ自体がクラッシュします。楽しいですねｗ

なお、一般ユーザであっても、カジュアルにPostgreSQLサーバをPANICさせることができます。楽しいですねｗ

```
[nuko@localhost ~]$ createuser -U postgres foo
[nuko@localhost ~]$
[nuko@localhost ~]$ psql -U postgres simula -c "GRANT ALL ON simula_events TO foo"
GRANT
[nuko@localhost ~]$ psql -U foo simula -c "SET pg_simula.enabled = on;CREATE VIEW simula_events_view AS SELECT * FROM simura_events"
2017-11-07 19:56:55.531 JST [58642] PANIC:  simulation of PANIC by pg_simula
2017-11-07 19:56:55.531 JST [58642] STATEMENT:  SET pg_simula.enabled = on;CREATE VIEW simula_events_view AS SELECT * FROM simura_events
2017-11-07 19:56:56.297 JST [57910] LOG:  server process (PID 58642) was terminated by signal 6: Aborted
（以下略）
```

# おわりに
ということで、pg_simula拡張を試してみました。
PANICをカジュアルに発生できるので、フェールオーバ検証とかがカジュアルにできるようになるかもしれません。
あとはWAIT指定をしておいて、何かのコマンドのしかかり中に、別のコマンドを打ち込んでタイミング系の試験をするときに役立つかもしれませんね。
もしかすると、ジョーク拡張の可能性もありますがｗ


# 蛇足
SELECT/INSERT/UPDATE/DELETEって、カタカナにすると全て語尾母音が「お」なんだよな。だから、「うしろ、うしろー！」の代わりにSELECT/INSERT/UPDATE/DELETEのどれをを当てはめても語呂が良い。実にどうでもいいのだが。