2019年10月10日
久しぶりに読み直すと日本語がおかしい、断言するのはおかしいと思う部分があったので修正します。
何故、チェックサム確認処理が必要か
とあるシステムでMicrosoft Azure Storage Libraries for .NETを使ってAzure Blobストレージからファイルをダウンロードしていたのですが、
- クライアントライブラリの処理としては正常に完了しているのに、ファイルが破損している
という事象が発生しました。
傾向を調べてみるとファイルサイズが大きくなると発生頻度が上がるようで、特に100GBytesを超えるファイルでは50%を超える頻度で問題が発生していました。
色々な調査の結果、最終的に原因はライブラリのバグで並列ダウンロード中に通信エラーが起きるとデータ破損が発生するというものでした。また、ライブラリのアップデートにより、問題は収束しました。1
この件ではダウンロード後にファイル変換処理があったおかげで問題が顕在化しましたが、例えばダウンロード後にどこか別のストレージに保存して終了という場合はすぐに問題が発覚せず、後々に大きな問題になってしまう危険性があります。処理コストとのバランスは必要ですが、ダウンロードしたファイルの復旧が難しい場合、ダウンロードされたファイルはアップロード前のファイルと同一のものなのか、チェックする処理は必要と思います。
Azure Blobは自動でMD5を計算してくれるが、クライアント側による暗号化をしている場合はうまくいかない
Azure BlobストレージはMD5のハッシュ値を自動で計算してくれますので、この機能を使えばアップロード前のファイルとダウンロード後のファイルが一致するかどうかが簡単にチェックできます。
上記の記事ではリクエストオプションで指定が必要とありますが、現在ではデフォルトでこの機能がオンになっているようです。
ただし、このAzure BlobのContentMD5はあくまでAzure Blobにアップロードされたファイルに対して計算されます。クライアント側による暗号化2を利用している場合はうまく検出できません。
解決策:独自にハッシュ値を計算してメタデータとして設定する
Azure Blobストレージの機能では足りないため、独自にハッシュ値を計算する必要があります。
Azure Blobストレージでは独自のメタデータを設定することができますので、事前に計算した値を保存しておくことでダウンロード時にチャックすることができます。3
public static async Task AddMetadataAsync(CloudBlockBlob blob,string checkSum)
{
blob.Metadata.Add("RawContentMD5", checkSum);
await container.SetMetadataAsync();
}
public static async Task<string> GetMetadataAsync(CloudBlockBlob blob)
{
await blob.FetchAttributesAsync();
string checkSum = null;
if (block.Metadata.TryGetValue("RawContentMD5", out checkSum))
{
return checkSum;
}
else
{
// 例外処理
}
}
ただし、この方法だとファイルの暗号化はライブラリ内で行われるため、正しくアップロードされたかはダウンロードしてみないと確認できません。
まとめ
もしもの場合を考慮をし始めると、事前に計算したチェックサムは正しいのか?など、この手の問題は際限ありません。
Microsoft Azure Storage Librariesに関しては、私が遭遇したバグの後も2018年10月14日時点で最新版であるv9.3.2でもダウンロード中のファイル破損のバグ修正が入っています。ライブラリに何らかのバグのリスクを考えると、確実にデータを保存したいファイルに対してはチェックは入れておくべきと思います。