DocumentDB: Create 操作の可否の判断方法

このポストは、10 月 6 日に投稿された DocumentDB: To create, or not to create, that is the question の翻訳です。

 

JSON ドキュメントを DocumentDB に永続化する場合、Create と Replace のどちらの操作を実行するか判断に悩む場合があります。

レコードが既に存在する場合、Create 操作を実行するとエラーが返されます。同様に、レコードが存在しない場合に Replace 操作を実行するとエラーが返されます。このような場合、まず ID による Read または Query のいずれかを実行してドキュメントの有無を確認し、Create と Replace のどちらを実行するかを決定します。

これを実装した場合の一般的な例を次に示します。

 using (DocumentClient client = new DocumentClient(new Uri(endpoint), authKey))
{
    var docSpec = new { id = "document id", foo = "bar" };

    var docExists = client.CreateDocumentQuery(UriFactory.CreateCollectionUri(databaseId, collectionId))
                        .Where(doc => doc.Id == "document id")
                        .Select(doc => doc.Id)
                        .AsEnumerable()
                        .Any();

    // ドキュメントが存在する場合は docSpec と置換
    if (docExists)
    {
        Uri docUri = UriFactory.CreateDocumentUri(databaseId, collectionId, "document id");
        await client.ReplaceDocumentAsync(docUri, docSpec);
    }
    // ドキュメントが存在しない場合は docSpec を使用してドキュメントを新規作成
    else
    {
        Uri collUri = UriFactory.CreateCollectionUri(databaseId, collectionId);
        await client.CreateDocumentAsync(collUri, docSpec);
    }
}

 

この方法はほとんどの場合に適用できますが、問題点もあります。1 つは、レコードが存在しているかどうかをデータベースに必ず確認するため、要求単位が余分に必要となることです。

また、クエリによる負荷増大とネットワークのラウンドトリップを無視するとしても、このコードではプロセスが 2 段階で実行されるため競合状態に陥るおそれがあります。仮に、2 つのプロセスがまったく同時にまったく同じ "SELECT * FROM c WHERE id = <ドキュメント ID>" というクエリを実行し、両方のプロセスで「ドキュメントが存在しない」という結果を受け取ったとします。この場合、両方のプロセスで Create 操作が実行され、競合に勝ったほうのプロセスではレコードが挿入されてこの操作が正常に処理されますが、もう片方のプロセスでは既にレコードが挿入されているため処理は失敗に終わります。

今回、DocumentDB でバックエンドでのアトミックな Upsert 操作がサポートされることが発表されました。

この Upsert のサポートにより、前述の 2 つの問題が解決されます。Upsert を使用すると、どちらの操作を実行するかを決定する前にドキュメントの有無を確認する必要がなく、データベースがこの決定を行います。この処理はアトミックに行われるため余分な要求単位が発生せず、また競合状態に陥るおそれもなくなります。

Upsert では、ドキュメントの ID プロパティを使用して、ドキュメントを新規作成するか既存のドキュメントを置換するかを判断します。ここで、DocumentDB ではドキュメントを一部のみ更新することは現時点では不可能で、必ずドキュメント全体が置換されることに注意する必要があります。このため、ドキュメントが既に存在している場合 (該当する ID が存在する場合)、Upsert 要求の内容によりドキュメント全体が置換されます。

Upsert を使用した場合、上記のコードは次のようになります。

 await client.UpsertDocumentAsync(UriFactory.CreateCollectionUri(databaseId, collectionId), docSpec);

実際の REST 要求を見ると、上記のコードはドキュメント リソースの POST に変換され、新しいカスタム HTTP ヘッダーの x-ms-documentdb-is-upsert が True に設定されています。DocumentDB からの応答は、Create と Replace のどちらの処理が実行されたかを示します。


 
Create が実行された場合は HTTP 応答は StatusCode 201 となり、Replace が実行された場合は StatusCode 200 が返されます。

Upsert 要求は上記のようにクライアント側の要求として実行するだけではなく、ストアド プロシージャやトリガーを構築する際にはサーバー側でも JavaScript SDK から実行できます。

Upsert 機能を使用するには、使用するプラットフォームに応じて .NET (英語)Node.js (英語)Python (英語)Java (英語) の中から最新の SDK をダウンロードする必要があります。

この新機能について、いつもと同様に皆様からのフィードバックをお待ちしております。ご意見がございましたら、ページ下部のコメント欄へお気軽にご投稿ください。また、不明な点やサポートが必要な場合は、MSDN の開発者フォーラム (英語) または StackOverflow (英語) をご利用ください。

DocumentDB の最新情報をいち早く入手するには、Twitter アカウント (@DocumentDB) をフォローしてください。

ではまた!