リサーチデベロップメントの たかはしとし( @doushiman_jp ) です。
好きな武器はランス、好きな鉄蟲糸技は昇天突きです(サンブレイク対応)
Azure で、関数A からServiceBus( Queue Storage )を使って、関数B を呼び出したい場合があります。
キューの数が、10 とか100 くらいなら特に気にならないのですが、数千とか万単位のメッセージを発行しようとする場合、キューを処理するだけでも時間がかかるケースに直面したので、その辺を書こうと思います。
例として、下記のようなコードを組んでみました。
{ name:hoge1 }
みたいなメッセージをエンキューするとします。
こんなデータを15,000件分用意して、書き込み用の処理B を呼び出すためのキューに一件ずつ込めてキューを発行します。
そしてそのキューを受け取った関数B は、受け取ったメッセージをDB に保存するという単純なものです。
エンキューのコードは、Microsoft様のドキュメント
をそのまま流用します。
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 を込めてキューを発行しているので、いい感じに分散されるのかな?と思っていたのですが、明示的にデータをまとめてキューを発行した方が、処理的には早くなるケースがある、ということがわかりました。
大量にキューを発行して処理する場合、こんな方法でも処理時間を短縮できますよ、ということが何かの参考になれば。
ではでは。