先日、「Azure Storageに保存したブロブをバックアップ専用ストレージアカウントにコピーする」プログラムを書いたが、素直に実装したため激しく遅かった。並列化すらしていないから当然か。
tnakamura.hatenablog.com
そこで、『Azure Storage Data Movement Library for .Net』というライブラリを使って高速化を試みた。
Microsoft 公式の AzCopy もこいつを使っているらしい。
github.com
ディレクトリをコピーするメソッドが提供されていたので、再起呼び出しが不要になってコードがすっきりした。
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;
}
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)
{
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)
{
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)
{
if (container.Properties.LastModified < aWeekAgo)
{
await container.DeleteIfExistsAsync();
}
}
}
}
}
}
これでも、60万ファイルのコピーで5時間弱かかった。さすがにこれ以上の高速化は難しいか。
フルバックアップは週1回くらいにしておき毎日増分バックアップを取る、
といった別アプローチを検討したほうがよさそうだ。