If an FDW's underlying storage mechanism has a concept of locking individual rows to prevent concurrent updates of those rows, it is usually worthwhile for the FDW to perform row-level locking with as close an approximation as practical to the semantics used in ordinary <productname>PostgreSQL</productname> tables. There are multiple considerations involved in this. FDWの元になる記憶機構が、行の同時更新を防ぐために個々の行をロックするという概念を持っているなら、PostgreSQLの通常のテーブルで使われている意味にできる限り現実的で近い行単位のロックをFDWが実施することは価値があるでしょう。 これに関していくつかの考慮点があります。
One key decision to be made is whether to perform <firstterm>early locking</firstterm> or <firstterm>late locking</firstterm>. In early locking, a row is locked when it is first retrieved from the underlying store, while in late locking, the row is locked only when it is known that it needs to be locked. (The difference arises because some rows may be discarded by locally-checked restriction or join conditions.) Early locking is much simpler and avoids extra round trips to a remote store, but it can cause locking of rows that need not have been locked, resulting in reduced concurrency or even unexpected deadlocks. Also, late locking is only possible if the row to be locked can be uniquely re-identified later. Preferably the row identifier should identify a specific version of the row, as <productname>PostgreSQL</productname> TIDs do. なされるべき重要な決定の一つは、早いロックを実行するか遅いロックを実行するか、です。 早いロックでは、行は、元となる記憶機構から最初に取り出されたときにロックされます。 一方、遅いロックでは、行は、それがロックされる必要があることがわかってからロックされます。 (この違いは、一部の行がローカルで検査される制約や結合条件によって除外されるために発生します。) 早いロックの方がずっと単純ですし、リモートの記憶機構との間の余分なやりとりもなくて済みますが、ロックしなくても良い行をロックするかもしれませんし、結果的に同時実行性が低下したり、予期しないデッドロックさえ発生します。 一方で、遅いロックは、ロックすべき行が後で一意に再識別できる場合にのみ可能です。 できれば、PostgreSQLのTIDがそうしているように、行識別子は行の特定のバージョンを識別できるのが望ましいです。
By default, <productname>PostgreSQL</productname> ignores locking considerations when interfacing to FDWs, but an FDW can perform early locking without any explicit support from the core code. The API functions described in <xref linkend="fdw-callbacks-row-locking"/>, which were added in <productname>PostgreSQL</productname> 9.5, allow an FDW to use late locking if it wishes. デフォルトではPostgreSQLはFDWとのやりとりにおいてロックの考慮をしませんが、FDWはコアのコードからの明示的なサポートなしに、早いロックを実行することができます。 PostgreSQLバージョン9.5で追加された57.2.6に記載されたAPI関数を使うことで、望むならFDWで遅いロックを使うことも可能です。
An additional consideration is that in <literal>READ COMMITTED</literal>
isolation mode, <productname>PostgreSQL</productname> may need to re-check
restriction and join conditions against an updated version of some
target tuple. Rechecking join conditions requires re-obtaining copies
of the non-target rows that were previously joined to the target tuple.
When working with standard <productname>PostgreSQL</productname> tables, this is
done by including the TIDs of the non-target tables in the column list
projected through the join, and then re-fetching non-target rows when
required. This approach keeps the join data set compact, but it
requires inexpensive re-fetch capability, as well as a TID that can
uniquely identify the row version to be re-fetched. By default,
therefore, the approach used with foreign tables is to include a copy of
the entire row fetched from a foreign table in the column list projected
through the join. This puts no special demands on the FDW but can
result in reduced performance of merge and hash joins. An FDW that is
capable of meeting the re-fetch requirements can choose to do it the
first way.
さらなる考慮点は、READ COMMITTED
分離モードにおいて、PostgreSQLは対象のタプルの更新されたバージョンに対して制約と結合条件の再検査を行う必要があるかもしれないということです。
結合条件を再検査するには、前回取得対象のタプルと結合された、取得対象外の行の複製を再取得する必要があります。
PostgreSQLの標準テーブルを使うときは、結合を通じて生成される列リストに対象でないテーブルのTIDを含めて、必要な時には対象でない行を再フェッチすることで解決しています。
この方法は結合のデータセットを小さくできますが、安価な再フェッチ機能と再フェッチすべきバージョンの行を一意に特定できるTIDが必要になります。
そのためデフォルトで外部テーブルに対して使われる方法は、外部テーブルからフェッチされた行全体を結合を通じて生成した列リストに含めるというものです。
これによりFDWに対する特別な要請はなくなりますが、マージ結合およびハッシュ結合に置いてパフォーマンスが低下する結果となるかもしれません。
再フェッチの要求を満たすことができるFDWでは最初の方法を選択するのも良いでしょう。
For an <command>UPDATE</command> or <command>DELETE</command> on a foreign table, it
is recommended that the <literal>ForeignScan</literal> operation on the target
table perform early locking on the rows that it fetches, perhaps via the
equivalent of <command>SELECT FOR UPDATE</command>. An FDW can detect whether
a table is an <command>UPDATE</command>/<command>DELETE</command> target at plan time
by comparing its relid to <literal>root->parse->resultRelation</literal>,
or at execution time by using <function>ExecRelationIsTargetRelation()</function>.
An alternative possibility is to perform late locking within the
<function>ExecForeignUpdate</function> or <function>ExecForeignDelete</function>
callback, but no special support is provided for this.
外部テーブルに対するUPDATE
やDELETE
では、対象テーブルに対するForeignScan
操作はフェッチする行を、恐らくはSELECT FOR UPDATE
と同等なものを用いてロックすることが推奨されます。
FDWはテーブルがUPDATE
またはDELETE
の対象かどうかを、計画時にそのrelidをroot->parse->resultRelation
と比較することで、あるいは実行時にExecRelationIsTargetRelation()
を使うことで検知できます。
これに代わる可能性として、ExecForeignUpdate
またはExecForeignDelete
のコールバック内で遅いロックを実行することがありますが、これについて特別なサポートは提供されません。
For foreign tables that are specified to be locked by a <command>SELECT
FOR UPDATE/SHARE</command> command, the <literal>ForeignScan</literal> operation can
again perform early locking by fetching tuples with the equivalent
of <command>SELECT FOR UPDATE/SHARE</command>. To perform late locking
instead, provide the callback functions defined
in <xref linkend="fdw-callbacks-row-locking"/>.
In <function>GetForeignRowMarkType</function>, select rowmark option
<literal>ROW_MARK_EXCLUSIVE</literal>, <literal>ROW_MARK_NOKEYEXCLUSIVE</literal>,
<literal>ROW_MARK_SHARE</literal>, or <literal>ROW_MARK_KEYSHARE</literal> depending
on the requested lock strength. (The core code will act the same
regardless of which of these four options you choose.)
Elsewhere, you can detect whether a foreign table was specified to be
locked by this type of command by using <function>get_plan_rowmark</function> at
plan time, or <function>ExecFindRowMark</function> at execution time; you must
check not only whether a non-null rowmark struct is returned, but that
its <structfield>strength</structfield> field is not <literal>LCS_NONE</literal>.
SELECT FOR UPDATE/SHARE
コマンドによりロックすることが指定された外部テーブルについて、ForeignScan
の操作ではSELECT FOR UPDATE/SHARE
と同等なものを使ってタプルをフェッチすることで、ここでも早いロックを実行できます。
逆に遅いロックを実行するには、57.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
でないことも確認しなければなりません。
Lastly, for foreign tables that are used in an <command>UPDATE</command>,
<command>DELETE</command> or <command>SELECT FOR UPDATE/SHARE</command> command but
are not specified to be row-locked, you can override the default choice
to copy entire rows by having <function>GetForeignRowMarkType</function> select
option <literal>ROW_MARK_REFERENCE</literal> when it sees lock strength
<literal>LCS_NONE</literal>. This will cause <function>RefetchForeignRow</function> to
be called with that value for <structfield>markType</structfield>; it should then
re-fetch the row without acquiring any new lock. (If you have
a <function>GetForeignRowMarkType</function> function but don't wish to re-fetch
unlocked rows, select option <literal>ROW_MARK_COPY</literal>
for <literal>LCS_NONE</literal>.)
最後に、UPDATE
、DELETE
またはSELECT FOR UPDATE/SHARE
コマンドで使用されたが、行ロックの指定はされなかった外部テーブルについて、ロック強度がLCS_NONE
になっているときにGetForeignRowMarkType
でオプションROW_MARK_REFERENCE
を選択すれば、すべての行を複製するというデフォルトの動作を変更することができます。
これにより、markType
にその値を入れてRefetchForeignRow
が呼び出されるようになります。
このとき、新しいロックを取得することなく行を再取得します。
(GetForeignRowMarkType
関数を使うが、ロックしていない行を再フェッチしたくない場合は、LCS_NONE
についてオプションROW_MARK_COPY
を選択して下さい。)
See <filename>src/include/nodes/lockoptions.h</filename>, the comments
for <type>RowMarkType</type> and <type>PlanRowMark</type>
in <filename>src/include/nodes/plannodes.h</filename>, and the comments for
<type>ExecRowMark</type> in <filename>src/include/nodes/execnodes.h</filename> for
additional information.
さらなる情報は、src/include/nodes/lockoptions.h
、src/include/nodes/plannodes.h
でのRowMarkType
とPlanRowMark
についてのコメント、src/include/nodes/execnodes.h
でのExecRowMark
についてのコメントを参照して下さい。