Freigeben über


Microsoft Azure Storage での同時実行制御の管理

このポストは、9 月 8 日に投稿された Managing Concurrency in Microsoft Azure Storage の翻訳です。

最新のインターネットを基盤とするアプリケーションでは、複数のユーザーが同時にデータを表示し、更新することが一般的です。このような場合、アプリケーション開発者は予測可能なエクスペリエンスをユーザーに提供する方法を注意深く検討する必要があり、特に複数のユーザーが同じデータを同時に更新できる場合はこれが重要となります。データの同時実行を処理する戦略としては、次の 3 つの手法が広く使用されます。

1. オプティミスティック同時実行制御 – アプリケーションでデータを更新する場合、更新処理の一部として、データがそのアプリケーションに最後に読み込まれた後に更新されていないかどうかを確認します。たとえば、wiki のページを閲覧している 2 人のユーザーが同じページを更新しようとしている場合、wiki のプラットフォームは、2 番目の更新が 1 番目の更新を上書きせず、また両方のユーザーがそれぞれの更新処理の成否を把握できるようにする必要があります。この戦略は、Web アプリケーションで最も広く使用されています。

2. ペシミスティック同時実行制御 – 更新を実行しようとするアプリケーションがオブジェクトをロックし、ロックが解除されるまで他のユーザーがデータを更新できないようにします。たとえば、マスターとスレーブの間でデータを複製する場合はマスターのみが更新を実行するため、通常は、データを一定期間排他的にロックして、該当するデータを他から更新できないようにします。

3. 最終書き込み者優先 – アプリケーションが最初にデータを読み込んだ後に他のアプリケーションが該当するデータを更新したかどうかを検証せず、すべての更新操作を実行します。この戦略 (つまり、確実な戦略を実装しない) は、通常、データがパーティション分割され、複数のユーザーが同時に同じデータにアクセスする可能性が十分に低い場合に使用されます。また、有効期限が短いデータ ストリームの処理にも有効です。

この記事では、Azure Storage プラットフォームで上記の 3 種類の同時実行制御戦略について業界最高クラスのサポートが提供されていることにより、開発がいかに簡素化されるかについて説明します。

Azure Storage – クラウド環境の開発を簡素化

Azure Storage サービスでは上記の 3 種類の戦略をすべてサポートしていますが、特にオプティミスティック同時実行制御とペシミスティック同時実行制御を完全にサポートしているという点が特徴的です。強力な整合性モデルを備えているため、Storage サービスでデータの挿入や更新の処理がコミットされた後にサービスがそのデータにアクセスした場合でも、確実に最新のデータが提供されます。結果整合性モデルを使用するストレージ プラットフォームでは、あるユーザーが書き込みを行ってから他のユーザーがそのデータを表示できるようになるまでにタイム ラグが発生します。エンド ユーザーがこの不整合の影響を受けないようにするために、クライアント アプリケーションの開発は複雑になります。

また、開発者は、適切な同時実行制御戦略を選択する他にも、同一のオブジェクトを複数のトランザクションが変更しようとする場合などに、ストレージ プラットフォームが変更を分離する方法について把握しておく必要があります。Azure Storage サービスではスナップショット分離の手法を使用していて、同一パーティション内で読み取り処理と書き込み処理が同時に発生する場合にも対応しています。他の分離レベルと異なり、スナップショット分離では、更新処理中でもすべての読み込み処理に対してデータのスナップショットを提供することにより整合性を確保します。このとき、更新トランザクションの処理が実行されていても、最後にコミットされた値が返されます。

BLOB サービスで同時実行制御を管理する

BLOB サービスでの BLOB およびコンテナーへのアクセスを管理する場合、オプティミスティック同時実行制御とペシミスティック同時実行制御のいずれかを使用できます。明示的に戦略を指定しない場合は、既定の最終書き込み者優先が適用されます。

BLOB およびコンテナーでオプティミスティック同時実行制御を使用する

Storage サービスでは、格納されているすべてのオブジェクトに識別子が割り当てられます。オブジェクトで更新処理が実行されると、そのたびにこの識別子は更新されます。識別子は、HTTP プロトコルで定義されている ETag (エントリ タグ) ヘッダーを使用して、HTTP GET 応答の一部としてクライアントに返されます。該当するオブジェクトをユーザーが更新しようとすると、条件ヘッダーが付属している元の ETag が送信され、特定の条件を満たしているときにのみ更新が行われます。この場合の条件は、更新要求で指定された ETag の値が Storage サービスに格納されている値と同一であることを、「If-Match」ヘッダーで Storage サービスに確認することです。

このプロセスの概要は次のとおりです。

1. Storage サービスから BLOB を取得します。この応答には HTTP ETag ヘッダーの値が含まれていて、Storage サービスに格納されているオブジェクトの現在のバージョンを示しています。

2. 手順 1 でサービスに要求を送信したときに受け取った If-Match 条件ヘッダーの ETag の値が、BLOB の更新要求と同時に送信されます。

3. 手順 2 で要求と同時に送信された ETag の値が、BLOB の現在の ETag の値と同一であるかどうかをサービスが確認します。

4. BLOB の現在の ETag の値が、要求と同時に送信された If-Match 条件ヘッダーの ETag の値と異なる場合、サービスはクライアントに 412 エラーを返します。この場合、クライアントがこの BLOB を取得した後に、他のプロセスがこの BLOB を更新したことを示しています。

5. BLOB の現在の ETag の値が、要求と同時に送信された If-Match 条件ヘッダーの ETag の値と等しい場合、サービスは要求された処理を実行します。それと同時に、この BLOB のバージョンが変更されたことを示すために、現在の ETag の値を更新します。

次に示す C# のスニペット (Client Storage Library 4.2.0 を使用) は、事前に取得または挿入された BLOB のプロパティからアクセスされた ETag の値に基づいて、If-Match AccessCondition を構築する方法の簡易な例です。その後、BLOB を更新する際に AccessCondition オブジェクトが使用されます。AccessCondition オブジェクトが If-Match ヘッダーを要求に追加します。他のプロセスが BLOB を更新した場合、該当する BLOB のサービスが HTTP 412 (Precondition Failed) のステータス メッセージを返します。完全なサンプルはこちらのページ (英語) でダウンロードできます。

 // ETag を、BLOB で前回 UploadText 操作を実行した時の応答から取得
string orignalETag = blockBlob.Properties.ETag;
// このコードはサードパーティにより更新された場合を想定
string helloText = "Blob updated by a third party.";
// ETag が存在しない場合、元の BLOB が上書きされる (この場合、新しい ETag が生成される)
blockBlob.UploadText(helloText);
Console.WriteLine("Blob updated. Updated ETag = {0}", blockBlob.Properties.ETag);
// BLOB 作成時に指定された元の ETag を使用して BLOB を更新
try
{
     Console.WriteLine("Trying to update blob using orignal etag to generate if-match access condition");
     blockBlob.UploadText(helloText,accessCondition:
     AccessCondition.GenerateIfMatchCondition(orignalETag));
}
catch (StorageException ex)
{
     if (ex.RequestInformation.HttpStatusCode == (int)HttpStatusCode.PreconditionFailed)
     {
          Console.WriteLine("Precondition failure as expected. Blob's orignal etag no longer matches");
     }
}

Storage サービスでは、他にも If-Modified-SinceIf-Unmodified-SinceIf-None-Match の各条件ヘッダー、およびその組み合わせをサポートしています。詳細については、MSDN の「BLOB サービス操作の条件ヘッダーの指定」のページをご覧ください。

次の表は、要求に含まれている If-Match などの条件ヘッダーを受け取り、ETag の値を返すコンテナー操作をまとめたものです。

操作

コンテナーの ETag 値を返す

条件ヘッダーを受け取る

Create Container

×

Get Container Properties

×

Get Container Metadata

×

Set Container Metadata

Get Container ACL

×

Set Container ACL

○ (*)

Delete Container

×

Lease Container

List Blobs

×

×

(*) SetContainerACL で定義されたアクセス許可はキャッシュされます。このアクセス許可の更新の伝達には 30 秒間かかり、その間は更新の整合性は保証されません。

次の表は、要求に含まれている If-Match などの条件ヘッダーを受け取り、ETag の値を返す BLOB 操作をまとめたものです。

操作

ETag 値を返す

条件ヘッダーを受け取る

Put Blob

Get Blob

Get Blob Properties

Set Blob Properties

Get Blob Metadata

Set Blob Metadata

Lease Blob (*)

Snapshot Blob

Copy Blob

○ (コピー元とコピー先の BLOB)

Abort Copy Blob

×

×

Delete Blob

×

Put Block

×

×

Put Block List

Get Block List

×

Put Page

Get Page Ranges

(*) Lease Blob では、BLOB の ETag は変更されません。

BLOB でのペシミスティック同時実行制御

BLOB をロックして排他的に使用する場合は、リースを取得します。リースを取得すると、必要に応じてリース期間を 15 ~ 60 秒の間、または無制限に設定できます。この期間内は BLOB が排他的にロックされます。リース期間が有限の場合は、これを延長することができます。また、操作が完了したリースは随時解放できます。期限が切れた有限のリースは、BLOB サービスが自動的に解放します。

リースでは、排他的書き込み/共有読み取り、排他的書き込み/排他的読み取り、共有書き込み/排他的読み取りのそれぞれの同期戦略がサポートされています。リースが存在する場合、Storage サービスは排他的書き込み (put、set、delete の各操作) を強制実行しますが、読み込み操作の排他性を確保するために、開発者はすべてのクライアント アプリケーションがリース ID を使用し、また有効なリース ID は同時に 1 つのクライアントのみが保持するようにシステムを構築する必要があります。読み込み操作にリース ID を使用しない場合、共有読み取りになります。

次の C# のサンプル スニペットでは、特定の BLOB で 30 秒間の排他的リースを取得し、BLOB の内容を更新し、その後リースを解放します。既に BLOB が有効なリースを保持している場合、新しいリースを取得しようとすると、BLOB サービスは「HTTP 409 (Conflict)」ステータス コードを返します。また、このスニペットでは、Storage サービスに BLOB の更新を要求するときに、AccessCondition オブジェクトを使用してリースの情報をカプセル化しています。完全なサンプルはこちらのページ (英語) でダウンロードできます。

 // 15 秒間のリースを取得
string lease = blockBlob.AcquireLease(TimeSpan.FromSeconds(15), null);
Console.WriteLine("Blob lease acquired. Lease = {0}", lease);

// リースを使用して BLOB を更新 (この操作は正常に完了します)
const string helloText = "Blob updated";
var accessCondition = AccessCondition.GenerateLeaseCondition(lease);
blockBlob.UploadText(helloText, accessCondition: accessCondition);
Console.WriteLine("Blob updated using an exclusive lease");

// サードパーティがリースを使用せずに BLOB を更新する場合を想定
try
{
// 有効なリースがないため、この操作は失敗します
     Console.WriteLine("Trying to update blob without valid lease");
     blockBlob.UploadText("Update without lease, will fail");
}
catch (StorageException ex)
{
     if (ex.RequestInformation.HttpStatusCode == (int)HttpStatusCode.PreconditionFailed)
          Console.WriteLine("Precondition failure as expected. Blob's lease does not match");
     else
          throw;
}

リース ID を渡さずにリースを取得した BLOB に対して書き込み操作を要求すると、412 エラーが返され、要求は失敗します。UploadText メソッドを呼び出す前にリース期間が終了したにもかかわらずリース ID を渡そうとすると、その要求も 412 エラーが返されて失敗します。リースの有効期限とリース ID の管理の詳細については、REST API の「Lease Blob」のドキュメントを参照してください。

次の BLOB 操作では、ペシミスティック同時実行制御の管理にリースを使用できます。

· Put Blob

· Get Blob

· Get Blob Properties

· Set Blob Properties

· Get Blob Metadata

· Set Blob Metadata

· Delete Blob

· Put Block

· Put Block List

· Get Block List

· Put Page

· Get Page Ranges

· Snapshot Blob – リースが存在する場合にオプションでリース ID を使用可能

· Copy Blob – コピー先の BLOB でリースが存在する場合はリース ID が必要

· Abort Copy Blob – コピー先の BLOB で無制限のリースが存在する場合はリース ID が必要

· Lease Blob

コンテナーでのペシミスティック同時実行制御

コンテナーのリースでは、BLOB と同じく、排他的書き込み/共有読み取り、排他的書き込み/排他的読み取り、共有書き込み/排他的読み取りのそれぞれの同期戦略がサポートされています。ただし、BLOB とは異なり、削除操作では Storage サービスが強制的に排他的操作を適用します。有効なリースを使用してコンテナーを削除する場合、クライアントは削除要求時に有効なリース ID を保持している必要があります。他のコンテナー操作ではすべてリース ID は不要で、リースが取得されたコンテナーで正常に行われます。この場合、各操作は共有操作になります。更新操作 (put または set) または読み込み操作を排他的に行う必要がある場合、開発者は、すべてのクライアントでリース ID を使用し、また有効なリース ID を持つクライアントは同時に 1 つのみになるようにする必要があります。

次のコンテナー操作では、ペシミスティック同時実行制御の管理にリースを使用できます。

· Delete Container

· Get Container Properties

· Get Container Metadata

· Set Container Metadata

· Get Container ACL

· Set Container ACL

· Lease Container

詳細については、次のページをご覧ください。

- BLOB サービス操作の条件ヘッダーの指定

- Lease Container

- Lease Blob

Table サービスで同時実行制御を管理する

Table サービスでは、作業にエンティティを使用している場合、オプティミスティック同時実行制御が既定の動作として適用されます。一方、BLOB サービスの場合は、明示的にオプティミスティック同時実行制御を確認するように選択する必要があります。この他に、Table サービスではエンティティの同時実行制御しか管理できませんが、BLOB サービスではコンテナーと BLOB の両方の同時実行制御を管理できるという違いがあります。

オプティミスティック同時実行制御を使用して、Table ストレージ サービスからエンティティを取得した後に他のプロセスがそれを更新していないかどうかを確認する場合、Table サービスがエンティティを返すときに受け取った ETag の値を利用できます。このプロセスの概要は次のとおりです。

1. Table ストレージ サービスからエンティティを取得します。この応答には ETag の値が含まれていて、Table ストレージ サービスでこのエンティティに関連付けられている現在の識別子を識別する際に使用されます。

2. サービスに要求を送信したときに手順 1 で受け取った If-Match 必須ヘッダーの ETag の値が、エンティティを更新するときに同時に送信されます。

3. 手順 2 で要求と同時に送信された ETag の値が、エンティティの現在の ETag の値と同一であるかどうかをサービスが確認します。

4. エンティティの現在の ETag の値が、要求と同時に送信された If-Match 必須ヘッダーの ETag の値と異なる場合、サービスはクライアントに 412 エラーを返します。これは、クライアントがこのエンティティを取得した後に、他のプロセスが該当するエンティティを更新したことを示しています。

5. エンティティの現在の ETag の値が、要求と同時に送信された If-Match 必須ヘッダーの ETag の値と等しい場合、または If-Match ヘッダーにワイルドカード文字 (*) が含まれている場合、サービスは要求された処理を実行します。それと同時に、該当するエンティティが更新されたことを示すために、現在の ETag の値を更新します。

BLOB サービスと異なり、Table サービスではクライアントが更新を要求する際に必ず If-Match ヘッダーを含める必要があります。ただし、クライアントの要求の If-Match ヘッダーにワイルドカード文字 (*) が設定されている場合は、条件なしで更新を強制適用 (最終書き込み者優先戦略) し、同時実行制御の確認を省略することができます。

次の C# のスニペットは、以前に電子メール アドレスが更新されたときに作成または取得されたユーザー エンティティを示しています。最初の挿入操作または取得操作で、ユーザー オブジェクトに ETag 値が格納されています。このサンプルでは同じオブジェクトのインスタンスを使用するため、置換操作を実行するときに自動的に ETag の値が Table サービスに戻され、サービスが同時実行制御の違反を確認できるようになっています。他のプロセスが Table ストレージでエンティティを更新した場合、該当するエンティティのサービスが HTTP 412 (Precondition Failed) のステータス メッセージを返します。完全なサンプルはこちらのページ (英語) でダウンロードできます。

 try
{
     customer.Email = "updatedEmail@contoso.org";
     TableOperation replaceCustomer = TableOperation.Replace(customer);
     customerTable.Execute(replaceCustomer);
     Console.WriteLine("Replace operation succeeded.");
}
catch (StorageException ex)
{
     if (ex.RequestInformation.HttpStatusCode == 412)
          Console.WriteLine("Optimistic concurrency violation – entity has changed since it was retrieved.");
     else
          throw; 
}

同時実行制御を明示的に無効にするには、置換操作を実行する前に、employee オブジェクトの ETag プロパティを “*” に設定します。

customer.ETag = “*”;

次の表は、Table エンティティで ETag の値がどのように使用されるかをまとめたものです。

操作

ETag 値を返す

If-Match 要求ヘッダーが必須

Query Entities

×

Insert Entity

×

Update Entity

Merge Entity

Delete Entity

×

Insert Entity/Replace Entity

×

Insert Entity/Merge Entity

×

Insert Entity/Replace Entity および Insert Entity/Merge Entity の各操作では、ETag の値が Table サービスに送信されないため、同時実行制御は行われません。

一般的に、スケーラブルなアプリケーションを開発するときには、Table に対してオプティミスティック同時実行制御を行う必要があります。ペシミスティック同時実行制御によるロックが必要な場合、アクセス時に各 Table が指定した BLOB に関連付けられ、Table で操作が実行される前に BLOB に対してリースが取得されます。この手法では、Table で操作が行われる前にアプリケーションがすべてのデータのアクセス経路でリースを確実に取得する必要があります。また、リース期間は最低 15 秒であるため、スケーラビリティについて考慮する必要がある場合には注意が必要です。

詳細については、次のページをご覧ください。

- エンティティに対する操作

Queue サービスで同時実行制御を管理する

Queue サービスでの同時実行制御について注意が必要なケースとして、複数のクライアントが同一の Queue からメッセージを取得する場合があります。Queue からメッセージを取得するとき、応答にはメッセージと PopReceipt の値が含まれます。この値はメッセージを削除するときに必要です。メッセージは Queue から自動的に削除されることはありませんが、取得された後、visibilitytimeout パラメーターで指定された期間は他のクライアントで表示されなくなります。クライアントは、メッセージを取得した後、応答の TimeNextVisible 要素で指定された時間内に処理を完了し、このメッセージを削除します。この時間は visibilitytimeout パラメーターにより決定されます。visibilitytimeout の値はメッセージが取得されるタイミングで追加され、この値を基に TimeNextVisible の値が決定されます。

Queue サービスでは、オプティミスティックおよびペシミスティックのいずれの同時実行制御もサポートされていません。このため、Queue からメッセージを取得して処理するクライアントは、元の条件が変わらないようにメッセージを処理する必要があります。SetQueueServiceProperties、SetQueueMetaData、SetQueueACL、UpdateMessage などの更新操作では、最終書き込み者優先戦略が適用されます。

詳細については、次のページをご覧ください。

- Queue サービスの REST API

- Get Messages

ファイル サービスで同時実行制御を管理する

ファイル サービスでは、エンドポイントのアクセスに SMB と REST の 2 種類のプロトコルが使用されます。REST サービスではオプティミスティック同時実行制御とペシミスティック同時実行制御のいずれもサポートされておらず、すべての更新操作に最終書き込み者優先戦略が適用されます。ファイル共有をマウントする SMB クライアントではファイル システムのロック機構を使用できるため、ペシミスティック同時実行制御によるロックも含め、共有ファイルへのアクセスの管理が可能です。SMB クライアントがファイルを開くときに、ファイルのアクセス権と共有モードの両方が指定されます。ファイルのアクセス権が「Write」または「Read/Write」に設定され、同時にファイル共有モードが「None」に設定された場合、そのファイルは閉じられるまで SMB クライアントがロックします。SMB クライアントがロックしているファイルで REST 操作を実行しようとすると、REST サービスはステータス コード 409 (Conflict) および「SharingViolation」のエラー コードを返します。

SMB クライアントがファイルを開いて削除する場合、そのファイルで開かれているハンドルが他のすべての SMB クライアントで閉じられるまで、「Delete Pending」とマークされます。「Delete Pending」状態のファイルで REST 操作を実行しようとすると、REST サービスはステータス コード 409 (Conflict) および SMBDeletePending のエラー コードを返します。ここで、SMB クライアントはファイルが閉じられなくても「Delete Pending」フラグを削除することができるため、ステータス コード 404 (Not Found) が返されることはありません。つまり、ステータス コード 404 (Not Found) が返されるのは、ファイルが削除済みである場合のみです。SMB で「Delete Pending」状態に設定されているファイルは、List Files の結果に含まれないので注意が必要です。また、REST Delete File および REST Delete Directory の各操作はアトミックにコミットされるため、「Delete Pending」状態に設定されることはありません。

詳細については、次のページをご覧ください。

- ファイルのロックの管理 (英語)

まとめと次のステップ

Microsoft Azure Storage サービスは、ほとんどのオンライン アプリケーションの複雑なニーズに対応しており、同時実行制御やデータの整合性など、設計時に通常想定される主要な項目を再考したり、妥協したりする必要はありません。

このブログで使用したサンプルの完全版は、次のページでダウンロードできます。

- Azure Storage での同時実行制御の管理 - サンプル アプリケーション (英語)

Azure Storage の詳細については、次のページをご覧ください。

- Microsoft Azure Storage のホーム ページ

- Azure Storage の概要

- Storage の入門ガイド: BLOB (英語)Table (英語)Queue (英語)

- Storage のアーキテクチャ – Azure Storage: 高い整合性を持つ高可用性クラウド ストレージ サービス (英語)