先日、「Azure Storageに保存したブロブをバックアップ専用ストレージアカウントにコピーする」プログラムを書いたが、素直に実装したため激しく遅かった。並列化すらしていないから当然か。
そこで、『Azure Storage Data Movement Library for .Net』というライブラリを使って高速化を試みた。 Microsoft 公式の AzCopy もこいつを使っているらしい。
ディレクトリをコピーするメソッドが提供されていたので、再起呼び出しが不要になってコードがすっきりした。
using Microsoft.WindowsAzure.Storage; using Microsoft.WindowsAzure.Storage.Blob; using Microsoft.WindowsAzure.Storage.DataMovement; using System; using System.Threading.Tasks; namespace AzureStorageBackup { class Program { static void Main(string[] args) { if (args.Length != 3) { Console.WriteLine("AzureStorageBackup <srcConnectionString> <destConnectionString> <containerName>"); return; } MainAsync(args).GetAwaiter().GetResult(); } static async Task MainAsync(string[] args) { var startedAt = DateTimeOffset.UtcNow; var backuper = new Backuper(args[0], args[1], args[2]); await backuper.BackupAsync(); await backuper.EraseAsync(); var timeSpan = DateTimeOffset.UtcNow - startedAt; Console.WriteLine($"TimeSpan = {timeSpan}"); } } class Backuper { CloudStorageAccount SrcAccount { get; } CloudStorageAccount DestAccount { get; } CloudBlobClient SrcBlobClient { get; } CloudBlobClient DestBlobClient { get; } string ContainerName { get; } public Backuper(string srcConnectionString, string destConnectionString, string containerName) { SrcAccount = CloudStorageAccount.Parse(srcConnectionString); DestAccount = CloudStorageAccount.Parse(destConnectionString); ContainerName = containerName; SrcBlobClient = SrcAccount.CreateCloudBlobClient(); DestBlobClient = DestAccount.CreateCloudBlobClient(); } public async Task BackupAsync() { // コピー元のコンテナが存在しなければ何もせずに即終了 var srcContainer = SrcBlobClient.GetContainerReference(ContainerName); if (await srcContainer.ExistsAsync() == false) { return; } // コピー先のストレージアカウントがコピー元のコンテナにアクセスできるようにするために、 // SAS を発行する var blobToken = srcContainer.GetSharedAccessSignature(new SharedAccessBlobPolicy() { Permissions = SharedAccessBlobPermissions.Read, SharedAccessStartTime = DateTimeOffset.UtcNow, SharedAccessExpiryTime = DateTimeOffset.UtcNow.AddDays(1), }); // コピー先のコンテナを作成 var destContainer = DestBlobClient.GetContainerReference(ContainerName + DateTime.UtcNow.ToString("yyyyMMddhhmmss")); await destContainer.CreateIfNotExistsAsync(); // すべてのブロブをコピーする await BackupContainerAsync(srcContainer, destContainer, blobToken); } async Task BackupContainerAsync(CloudBlobContainer srcContainer, CloudBlobContainer destContainer, string blobToken) { var continuationToken = new BlobContinuationToken(); while (continuationToken != null) { // ページングされているので、続きを取得するには // ContinuationToken を指定する必要がある var response = await srcContainer.ListBlobsSegmentedAsync(continuationToken); continuationToken = response.ContinuationToken; foreach (var item in response.Results) { if (item is CloudPageBlob pageBlob) { var destBlob = destContainer.GetPageBlobReference(pageBlob.Name); await destBlob.StartCopyAsync(new Uri(item.Uri.AbsoluteUri + blobToken)); } else if (item is CloudBlockBlob blockBlob) { var destBlob = destContainer.GetBlockBlobReference(blockBlob.Name); await destBlob.StartCopyAsync(new Uri(item.Uri.AbsoluteUri + blobToken)); } else if (item is CloudBlobDirectory directory) { // Data Movement Library を使ってディレクトリごとコピー var destDirectory = destContainer.GetDirectoryReference(directory.Prefix); var status = await TransferManager.CopyDirectoryAsync( sourceBlobDir: directory, destBlobDir: destDirectory, isServiceCopy: true, options: new CopyDirectoryOptions() { Recursive = true, }, context: new DirectoryTransferContext()); } Console.WriteLine($"COPY {item.Uri}"); } } } public async Task EraseAsync() { var aWeekAgo = DateTimeOffset.UtcNow.AddDays(-7); var continuationToken = new BlobContinuationToken(); while (continuationToken != null) { var response = await DestBlobClient.ListContainersSegmentedAsync( $"{ContainerName}", continuationToken); continuationToken = response.ContinuationToken; foreach (var container in response.Results) { // 7日前までのバックアップを保持する // 7日より前は削除 if (container.Properties.LastModified < aWeekAgo) { await container.DeleteIfExistsAsync(); } } } } } }
これでも、60万ファイルのコピーで5時間弱かかった。さすがにこれ以上の高速化は難しいか。 フルバックアップは週1回くらいにしておき毎日増分バックアップを取る、 といった別アプローチを検討したほうがよさそうだ。