色んな事を書く

シンプルさを極めたエンジニアになりたい

Azure Table Storage の Entity Group Transaction でスループットが向上するのか試してみた

Azure Table Storage のパーティション設計に関するドキュメントを読んでいると、Entity Group Transaction に関する記述がありました。

この記事では Entity Group Transaction を使うとどれほどスループットが向上するのか試してみたので紹介します。

Entity Group Transaction とはなんぞや

まずはドキュメントで説明されている Entity Group Transaction について抜粋して紹介

An entity group transaction is a set of storage operations that are implemented atomically on entities that have the same PartitionKey value.

 

Entity group transactions improve throughput because they reduce the number of individual storage operations that must be submitted to Azure Table storage. Entity group transactions also provide an economic benefit. An entity group transaction is billed as a single storage operation regardless of how many storage operations it contains.

要約すると、

  • Entity Group Transaction とは同じ PartitionKey を持つエンティティに対するストレージ操作のまとまりだよ
  • Table Storage に対する個別の操作数を減らすことが出来るので、スループットが向上するよ
  • Table Storage の課金はトランザクション数 (読み取りや書き込みなど個々の操作) にも関連するが、Entity Group Transaction は複数の操作を一つにまとめる事が出来るのでお得だよ

です。

Entity Group Transaction に含める事が出来る操作の最大数は 100 です。 このあたりの詳細は以下の公式ドキュメントを参照してください。

docs.microsoft.com

んで、Entity Group Transaction を使うとどれほどスループットが向上するのか気になったので試してみました。

Entity Group Transaction とスループットの向上

Azure.Data.Tables の TableClient を使ってみました。

docs.microsoft.com

の 2 つを試してみました。

書いたプログラムはこちらです。

[FunctionName("InsertEntityWithSamePartition")]
public async Task<IActionResult> Run(
    [HttpTrigger(AuthorizationLevel.Function, "post", Route = "tenant/_samepartitionKey")] HttpRequest req,
    ExecutionContext executionContext,
    ILogger log)
{
    log.LogInformation($"C# HTTP trigger {executionContext.FunctionName} processed a request.");

    var tableClient = _tableServiceClient.GetTableClient(SamePartitionTableName);

    var sw = new Stopwatch();
    var elapsedTimes = new List<long>(100);

    var partitonKey = "samePartitionKey";
    foreach(var _ in Enumerable.Range(0, 100))
    {
        sw.Start();
        foreach (var __ in Enumerable.Range(0,100))
        {
            var entity = new SampleEntity
            {
                PartitionKey = partitonKey,
                RowKey = Guid.NewGuid().ToString()
            };

            await tableClient.AddEntityAsync(entity);
        }

        sw.Stop();
        elapsedTimes.Add(sw.ElapsedMilliseconds);
    }

    await WriteFile(executionContext.FunctionName, elapsedTimes);

    return new OkObjectResult("Success");
}

同一のパーティションに対して書き込みを続けるので、時間経過とともにスループットの変化を見てみたく 100 件書き込むごとに ElapsedMilliseconds を取得したのですが、大きな変化はありませんでした。

続いてバッチ書き込みのプログラムです。

[FunctionName("BatchInsertEntitySamePartition")]
public async Task<IActionResult> RunBatchInsertSamePartition(
    [HttpTrigger(AuthorizationLevel.Function, "post", Route = "tenant/_samepartitionKey/_batch")] HttpRequest req,
    ExecutionContext executionContext,
    ILogger log)
{
    var tableClient = _tableServiceClient.GetTableClient(SamePartitionTableName);

    var sw = new Stopwatch();
    var elapsedTimes = new List<long>(100);

    foreach (var i in Enumerable.Range(0, 100))
    {

        var partitonKey = "partitionKey";

        var batch = new List<TableTransactionAction>(100);

        sw.Start();
        foreach (var _ in Enumerable.Range(0, 100))
        {
            var entity = new SampleEntity
            {
                PartitionKey = partitonKey,
                RowKey = Guid.NewGuid().ToString(),
            };

            batch.Add(new TableTransactionAction(TableTransactionActionType.Add, entity));
        }

        await tableClient.SubmitTransactionAsync(batch);
        sw.Stop();
        elapsedTimes.Add(sw.ElapsedMilliseconds);
    }

    await WriteFile(executionContext.FunctionName, elapsedTimes);

    return new ObjectResult("Success");
}

ファイルへの書き込みは同じメソッドを使っています。

結論だけ言うと 14 倍ほど速くなっていました。(数回やっての平均値とかはめんどくさかったので取ってないです)

気が向いたら PartitionKey が異なるものを混ぜるとどうなるか、100 件以上の操作をとりあつかうとどうなるか、とか見ようと思います。

あ、github はこちらです。

github.com