トランザクションとオプティミスティック同時実行制御

適用対象: NoSQL

データベース トランザクションでは、データの同時変更に対応する、安全で予測可能なプログラミング モデルが提供されます。 SQL Server などの従来のリレーショナル データベースは、ストアド プロシージャやトリガーを使用してビジネス ロジックを記述し、それをサーバーに送信することで、データベース エンジン内で直接実行することができます。 従来のリレーショナル データベースの場合、2 種類のプログラミング言語に対応することが求められます。1 つは、JavaScript、Python、C#、Java などの (非トランザクションの) アプリケーション プログラミング言語であり、もう 1 つはデータベースによってネイティブに実行されるトランザクションのプログラミング言語 (T-SQL など) です。

Azure Cosmos DB 内のデータベース エンジンでは、スナップショット分離を使用して ACID (原子性、一貫性、分離、持続性) への完全準拠のトランザクションがサポートされています。 コンテナーの論理パーティションのスコープ内におけるデータベース操作はすべて、そのパーティションのレプリカによってホストされたデータベース エンジン内でトランザクションとして実行されます。 これらの操作には、書き込み操作 (論理パーティション内の 1 つまたは複数の項目の更新) と読み取り操作の両方が含まれます。 次の表に、さまざまな操作とトランザクションの種類を示します。

操作 操作の種類 1 つまたは複数の項目のトランザクション
挿入 (事前/事後トリガーなし) Write 単一項目のトランザクション
挿入 (事前/事後トリガーを使用) 書き込みと読み取り 複数項目のトランザクション
置換 (事前/事後トリガーなし) Write 単一項目のトランザクション
置換 (事前/事後トリガーを使用) 書き込みと読み取り 複数項目のトランザクション
Upsert (事前/事後トリガーなし) Write 単一項目のトランザクション
Upsert (事前/事後トリガーを使用) 書き込みと読み取り 複数項目のトランザクション
削除 (事前/事後トリガーなし) Write 単一項目のトランザクション
削除 (事前/事後トリガーを使用) 書き込みと読み取り 複数項目のトランザクション
ストアド プロシージャの実行 書き込みと読み取り 複数項目のトランザクション
システムによって開始されるマージ プロシージャの実行 Write 複数項目のトランザクション
項目の有効期限 (TTL) に基づいて、システムによって開始される項目削除の実行 Write 複数項目のトランザクション
Read Read 単一項目のトランザクション
変更フィード Read 複数項目のトランザクション
ページ分割された読み取り Read 複数項目のトランザクション
ページ分割されたクエリ Read 複数項目のトランザクション
ページ分割されたクエリの一部として UDF を実行 Read 複数項目のトランザクション

複数項目のトランザクション

Azure Cosmos DB を使用すると、ストアド プロシージャ、事前/事後トリガー、ユーザー定義関数 (UDF)、およびマージ プロシージャを JavaScript で記述できます。 Azure Cosmos DB のデータベース エンジン内では、JavaScript の実行がネイティブでサポートされています。 ストアド プロシージャ、事前/事後トリガー、ユーザー定義関数 (UDF)、およびマージ プロシージャをコンテナーに登録すると、それらを後で Azure Cosmos DB データベース エンジン内でトランザクションとして実行することができます。 JavaScript でアプリケーション ロジックを記述する場合、制御フロー、変数のスコーピング、割り当て、およびデータベース トランザクション内での例外処理プリミティブの直接統合を JavaScript プログラミング言語で自然に表現することができます。

JavaScript ベースのストアド プロシージャ、トリガー、UDF、およびマージ プロシージャは、論理パーティション内のすべての項目でのスナップショット分離によって、アンビエント ACID トランザクション内にラップされます。 その実行中に JavaScript プログラムで例外がスローされた場合、トランザクション全体が中止され、ロールバックされます。 結果のプログラミング モデルは簡潔でありながら強力なものになります。 JavaScript 開発者は、使い慣れた言語コンストラクトとライブラリ プリミティブを引き続き利用しながら、耐性のあるプログラミング モデルを手に入れることができます。

データベース エンジン内部で JavaScript を直接実行する機能により、コンテナーの項目に対するデータベース操作が高いパフォーマンスでトランザクション実行されます。 さらに、Azure Cosmos DB データベース エンジンによって JSON および JavaScript がネイティブでサポートされていることから、アプリケーションとデータベースの型システム間にインピーダンス ミスマッチは存在しません。

オプティミスティック コンカレンシー

オプティミスティック同時実行制御を使用すると、更新によるデータ喪失および削除によるデータ喪失を防ぐことができます。 競合する同時実行操作は、項目を保持する論理パーティションによってホストされているデータベース エンジンの通常の排他的ロックの対象となります。 論理パーティション内で最新バージョンの項目の更新を試みる 2 つの同時実行操作がある場合、一方の操作は成功し、もう一方の操作は失敗します。 しかし、同じ項目を同時に更新しようとしている 2 つの操作またはそのいずれかによって項目のより古い値が以前に読み取られている場合、競合する操作のいずれか一方または両方によって以前に読み取られたその値が本当に項目の最新値であったかどうかは、データベースでは確認されません。 幸い、この状況は、2 つの操作がデータベース エンジン内のトランザクション境界に入るのを許可する前にオプティミスティック同時実行制御 (OCC) によって検出できます。 他のユーザーが加えた変更が自分のデータによって誤って上書きされないように、OCC によって保護されます。 また、ご自分が加えた変更により他のユーザーのデータが誤って上書きされることも防ぎます。

ETag ヘッダーと HTTP ヘッダーを使用したオプティミスティック同時実行制御の実装

Azure Cosmos DB コンテナーに格納されている項目はすべて、システム定義の _etag プロパティを備えています。 _etag の値は、項目が更新されるたびに、サーバーによって自動的に作成および更新されます。 _etag をクライアントによって指定される if-match 要求ヘッダーと共に使用して、サーバーで項目を条件付きで更新できるどうかを判別できます。 if-match ヘッダーの値がサーバーでの _etag の値と一致すると、項目が更新されます。 if-match 要求ヘッダーの値が最新ではなくなると、操作はサーバーによって拒否され、"HTTP 412 前提条件エラー" という応答メッセージが返されます。 その後、クライアントでは項目の再フェッチを行って、サーバー上の項目の現在のバージョンを取得したり、サーバーにある項目のバージョンを、その項目に対する独自の _etag 値でオーバーライドしたりできます。 また、_etagif-none-match ヘッダーと共に使用して、リソースの再フェッチが必要かどうかを判断できます。

項目の _etag 値は、項目が更新されるたびに変更されます。 項目の置換操作の場合、if-match が要求オプションの一部として明示されている必要があります。 例については、GitHub 内のサンプル コードを参照してください。 ストアド プロシージャによって影響を受けたすべての書き込み項目について、_etag 値が暗黙的にチェックされます。 競合が検出されると、ストアド プロシージャによってトランザクションがロールバックされ、例外がスローされます。 この方法では、ストアド プロシージャ内のすべての書き込みまたは書き込みなしがアトミックに適用されます。 これは、更新を再び適用し、元のクライアント要求を再試行することをアプリケーションに求めるシグナルです。

オプティミスティック同時実行制御とグローバル分散

項目の同時更新は、Azure Cosmos DB の通信プロトコル レイヤーによって OCC の管理下に置かれます。 単一リージョン書き込みで構成された Azure Cosmos DB アカウントの場合、Azure Cosmos DB では、ご自分で更新 (または削除) している項目のクライアント側のバージョンと Azure Cosmos DB コンテナー内のその項目のバージョンが確実に同じになります。 これにより、ご自分の書き込みが他のユーザーの書き込みによって誤って上書きされたり、その逆のケースが生じたりしないように、確実に保護されます。 マルチ ユーザー環境では、ご自分が間違ったバージョンの項目を誤って削除または更新しないように、オプティミスティック同時実行制御によって保護されます。 したがって、項目が "更新によるデータ喪失" または "削除によるデータ喪失" という悩ましい問題から保護されます。

複数リージョン書き込みで構成された Azure Cosmos DB アカウントでは、データの _etag がローカル リージョン内のデータのものと一致する場合、データをセカンダリ リージョンに個別にコミットできます。 新しいデータがセカンダリ リージョンにローカルにコミットされると、新しいデータはハブまたはプライマリのリージョン内でマージされます。 競合解決ポリシーによって新しいデータがハブ リージョンにマージされると、このデータは新しい _etag でグローバルにレプリケートされます。 競合解決ポリシーによって新しいデータが拒否された場合、セカンダリ リージョンは元のデータおよび _etag にロールバックされます。

次のステップ

次の記事で、データベース トランザクションとオプティミスティック同時実行制御の詳細を説明します。