PHONE APPLI Engineer blog

エンジニアブログ

Azureで大量のデータをキューで処理する場合のボトルネックを調べてみた

リサーチデベロップメントの たかはしとし( @doushiman_jp ) です。

好きな武器はランス、好きな鉄蟲糸技は昇天突きです(サンブレイク対応)

Azure で、関数A からServiceBus( Queue Storage )を使って、関数B を呼び出したい場合があります。

キューの数が、10 とか100 くらいなら特に気にならないのですが、数千とか万単位のメッセージを発行しようとする場合、キューを処理するだけでも時間がかかるケースに直面したので、その辺を書こうと思います。

例として、下記のようなコードを組んでみました。

{
  name:hoge1
}

みたいなメッセージをエンキューするとします。

こんなデータを15,000件分用意して、書き込み用の処理B を呼び出すためのキューに一件ずつ込めてキューを発行します。

そしてそのキューを受け取った関数B は、受け取ったメッセージをDB に保存するという単純なものです。

エンキューのコードは、Microsoft様のドキュメント

docs.microsoft.com

をそのまま流用します。

const messages = [
 { name: "Albert Einstein" },
 { name: "Werner Heisenberg" },
 { name: "Marie Curie" },
 // こんなデータを15000件用意
];
async function main() {
    const sbClient = new ServiceBusClient(connectionString);
    const sender = sbClient.createSender(queueName);
    try {
        let batch = await sender.createMessageBatch(); 
        for (let i = 0; i < messages.length; i++) {
            if (!batch.tryAddMessage(messages[i])) {         
                await sender.sendMessages(batch);
                batch = await sender.createMessageBatch();
                if (!batch.tryAddMessage(messages[i])) {
                    throw new Error("Message too big to fit in a batch");
                }
            }
        }
        await sender.sendMessages(batch);
        console.log(`Sent a batch of messages to the queue: ${queueName}`);
        await sender.close();
    } finally {
        await sbClient.close();
    }
}

messages を1件ずつbatch に込めてエンキューしているのですが、15,000件のデータを保存するまでに、処理Bはなんと63秒もかかってしまいました…(Functions のプランや設定により上下します)

おそらくですが、書き込みそのものよりは、キューを発行してデキューするあたりが障害になっているんじゃないかな〜という気配がします。

なので、メッセージ処理そのものを減らすために、メッセージを配列にしてまとめて発行することで、ボトルネックの解消を狙います。

messages を

const messages = [
  [
    { name: "Albert Einstein" },
    { name: "Werner Heisenberg" },
    { name: "Marie Curie" }
    // 1配列に100レコードほど格納
  ],
  [
    ………
  ],
  [
    ………
  ]
  // 100レコード格納された配列を150個用意
];

のように変更します。

15000件のデータを100件ずつ配列に分割し、100件まとめてキューを投げることでキューの数を減らす作戦です。

処理B は配列をループし、DB に保存していきます。これで処理時間が減ればいいんだけど…どうかな…

結果として処理B の所要時間は15秒ほどに短縮されました。

実装したこっちが引くほど短縮されましたね(笑)

処理B で書き込みをする回数は双方同じであるため(書き込みはbulkで行わず、1レコードずつ保存しています)、純粋にキューを処理する部分がボトルネックだったようです。

batch にmessege を込めてキューを発行しているので、いい感じに分散されるのかな?と思っていたのですが、明示的にデータをまとめてキューを発行した方が、処理的には早くなるケースがある、ということがわかりました。

大量にキューを発行して処理する場合、こんな方法でも処理時間を短縮できますよ、ということが何かの参考になれば。

ではでは。


PHONE APPLIについて

phoneappli.net
phoneappli.net