0
0

More than 3 years have passed since last update.

Azure Blob に対して、同じBlobを複数のワーカーから同時更新する

Posted at

タイトル名のようなことが簡単に出来るのでは?と思いトライしてみたが、残念ながら、次のようなエラーをもらった。プログラム的には、Parallel.ForEach を使って、32 ワーカー(仮)から、同じBlobを同時更新するようなコードを書いた。

自分のイメージでは、その32 のワーカーは同時刻には同じ内容を返すはずなので、32のワーカーのどれが勝っても問題なく、楽観ロックすら必要ないので、楽勝と思っていたが違った。

Microsoft.Azure.Storage.StorageException: 'The MD5 value specified in the request did not match with the MD5 value 

This exception was originally thrown at this call stack:
    Microsoft.Azure.Storage.Core.Executor.Executor.ExecuteAsync<T>(Microsoft.Azure.Storage.Core.Executor.RESTCommand<T>, Microsoft.Azure.Storage.RetryPolicies.IRetryPolicy, Microsoft.Azure.Storage.OperationContext, System.Threading.CancellationToken) in Executor.cs
    System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(System.Threading.Tasks.Task) in TaskAwaiter.cs
    System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(System.Threading.Tasks.Task) in TaskAwaiter.cs
    System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd(System.Threading.Tasks.Task) in TaskAwaiter.cs
    Microsoft.Azure.Storage.Blob.CloudBlockBlob.UploadFromStreamAsyncHelper(System.IO.Stream, long?, Microsoft.Azure.Storage.AccessCondition, Microsoft.Azure.Storage.Blob.BlobRequestOptions, Microsoft.Azure.Storage.OperationContext, Microsoft.Azure.Storage.Core.Util.AggregatingProgressIncrementer, System.Threading.CancellationToken) in CloudBlockBlob.cs
    System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(System.Threading.Tasks.Task) in TaskAwaiter.cs
    System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(System.Threading.Tasks.Task) in TaskAwaiter.cs
    Microsoft.Azure.Storage.Blob.CloudBlockBlob.UploadFromByteArrayAsync(byte[], int, int, Microsoft.Azure.Storage.AccessCondition, Microsoft.Azure.Storage.Blob.BlobRequestOptions, Microsoft.Azure.Storage.OperationContext, System.IProgress<Microsoft.Azure.Storage.Core.Util.StorageProgress>, System.Threading.CancellationToken) in CloudBlockBlob.cs
    System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(System.Threading.Tasks.Task) in TaskAwaiter.cs
    System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(System.Threading.Tasks.Task) in TaskAwaiter.cs
    ...
    [Call Stack Truncated]

MD5 のバリューがマッチしない

MD5のバリューがマッチしないと突然言われても「なんのMD5やねん」と思ってしまう。検索してみると、Blobのリファレンスを使いまわしているのが問題らしい。

このMD5は何をしているか?というと、ファイルから、MD5を作成して、チェックサムとして使用している。しばやん先生のブログにも記事があった。ファイルをアップロードすると、たまに壊れるケースがあるので、MD5でチェックサムを作って送る機能があるようす。

エラーのバージョン

先の Exception が出たバージョンは次の通り。cloudBlockBlob は全く同じブロブなので、クラス変数にして、リファレンスを共有していた。

        public async Task UploadScaleDecisionAsync(object obj)
        {
            var json = JsonConvert.SerializeObject(obj);
            await cloudBlockBlob.UploadTextAsync(json);
        }

上記のメソッドを呼び出す部分は、次のように、32並列で、同じ Blob を更新する。

            Parallel.ForEach(Enumerable.Range(1, 32).ToArray(), async (idx) =>
            {
                while (true)
                {
                    await manager.UploadScaleDecisionAsync(new ScaleDecision(){Decision = "AddWorker", Index = idx, TimeStamp = DateTime.UtcNow});
                    await Task.Delay(TimeSpan.FromSeconds(1));
                }
            });

デバッグで MD5 を観察する

コードを次のように修正して、デバッグをしてみる。

        public async Task UploadScaleDecisionAsync(object obj)
        {
            var cloudBlockBlob = cloudBlobContainer.GetBlockBlobReference(Configuration.BlobName);
            var json = JsonConvert.SerializeObject(obj);
            await cloudBlockBlob.UploadTextAsync(json);
        }

MD5 が生成されている。
image.png

ドキュメントを見ると、MD5 のチェックサムはデフォルトで有効なので、あのエラーがでた原因もうなづける。下記に面白そうなオプションが定義できるようになっている。

BlobRequestOptions.DisableContentMD5Validation Propertyというオプションがあり、これがデフォルトで false なので、基本的にMD5のバリデーションをしてくれるのだろう。

おまけだが、リトライポリシーも設定できる。何も設定していなので、こちらの値がデフォルト値なのだろう。ただ、ドキュメントに記述がないので、将来にわたってそうであるかはわからない。

image.png

まとめ

Blobのリファレンスは同時更新するのであれば、シェアしてはいけない。リファレンスを無くすことで、Exception が出ず、期待通り後勝ちの動作をしてくれるようになった。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0