Index access methods must handle concurrent updates
of the index by multiple processes.
The core <productname>PostgreSQL</productname> system obtains
<literal>AccessShareLock</literal> on the index during an index scan, and
<literal>RowExclusiveLock</literal> when updating the index (including plain
<command>VACUUM</command>). Since these lock types do not conflict, the access
method is responsible for handling any fine-grained locking it might need.
An <literal>ACCESS EXCLUSIVE</literal> lock on the index as a whole will be
taken only during index creation, destruction, or <command>REINDEX</command>
(<literal>SHARE UPDATE EXCLUSIVE</literal> is taken instead with
<literal>CONCURRENTLY</literal>).
インデックスアクセスメソッドは、複数のプロセスによるインデックスの同時更新を取り扱えなければなりません。
PostgreSQLコアシステムはインデックススキャン中にインデックスに対してAccessShareLock
を獲得します。
また、(通常のVACUUM
を含む)インデックスの更新中にRowExclusiveLock
を獲得します。
これらの種類のロックは競合しませんので、アクセスメソッドは必要になるかもしれない粒度の細かなロック処理に関して責任を持ちます。
インデックスの生成、破棄、REINDEX
時にインデックス全体に対するACCESS EXCLUSIVE
ロックが獲得されます(CONCURRENTLY
では代わりにSHARE UPDATE EXCLUSIVE
が取得されます)。
Building an index type that supports concurrent updates usually requires
extensive and subtle analysis of the required behavior. For the b-tree
and hash index types, you can read about the design decisions involved in
<filename>src/backend/access/nbtree/README</filename> and
<filename>src/backend/access/hash/README</filename>.
同時更新をサポートするインデックス種類を構築することは通常、必要な動作について広範かつ微細にわたる解析が必要です。
B-treeおよびハッシュインデックス種類では、src/backend/access/nbtree/README
と src/backend/access/hash/README
にある設計に関する決定事項を読むことができます。
Aside from the index's own internal consistency requirements, concurrent updates create issues about consistency between the parent table (the <firstterm>heap</firstterm>) and the index. Because <productname>PostgreSQL</productname> separates accesses and updates of the heap from those of the index, there are windows in which the index might be inconsistent with the heap. We handle this problem with the following rules: インデックス自身の内部的な一貫性要求の他に、同時実行更新には、親テーブル(ヒープ)とインデックス間の一貫性に関する問題が発生します。 PostgreSQLはヒープへのアクセスおよび更新とインデックスへのアクセスおよび更新を分離していますので、インデックスとヒープとの間の一貫性が無くなる間隔が存在します。 以下の規則でこうした問題を扱います。
A new heap entry is made before making its index entries. (Therefore a concurrent index scan is likely to fail to see the heap entry. This is okay because the index reader would be uninterested in an uncommitted row anyway. But see <xref linkend="index-unique-checks"/>.) 新しいヒープ項目はインデックス項目を作成する前に作成されます。 (このため、同時実行インデックススキャンはヒープエントリを確認する時によく失敗します。 インデックスの読み取りは、未コミットの行を対象としませんので問題ありません。 しかし、62.5を参照してください。)
When a heap entry is to be deleted (by <command>VACUUM</command>), all its
index entries must be removed first.
ヒープエントリが(VACUUM
によって)削除される時、これに対するすべてのインデックス項目が先に削除されます。
An index scan must maintain a pin
on the index page holding the item last returned by
<function>amgettuple</function>, and <function>ambulkdelete</function> cannot delete
entries from pages that are pinned by other backends. The need
for this rule is explained below.
インデックススキャンは、最後にamgettuple
が返した項目を保持するインデックスページ上のピンを管理しなければなりません。
また、ambulkdelete
は、他のバックエンドがピンを持つページから項目を削除することはできません。
この規則の必要性については後で説明します。
Without the third rule, it is possible for an index reader to
see an index entry just before it is removed by <command>VACUUM</command>, and
then to arrive at the corresponding heap entry after that was removed by
<command>VACUUM</command>.
This creates no serious problems if that item
number is still unused when the reader reaches it, since an empty
item slot will be ignored by <function>heap_fetch()</function>. But what if a
third backend has already re-used the item slot for something else?
When using an MVCC-compliant snapshot, there is no problem because
the new occupant of the slot is certain to be too new to pass the
snapshot test. However, with a non-MVCC-compliant snapshot (such as
<literal>SnapshotAny</literal>), it would be possible to accept and return
a row that does not in fact match the scan keys. We could defend
against this scenario by requiring the scan keys to be rechecked
against the heap row in all cases, but that is too expensive. Instead,
we use a pin on an index page as a proxy to indicate that the reader
might still be <quote>in flight</quote> from the index entry to the matching
heap entry. Making <function>ambulkdelete</function> block on such a pin ensures
that <command>VACUUM</command> cannot delete the heap entry before the reader
is done with it. This solution costs little in run time, and adds blocking
overhead only in the rare cases where there actually is a conflict.
3番目の規則がないと、VACUUM
によって削除される直前に、インデックス読み取りがインデックス項目を見つけ、そして、VACUUM
によって削除された後に対応するヒープ項目に達する可能性があります。
空の項目スロットはheap_fetch()
で無視されますので、これは読み取りが達した時にその項目番号が未使用である場合でも大きな問題は起こりません。
しかし、第三のバックエンドがすでにその項目スロットを他のものに再使用した場合はどうなるでしょうか?
そのスロット内の新しいものが、スナップショット試験を通過するには新しすぎることが確実ですので、MVCCに則ったスナップショットを使用する場合は問題ありません。
しかし、MVCCに則らないスナップショット(SnapshotAny
など)では、実際にはスキャンキーに合わない行を受付け、返す可能性があります。
すべての場合においてヒープ行に対しスキャンキーの再検査を行うことを必須とすることで、こうした状況から保護することができますが、これは高価すぎます。
代わりに、読み取りがまだ一致するヒープ項目へのインデックス項目の「作業中」であることを示す代理として、インデックスページに対するピンを使用します。
このピンに対してambulkdelete
がブロックするようにすることで、読み取りの作業が終わる前にVACUUM
がそのヒープ項目を削除できないことを確実にします。
実行時におけるこの対策のコストは小さく、実際に競合が発生するごく稀な場合にのみブロックするためのオーバーヘッドが加わります。
This solution requires that index scans be <quote>synchronous</quote>: we have to fetch each heap tuple immediately after scanning the corresponding index entry. This is expensive for a number of reasons. An <quote>asynchronous</quote> scan in which we collect many TIDs from the index, and only visit the heap tuples sometime later, requires much less index locking overhead and can allow a more efficient heap access pattern. Per the above analysis, we must use the synchronous approach for non-MVCC-compliant snapshots, but an asynchronous scan is workable for a query using an MVCC snapshot. この対策は、インデックススキャンが「同期」していることを要求します。 対応するインデックス項目のスキャンの後即座に各ヒープタプルを取り出さなければなりません。 多くの理由のため、これは高価です。 インデックスから多くのTIDを収集し、少し後でのみヒープタプルにアクセスする「非同期」スキャンでは、必要なロック処理オーバーヘッドがかなり少なくなり、また、より効率的なヒープへのアクセスパターンを取ることができます。 上の解析に従うと、MVCCに則らないスナップショットでは同期方式を使用しなければなりませんが、問い合わせがMVCCスナップショットを使用する場合は非同期スキャンを使用することができます。
In an <function>amgetbitmap</function> index scan, the access method does not
keep an index pin on any of the returned tuples. Therefore
it is only safe to use such scans with MVCC-compliant snapshots.
amgetbitmap
インデックススキャンでは、アクセスメソッドは返されるタプル上にインデックスピンをまったく保持しません。
したがって、MVCCに則ったスナップショットでこうしたスキャンを使用することのみが安全です。
When the <structfield>ampredlocks</structfield> flag is not set, any scan using that
index access method within a serializable transaction will acquire a
nonblocking predicate lock on the full index. This will generate a
read-write conflict with the insert of any tuple into that index by a
concurrent serializable transaction. If certain patterns of read-write
conflicts are detected among a set of concurrent serializable
transactions, one of those transactions may be canceled to protect data
integrity. When the flag is set, it indicates that the index access
method implements finer-grained predicate locking, which will tend to
reduce the frequency of such transaction cancellations.
ampredlocks
フラグが設定されていない場合、シリアライザブルトランザクション内でそのインデックスアクセスメソッドを使用するスキャンはいずれもインデックス全体に対するブロックしない述語ロックを獲得します。
これは、同時実行のシリアライザブルトランザクションによるそのインデックスへの何らかのタプル挿入で、読み書きの競合が発生することがあります。
同時実行のシリアライザブルトランザクションの集合の中で特定の読み書きの競合パターンが検知された場合、データの整合性を保護するためにこれらのトランザクションの1つはキャンセルされます。
このフラグが設定されている場合、こうしたトランザクションのキャンセルの頻度を低減することになる、より粒度の細かな述語ロックをインデックスアクセスメソッドが実装していることを示します。