翻訳は機械翻訳により提供されています。提供された翻訳内容と英語版の間で齟齬、不一致または矛盾がある場合、英語版が優先します。
Amazon Neptune は、読み取り専用クエリとミューテーションクエリに異なるトランザクション分離レベルを実装します。SPARQL および Gremlin クエリは、以下の基準に基づいて読み取り専用またはミューテーションとして分類されます。
-
SPARQL では、読み取りクエリ (
SELECT
、ASK
、CONSTRUCT
、およびDESCRIBE
SPARQL 1.1 クエリ言語仕様で定義される)、とミューテーションクエリ ( INSERT
およびDELETE
、SPARQL 1.1 アップデート仕様で定義される) の明確な区別があります。 Neptune は、一緒に送信された複数のミューテーションクエリ (セミコロンで区切られた
POST
メッセージなど) を 1 つのトランザクションとして扱います。これらは、アトミックユニットとして成功するか失敗するかが保証されています。障害が発生した場合、部分的な変更がロールバックされます。 ただし、Gremlin では、Neptune は、データを操作する
addE()
、addV()
、property()
、drop()
などのクエリパスステップが含まれているかどうかに基づいて、クエリを読み取り専用クエリまたはミューテーションクエリとして分類します。クエリにそのようなパスステップが含まれている場合、クエリはミューテーションクエリとして分類され、実行されます。
Gremlin でスタンドセッションを使用することもできます。詳細については、「Gremlin スクリプトベースのセッション」を参照してください。これらのセッションでは、読み取り専用クエリを含め、すべてのクエリが、ライターエンドポイントに対するミューテーションクエリと同じ分離下で実行されます。
openCypher で Bolt 読み取り/書込みセッションを使用して、読み取り専用クエリを含め、すべてのクエリが、ライターエンドポイントに対するミューテーションクエリと同じ分離下で実行されます。
Neptune での読み取り専用クエリの分離
Neptune は、スナップショット分離セマンティクスで読み取り専用クエリを評価します。つまり、読み取り専用クエリは、クエリ評価の開始時に作成されたデータベースの整合性のあるスナップショットで論理的に動作します。Neptune は、次の現象が発生しないことを保証します。
Dirty reads
– Neptune の読み取り専用クエリでは、同時トランザクションからのコミットされていないデータを参照しません。Non-repeatable reads
– 同じデータを複数回読み取る読み取り専用トランザクションは、常に同じ値を返します。Phantom reads
– 読み取り専用トランザクションは、トランザクションの開始後に追加されたデータを読み取ることはありません。
スナップショットの分離は複数バージョンの同時実行制御 (MVCC) を使用して実現されるため、読み取り専用クエリはデータをロックする必要がなく、ミューテーションクエリをブロックしません。
リードレプリカは読み取り専用クエリのみを受け付けるため、リードレプリカに対するすべてのクエリは SNAPSHOT
分離セマンティクスで実行されます。
リードレプリカをクエリするときの唯一の追加の考慮事項は、ライターとリードレプリカの間にわずかなレプリケーション遅延が生じる可能性があることです。つまり、ライターで行われた更新が、読み取り元のリードレプリカに反映されるまで少し時間がかかることがあります。実際のレプリケーション時間は、プライマリインスタンスに対する書き込み負荷によって異なります。Neptune アーキテクチャは低レイテンシーのレプリケーションをサポートし、レプリケーションラグは Amazon CloudWatch メトリクスで計測されます。
それでも、SNAPSHOT
分離レベルのため、読み込みクエリは、たとえ最新でない場合でも、常に一貫性のあるデータベースの状態を参照します。
クエリが以前の更新の結果を確認することを強く保証する必要がある場合は、リードレプリカではなくライターエンドポイント自体にクエリを送信します。
Neptune でのミューテーションクエリの分離
ミューテーションクエリの一部として行われた読み込みは、READ COMMITTED
トランザクション分離下で実行され、ダーティな読み取りの可能性は除外されます。Neptune は、READ COMMITTED
トランザクション分離で提供される通常の保証を超えて、NON-REPEATABLE
も PHANTOM
読み取りも発生しないという強力な保証を提供します。
これらの強力な保証は、データの読み取り時にレコードやレコードの範囲をロックすることによって実現されます。これにより、読み取り後に同時トランザクションがインデックス範囲で挿入または削除を行わないため、同じ読み込みの繰り返しが保証されます。
注記
ただし、同時ミューテーショントランザクション Tx2
は、ミューテーショントランザクション Tx1
の開始後に開始でき、Tx1
がロックデータを読み取る前に変更をコミットできます。この場合、Tx1
は Tx1
の開始前に Tx2
が完了したかのように Tx2
の変更を参照します。これはコミットされた変更にのみ適用されるため、dirty
read
が発生することはありません。
Neptune がミューテーションクエリに使用するロックメカニズムを理解するには、まずNeptune の詳細を理解することが役立ちます。グラフデータモデルそしてインデックス作成戦略。Neptune は SPOG
、POGS
、および GPSO
の 3 つのインデックスを使用してデータを管理します。
READ COMMITTED
トランザクションレベルで繰り返し可能な読み込みを実現するために、Neptune は使用されているインデックスの範囲ロックを取得します。たとえば、ミューテーションクエリが person1
という名前の頂点のすべてのプロパティと出力エッジを読み取る場合、ノードはデータを読み取る前に SPOG
インデックスのプレフィックス S=person1
で定義された範囲全体をロックします。
他のインデックスを使用する場合も同じメカニズムが適用されます。たとえば、ミューテーショントランザクションが、POGS
インデックスを使用して特定のエッジラベルのソースとターゲットの頂点ペアをすべて検索すると、P
位置のエッジラベルの範囲がロックされます。同時トランザクションは、読み取り専用クエリかミューテーションクエリかにかかわらず、ロックされた範囲内で読み取りを実行できます。ただし、ロックされたプレフィックス範囲内の新しいレコードの挿入または削除を含むミューテーションは、排他的なロックを必要とし、禁止されます。
つまり、インデックスの範囲がミューテーショントランザクションによって読み取られた場合、この範囲は読み取りトランザクションが終了するまで同時トランザクションによって変更されないという強力な保証があります。これにより、non-repeatable
reads
が発生しないことが保証されます。
ロック待機タイムアウトを使用した競合の解決
2 番目のトランザクションが、最初のトランザクションがロックした範囲のレコードを変更しようとすると、Neptune は競合を即座に検出し、2 番目のトランザクションをブロックします。
依存関係のデッドロックが検出されない場合、Neptune は自動的にロック待機タイムアウトメカニズムを適用します。このメカニズムでは、ブロックされたトランザクションは、ロックを保持しているトランザクションが終了してロックを解放するまで最大 60 秒待機します。
ロックが解放される前にロック待機タイムアウトが期限切れになると、ブロックされたトランザクションはロールバックされます。
ロックがロック待機タイムアウト内に解放された場合、2 番目のトランザクションはブロック解除され、再試行することなく正常に終了できます。
ただし、Neptune が 2 つのトランザクション間の依存関係デッドロックを検出した場合、競合の自動調整はできません。この場合、Neptune はロック待機タイムアウトを開始することなく、2 つのトランザクションの 1 つを直ちにキャンセルしてロールバックします。Neptune は、挿入または削除されたレコードが最も少ないトランザクションをロールバックするよう最善を尽くします。
範囲ロックと誤った競合
Neptune はギャップロックを使用して範囲ロックを行います。ギャップロックは、インデックスレコード間のギャップに対するロック、または最初のインデックスレコードの前または最後のインデックスレコードの後のギャップに対するロックです。
Neptune は、いわゆるディクショナリテーブルを使用して、数値 ID 値を特定の文字列リテラルに関連付けます。このような Neptune ディクショナリテーブルのサンプルステートは次のとおりです。
String | ID |
---|---|
type |
1 |
default_graph |
2 |
person_3 |
3 |
person_1 |
5 |
knows |
6 |
person_2 |
7 |
age |
8 |
edge_1 |
9 |
lives_in |
10 |
New York |
11 |
個人 |
12 |
Place |
13 |
edge_2 |
14 |
上記の文字列はプロパティグラフモデルに属しますが、概念はすべての RDF グラフモデルにも等しく当てはまります。
対応する SPOG (Subject-Predicate-Object_Graph) インデックスの状態は、以下の左側に示されています。右側には、インデックスデータの意味を理解しやすいように、対応する文字列が示されています。
S (ID) | P (ID) | O (ID) | G (ID) | S (文字列) | P (文字列) | O (文字列) | G (文字列) | |
---|---|---|---|---|---|---|---|---|
3 |
1 |
12 |
2 |
person_3 |
type |
個人 |
default_graph |
|
5 |
1 |
12 |
2 |
person_1 |
type |
個人 |
default_graph |
|
5 |
6 |
3 |
9 |
person_1 |
knows |
person_3 |
edge_1 |
|
5 |
8 |
40 |
2 |
person_1 |
age |
40 |
default_graph |
|
5 |
10 |
11 |
14 |
person_1 |
lives_in |
New York |
edge_2 |
|
7 |
1 |
12 |
2 |
person_2 |
type |
個人 |
default_graph |
|
11 |
1 |
13 |
2 |
New York |
type |
Place |
default_graph |
今度は、ミューテーションクエリが person_1
という名前の頂点のすべてのプロパティと出力エッジを読み取る場合、ノードはデータを読み取る前に SPOG インデックスのプレフィックス S=person_1
で定義された範囲全体をロックします。範囲ロックは、一致するすべてのレコードと、一致しない最初のレコードにギャップロックをかけます。一致するレコードはロックされ、一致しないレコードはロックされません。Neptune は、次のようにギャップロックを掛けます。
5 1 12 2
(ギャップ 1)5 6 3 9
(ギャップ 2)5 8 40 2
(ギャップ 3)5 10 11 14
(ギャップ 4)7 1 12 2
(ギャップ 5)
これにより、以下のレコードがロックされます。
5 1 12 2
5 6 3 9
5 8 40 2
5 10 11 14
この状態では、以下の操作は合法的にブロックされます。
S=person_1
について新しいプロパティまたはエッジの挿入。type
と異なる新しいプロパティや新しいエッジは、ギャップ 2、ギャップ 3、ギャップ 4、ギャップ 5 のいずれかに配置する必要があり、これらはすべてロックされます。既存のレコードのいずれかの削除。
同時に、いくつかの同時操作が誤ってブロックされます (誤った競合が発生します)。
S=person_3
のプロパティやエッジの挿入は、ギャップ 1 に入らなければならないためブロックされます。3 から 5 の間の ID を割り当てられた新しい頂点の挿入は、ギャップ 1 に入らなければならないためブロックされます。
5 から 7 の間の ID を割り当てられた新しい頂点の挿入は、ギャップ 5 に入らなければならないためブロックされます。
ギャップロックは、特定の 1 つの述部のギャップをロックするほど正確ではありません (例えば、述語 S=5
の場合、gap5 をロックするなど)。
範囲ロックは読み取りが行われるインデックスにのみ適用されます。上記の場合、レコードは SPOG インデックスでのみロックされ、POGS や GPSO ではロックされません。アクセスパターンによっては、クエリの読み取りがすべてのインデックスに対して行われる場合があります。アクセスパターンは explain
API (Sparql および Gremlin の場合) を使用して一覧表示できます。
注記
また、基礎となるインデックスを安全に同時更新するためにギャップロックを適用することもできますが、これによって誤った競合が発生する可能性もあります。これらのギャップロックは、トランザクションによって実行される分離レベルや読み取り操作とは無関係に設定されます。
誤った競合は、ギャップロックが原因で同時実行中のトランザクションが衝突したときだけでなく、何らかの障害が発生した後にトランザクションが再試行された場合も発生する可能性があります。障害によってトリガーされたロールバックがまだ進行中で、そのトランザクションで以前に掛けられたロックがまだ完全に解除されていない場合、再試行は誤った競合が発生して失敗します。
負荷が高いと、通常、書き込みクエリの 3 ~ 4% が誤った競合のために失敗することがあります。外部クライアントの場合、このような誤った競合は予測が難しく、再試行を使用して処理する必要があります。