続Azure Functions Isolated でのシングルトン

以前dotnet-isolatedなFunctionでSingletonがどうしても使いたくて、自前ロックを作った。



ただ、これでも完ぺきではなく課題が残ってる。それは実行順序。


前にも書いたが、a)のように並行して動いてしまう関数を、b)OrderIDごとに直列にしたい。



   a)

   +-----------------+  +-----------------+  +-----------------+
   |ORDER ID: 100    |  |ORDER ID: 100    |  |ORDER ID: 101    |
   |TIMESTAMP: 3s ago|  |TIMESTAMP: 2s ago|  |TIMESTAMP: 1s ago|
   +-------+---------+  +-------+---------+  +--------+--------+
           |                    |                     |
   +-------v--------------------v---------------------v--------+
   |                                                           |
   |                      Azure Functions                      |
   |                                                           |
   +-----------------------------------------------------------+

   b)

   +-----------------+
   |ORDER ID: 100    |
   |TIMESTAMP: 2s ago|
   +-------+---------+
           |
   +-------v---------+                       +-----------------+
   |ORDER ID: 100    |                       |ORDER ID: 101    |
   |TIMESTAMP: 3s ago|                       |TIMESTAMP: 1s ago|
   +-------+---------+                       +--------+--------+
           |                                          |
   +-------v------------------------------------------v--------+
   |                                                           |
   |                      Azure Functions                      |
   |                                                           |
   +-----------------------------------------------------------+


このとき、前回の自前ロックを使うことで、b)のようにOrderID=100のイベントを1個ずつ処理することに成功した。


ただし、実際は、a)のように走り始めて、先にロックが取れたほうが後続処理を実行できるので、


OrderId=100に関する2つのイベントの実行順序は不定である。 3s ago → 2s ago かもしれないし、 2s ago → 3s ago かもしれない。


それだと困る場合がある。 これをどうにか回避したい。



キューは発火のみに使う


まず、困るときの内容について考える。 どういったときに困るのか、それは、Queueメッセージに具体的なデータがありそれに基づき処理を行う場合だ。


例えば、OrderId=100の2イベント内容が以下だったとする。


{
	"OrderId": 100,
  "OrderStatus": "出荷指示",
  "Timestamp": <3s ago>,
},
{
	"OrderId": 100,
  "OrderStatus": "出荷済",
  "Timestamp": <2s ago>,
},


出荷指示がでて、出荷済になったという順番で到着する。これに基づいて処理をする函数を作らなければならないとすると、実行順序が不定だと困ることが容易に想像できる。(そもそもAuzre Storage Queue自体が順序保証していないということはいったん棚上げしておく)


この2イベントをこの順序で実行したい。ただし、CosmosDB ChangeTrackerやService Bus Queueを使うのはなし。あくまでストレージ縛りでいく。


そこで思いつくのは、キューからイベントデータであるOrderStatusとTimestampを落としてしまうという方法だ。


そうすると、キューメッセージはこのようになり、もはや順番は関係なくなる。


{ "OrderId": 100 }
{ "OrderId": 100 }


そして、除外したデータは、Blob(もしくはTable)に置いておく。

Path

Content

/events/{OrderId}/{timestamp or UUID}.json

{"OrderStatus": "出荷指示", "Timestamp": 3s ago }

/events/{OrderId}/{timestamp or UUID}.json

{"OrderStatus": "出荷済", "Timestamp": 2s ago }


キュートリガーで起動した関数は、ロックを取得後、このBlobから自身のトピックディレクトリ、ここでは /events/100/ 配下の先頭ファイルを取得して処理をする。


処理が終わったらこのBlobファイルは削除するか、処理済みディレクトリに移動させる。


これで処理順序を保証しつつトピックに基づいた直列実行ができそう。まだ試してない。