在表存储中的分区之间移植实体

时间:2015-05-22 08:13:05

标签: .net async-await task-parallel-library azure-table-storage tpl-dataflow

我需要将Azure Table Storage中的一个表中的记录的整个分区从Partition1移植到Partition2。成千上万,即使不是数百万。

我知道在Azure表存储中无法将实体从一个分区移植到另一个分区,您需要删除旧分区并插入一个更新的PartitionKey,所以我的任务是做许多记录也一样。

有标准的东西吗?

我提出了以下解决方案(简化):

public async Task Migrate(string oldPartition, string newPartition)
{
    TableContinuationToken token = null;

    List<Task> migrationTasks = new List<Task>();

    do
    {
         TableQuerySegment<MyTableEntity> entries = await GetEntriesSegment(
             oldPartition, 
             token);

         token = entries.ContinuationToken;

         migrationTasks.Add(MigrateEntries(entries, newPartition));
    } while (token != null)

    await Task.WhenAll(migrationTasks);
}

private async Task MigrateEntries(IEnumerable<MyTableEntity> entries, string newPartition)
{
    await Task.WhenAll(
        InsertInBatches(entries.Select(
            e => GetEntryWithUpdatedPartitionKey(e, newPartition)),
        DeleteInBatches(entries));
}
  • GetEntriesSegment包装逻辑以访问表并获取段
  • GetEntryWithUpdatedPartitionKey只是将MyTableEntity类型的一个对象中的所有字段复制到新创建的字段中,但使用不同的PartitionKey
  • InsertInBatches负责将条目集合拆分为100个批次(Azure表存储限制)并并行执行批量插入(通过另外一个await Task.WhenAll(insertTasks)
  • DeleteInBatches负责将条目集合拆分为100个批次(Azure表存储限制)并对所有项目进行批量删除(通过另外一个await Task.WhenAll(deleteTasks)

我的主要目标是平行一切。即,应该读取新条目,同时删除已读取的条目并插入新条目。

这个解决方案看起来合理吗?您是否知道任何经过时间验证(经过良好测试,用于生产项目)的替代方案?

2 个答案:

答案 0 :(得分:1)

你能试试https://msdn.microsoft.com/library/hh228603.aspx

吗?

任务并行库(TPL)提供数据流组件,以帮助提高启用并发的应用程序的健壮性。

答案 1 :(得分:0)

您可以使用TPL Dataflow库来完成此任务,该库还可以优雅地处理将操作批处理为每个表批处理100个操作的要求。我还想强调一下,这段代码不会阻止等待读取整个分区,而是一次流一个批处理。这就是Dataflow库为您提供的功能。

public async Task MigratePartitionAsync<T>(CloudTable table, string oldPartitionKey, string newPartitionKey)
    where T : TableEntity, new()
{
    // batch up to 100 records per table operation
    var buffer = new BatchBlock<T>(100);

    // migrate the records
    var migrator = new ActionBlock<T[]>(async x => await MigrateRecordsAsync(table, x, newPartitionKey));

    // link the blocks and set them to propogate their completion
    buffer.LinkTo(migrator, new DataflowLinkOptions { PropagateCompletion = true });

    // read the old partition
    await ReadPartitionAsync(table, buffer, oldPartitionKey);

    await migrator.Completion;
}

public async Task ReadPartitionAsync<T>(CloudTable table, ITargetBlock<T> buffer, string partitionKey)
    where T : TableEntity, new()
{
    var results = table.CreateQuery<T>().Where(x => x.PartitionKey == partitionKey); 

    foreach (var record in results)
    {
        await buffer.SendAsync(record);
    }

    buffer.Complete();
}

public async Task MigrateRecordsAsync<T>(CloudTable table, IEnumerable<T> records, string newPartitionKey)
    where T : TableEntity, new()
{
    var deleteBatch = new TableBatchOperation();

    foreach (var element in records)
    {
        deleteBatch.Delete(element);
    }

    await table.ExecuteBatchAsync(deleteBatch);

    var insertBatch = new TableBatchOperation();

    foreach (var element in records)
    {
        element.PartitionKey = newPartitionKey;
        insertBatch.Insert(element);
    }

    await table.ExecuteBatchAsync(insertBatch);
}

您可以这样使用它:

CloudTable table = GetCloudTable();

await MigratePartitionAsync<MyTableEntityClass>(table, "OldPartitionKey", "NewPartitionKey");