PostgreSQL 17がやってくる(8) transaction_timeout

# はじめに
にゃーん。
今回は小ネタ。
PostgreSQL 17に追加されるtransaction_timeoutパラメータについてついて調べてみた。

# タイムアウトがいっぱい
PostgreSQLにはタイムアウト制御に関するパラメータがいろいろある。
このうち、セッション実行時に関するものだけでも以下のパラメータが存在する。

|パラメータ名|context|説明|
|:-|:-|:-|
|idle_in_transaction_session_timeout |user |IDLE IN TRANSACTION時間のタイムアウト値 |
|idle_session_timeou |user |IDLE時間のタイムアウト値 |
|lock_timeout |user |ロック処理のタイムアウト値 |
|statement_timeout |user |1つのSQL文実行時間のタイムアウト値。 |
|transaction_timeout |user |PostgreSQL 17追加予定パラメータ。<br>本記事で説明。 |

# transaction_timeout
transaction_timeoutパラメータはPostgreSQL 17に入る予定のパラメータだ。
この機能に関するcommitfest項目：[Transaction timeout](https://commitfest.postgresql.org/47/4040/)
Beta1のリリースノートだと以下の記載が該当する。
> Add server variable transaction_timeout to restrict the duration of transactions

## transaction_timeoutパラメータ

|パラメータ名|transaction_timeout|
|:--|:--|
|context|user|
|型|integer|
|デフォルト値|0|
|最小値|0|
|最大値|2147483647|

* 値が0の場合には、タイムアウト制御が無効とみなされる。
* 他のタイムアウト関連のパラメータ(idle_in_transaction_session_timeoutまたはstatement_timeout)の設定値とは以下のような関係がある。
  * transaction_timeoutがidle_in_transaction_session_timeoutまたはstatement_timeoutより短いか等しい場合、長い方のタイムアウトは無視される。
* contextはuserなので、セッションの最初にこのパラメータを指定することで、そのセッションに閉じた範囲でトランザクションのタイムアウト時間を設定する使い方がいいのかもしれない。

## transaction_timeoutの使い所
このパラメータが有効なケースとしては、
* トランザクション内で実行される個々のSQLの実行時間は短い。
* しかしトランザクション内で実行されるSQLの数は多い。

ケースだと思われる。
例えばOLTP系のように1つのトランザクション内でレイテンシが小さいクエリを多数発行するような場合に、トランザクション全体として処理時間がかかっているようなときに、ロールバックさせたいというケースだろうか？

# 動作検証
## 動作検証時のバージョン
先日リリースされたPostgreSQL 17 Beta1を使用した。

## 検証内容
以下のようなトランザクションを実行する。
（文字列のみのSELECTとpg_sleep(1)を繰り返し実行する）

```sql
BEGIN;
SELECT 'in tx(1)';
SELECT pg_sleep(1);
SELECT 'in tx(2)';
SELECT pg_sleep(1);
SELECT 'in tx(3)';
SELECT pg_sleep(1);
SELECT 'in tx(4)';
COMMIT;
```

``statement_timeout = 1500``を設定した場合は、トランザクション内のどのSQLステートメントも1500msを超えることはないため、トランザクションは最後まで実行される。

```sql
$ psql postgres -ef st_timeout.sql
Null display is "(null)".
SET statement_timeout = 1500;
SET
Timing is on.
BEGIN;
BEGIN
Time: 0.075 ms
SELECT 'in tx(1)';
 ?column?
----------
 in tx(1)
(1 row)

Time: 0.224 ms
SELECT pg_sleep(1);
 pg_sleep
----------

(1 row)

Time: 1001.469 ms (00:01.001)
SELECT 'in tx(2)';
 ?column?
----------
 in tx(2)
(1 row)

Time: 0.180 ms
SELECT pg_sleep(1);
 pg_sleep
----------

(1 row)

Time: 1001.781 ms (00:01.002)
SELECT 'in tx(3)';
 ?column?
----------
 in tx(3)
(1 row)

Time: 0.195 ms
SELECT pg_sleep(1);
 pg_sleep
----------

(1 row)

Time: 1000.843 ms (00:01.001)
SELECT 'in tx(4)';
 ?column?
----------
 in tx(4)
(1 row)

Time: 0.181 ms
COMMIT;
COMMIT
Time: 0.123 ms
$
```

``statement_timeout = 1500``と``transaction_timeout=3000``を設定した場合は、トランザクション内のどのSQLステートメントも1500msを超えることはないが、3回目の``pg_sleep(1)``の実行により、``transaction_timeout=3000``の閾値を超えてしまうためタイムアウトエラーとなり、そのセッションは終了する（単にトランザクションがアボートするだけではない）。

```sql
$ psql postgres -ef tx_timeout.sql
Null display is "(null)".
SET statement_timeout = 1500;
SET
SET transaction_timeout = 3000;
SET
Timing is on.
BEGIN;
BEGIN
Time: 0.085 ms
SELECT 'in tx(1)';
 ?column?
----------
 in tx(1)
(1 row)

Time: 0.228 ms
SELECT pg_sleep(1);
 pg_sleep
----------

(1 row)

Time: 1001.444 ms (00:01.001)
SELECT 'in tx(2)';
 ?column?
----------
 in tx(2)
(1 row)

Time: 0.166 ms
SELECT pg_sleep(1);
 pg_sleep
----------

(1 row)

Time: 1001.692 ms (00:01.002)
SELECT 'in tx(3)';
 ?column?
----------
 in tx(3)
(1 row)

Time: 0.162 ms
SELECT pg_sleep(1);
psql:tx_timeout.sql:16: FATAL:  terminating connection due to transaction timeout
psql:tx_timeout.sql:16: server closed the connection unexpectedly
        This probably means the server terminated abnormally
        before or while processing the request.
psql:tx_timeout.sql:16: error: connection to server was lost
$
```

# おわりに
今回はトランザクション全体の処理時間のタイムアウトを設定するtransaction_timeoutについて調べてみた。
個々のSQL文の処理時間は小さいけど、発行されるSQLの数が多い場合、あるいは発行SQL数が不定の場合などには、このパラメータを設定することで、トランザクションが長時間存在するような問題を解決できるかもしれない。