スキップしてメイン コンテンツに移動

COMMITについて少し考えてみた(4)

前回、Oracleの場合と同様にPostgreSQLのCOMMIT失敗時にトランザクションの結果が不定になる状況を確認しました。
今回は、PostgreSQL 10から導入された行方不明になってしまったトランザクションの結果を確認するtxid_status()について確認してみます。

前回のCOMMIT問題のまとめ

txid_status()を使う流れ

txid_status()でトランザクションの結果が不明になった状態を確認する大まかな流れは以下のような感じになります。
  1. COMMIT前のトランザクションIDをtxid_current()もしくはtxid_current_if_assigned()で取得しておく
  2. COMMITの状態が不明になった場合に、1で取得したtxidを引数にしてtxid_status()でトランザクションの状態を確認する

      前回と同じ方法でCOMMITエラーを発生させてみる

      WALファイルへの書き込み完了前に障害が発生した場合

      1) INSERTを実行(AUTOCOMMITはoffに設定)
       postgres=# insert into sample_table values (1);
      2) txid_current()でトランザクションIDを取得
       postgres=# select txid_current();
        txid_current
       --------------
                 627
       (1 行)
      3) backendのrecvfrom(2)をブロック(上記シーケンス図の①の部分)
       postgres=# select pg_backend_pid();
        pg_backend_pid
       ----------------
                 18623
       (1 行)
       $ gdb -p 18623
       ...
       (gdb) catch syscall recvfrom
       Catchpoint 1 (syscall 'recvfrom' [45])
       (gdb) c
       Continuing.
      4) COMMITを実行
       postgres=# commit;
      上記の状態でCOMMITが完了することはありません
      5) この状態でbackendの障害を発生(kill)させてみます
       $ kill -9 18623
      また、2)のgdbのセッションをキャンセルします
      6) psqlにエラーが返ります
       postgres=# commit;
       サーバとの接続が想定外にクローズされました
               おそらく要求の処理前または処理中にサーバが異常終了
               したことを意味しています。
       サーバへの接続が失われました。リセットしています: 成功。
      7) 再接続して失敗したCOMMITの状態を確認します
       postgres=# select txid_status(627);
        txid_status
       -------------
        aborted <- コミットは未完了
       (1 行)
      8) データが存在しないことを確認します
       postgres=# select * from sample_table;
        id
       ----
       (0 行)

      今度はWALファイルへの書き込み完了後が障害が発生した場合

      1) INSERTを実行(AUTOCOMMITはoffに設定)
       postgres=# insert into sample_table values (1);
      2) txid_current()でトランザクションIDを取得
       postgres=# select txid_current();
        txid_current
       --------------
                 628
       (1 行)
      3) backendのrecv(2)をブロック(上記シーケンス図の①の部分)
       postgres=# select pg_backend_pid();
        pg_backend_pid
       ----------------
                 18667
       (1 行)
       $ gdb -p 18667
       ...
       (gdb) catch syscall sendto
       Catchpoint 1 (syscall 'sendto' [44])
       (gdb) c
       Continuing.
      4) COMMITを実行
       postgres=# commit;
      上記の状態でCOMMITが完了することはありません
      5) この状態でbackendの障害を発生(kill)させてみます
       $ kill -9 18667
      また、2)のgdbのセッションをキャンセルします
      6) psqlにエラーが返ります
       postgres=# commit;
       サーバとの接続が想定外にクローズされました
               おそらく要求の処理前または処理中にサーバが異常終了
               したことを意味しています。
       サーバへの接続が失われました。リセットしています: 成功。
      7) 再接続して失敗したCOMMITの状態を確認します
       postgres=# select txid_status(628);
        txid_status
       -------------
        committed <- コミットは完了
       (1 行)
      8) データが存在することを確認します
       postgres=# select * from sample_table;
        id
       ----
         1
       (1 行)

      まとめ

      数回に渡ってOracleとPostgreSQLのCOMMITの失敗によるトランザクションの結果はCOMMIT中に障害が発生するタイミングに依存することを見てきました。また、トランザクションの結果が不明になってしまった場合、Oracleではトランザクションガード、PostgreSQL(10以降)ではtxid_status()により過去(txid_current()等で取得したトランザクションID)のトランザクションの結果が取得できるので信頼性の高いアプリケーションを構築する上で参考にしてみてください。

      コメント