FDWの元になる記憶機構が、行の同時更新を防ぐために個々の行をロックするという概念を持っているなら、PostgreSQLの通常のテーブルで使われている意味にできる限り現実的で近い行単位のロックをFDWが実施することは価値があるでしょう。 これに関していくつかの考慮点があります。
なされるべき重要な決定の一つは、早いロックを実行するか遅いロックを実行するか、です。 早いロックでは、行は、元となる記憶機構から最初に取り出されたときにロックされます。 一方、遅いロックでは、行は、それがロックされる必要があることがわかってからロックされます。 (この違いは、一部の行がローカルで検査される制約や結合条件によって除外されるために発生します。) 早いロックの方がずっと単純ですし、リモートの記憶機構との間の余分なやりとりもなくて済みますが、ロックしなくても良い行をロックするかもしれませんし、結果的に同時実行性が低下したり、予期しないデッドロックさえ発生します。 一方で、遅いロックは、ロックすべき行が後で一意に再識別できる場合にのみ可能です。 できれば、PostgreSQLのTIDがそうしているように、行識別子は行の特定のバージョンを識別できるのが望ましいです。
デフォルトではPostgreSQLはFDWとのやりとりにおいてロックの考慮をしませんが、FDWはコアのコードからの明示的なサポートなしに、早いロックを実行することができます。 PostgreSQLバージョン9.5で追加された59.2.6に記載されたAPI関数を使うことで、望むならFDWで遅いロックを使うことも可能です。
さらなる考慮点は、READ COMMITTED
分離モードにおいて、PostgreSQLは対象のタプルの更新されたバージョンに対して制約と結合条件の再検査を行う必要があるかもしれないということです。
結合条件を再検査するには、前回取得対象のタプルと結合された、取得対象外の行の複製を再取得する必要があります。
PostgreSQLの標準テーブルを使うときは、結合を通じて生成される列リストに対象でないテーブルのTIDを含めて、必要な時には対象でない行を再フェッチすることで解決しています。
この方法は結合のデータセットを小さくできますが、安価な再フェッチ機能と再フェッチすべきバージョンの行を一意に特定できるTIDが必要になります。
そのためデフォルトで外部テーブルに対して使われる方法は、外部テーブルからフェッチされた行全体を結合を通じて生成した列リストに含めるというものです。
これによりFDWに対する特別な要請はなくなりますが、マージ結合およびハッシュ結合に置いてパフォーマンスが低下する結果となるかもしれません。
再フェッチの要求を満たすことができるFDWでは最初の方法を選択するのも良いでしょう。
外部テーブルに対するUPDATE
やDELETE
では、対象テーブルに対するForeignScan
操作はフェッチする行を、恐らくはSELECT FOR UPDATE
と同等なものを用いてロックすることが推奨されます。
FDWはテーブルがUPDATE
またはDELETE
の対象かどうかを、計画時にそのrelidをroot->parse->resultRelation
と比較することで、あるいは実行時にExecRelationIsTargetRelation()
を使うことで検知できます。
これに代わる可能性として、ExecForeignUpdate
またはExecForeignDelete
のコールバック内で遅いロックを実行することがありますが、これについて特別なサポートは提供されません。
SELECT FOR UPDATE/SHARE
コマンドによりロックすることが指定された外部テーブルについて、ForeignScan
の操作ではSELECT FOR UPDATE/SHARE
と同等なものを使ってタプルをフェッチすることで、ここでも早いロックを実行できます。
逆に遅いロックを実行するには、59.2.6で定義されるコールバック関数を提供して下さい。
GetForeignRowMarkType
では、要求されたロックの強度に応じて、rowmarkのオプションROW_MARK_EXCLUSIVE
、ROW_MARK_NOKEYEXCLUSIVE
、ROW_MARK_SHARE
またはROW_MARK_KEYSHARE
を選択して下さい。
(コアのコードは、この4つのオプションのどれが選ばれたかに関係なく、同じ動作をします。)
その他には、この種のコマンドによって外部テーブルのロックが指定されたかどうかを、計画時にget_plan_rowmark
を使うことで、あるいは実行時にExecFindRowMark
を使うことで検知できます。
このとき、NULLでないrowmark構造体が戻されるかどうかだけでなく、そのstrength
フィールドがLCS_NONE
でないことも確認しなければなりません。
最後に、UPDATE
、DELETE
またはSELECT FOR UPDATE/SHARE
コマンドで使用されたが、行ロックの指定はされなかった外部テーブルについて、ロック強度がLCS_NONE
になっているときにGetForeignRowMarkType
でオプションROW_MARK_REFERENCE
を選択すれば、すべての行を複製するというデフォルトの動作を変更することができます。
これにより、markType
にその値を入れてRefetchForeignRow
が呼び出されるようになります。
このとき、新しいロックを取得することなく行を再取得します。
(GetForeignRowMarkType
関数を使うが、ロックしていない行を再フェッチしたくない場合は、LCS_NONE
についてオプションROW_MARK_COPY
を選択して下さい。)
さらなる情報は、src/include/nodes/lockoptions.h
、src/include/nodes/plannodes.h
でのRowMarkType
とPlanRowMark
についてのコメント、src/include/nodes/execnodes.h
でのExecRowMark
についてのコメントを参照して下さい。