大量ファイルによるBlob Trigger起動遅延の防止方法

# 背景
IoT機器から送信されるさまざまなファイルをBlob Storageに取り込み、Azure FunctionsのBlob Triggerを用いて処理を行っています。システムをリリースした数年前は数分以内に処理が完了しており[^1]、システムの特性上、特に問題はありませんでした。

[^1]: 従量課金プランではありませんでしたが、当時から送信されるファイルが多かったため、処理が完了するまでに時間がかかっていました。

しかしながら、ここ1年ほどでファイル数が大幅に増え[^2]、取り込みの遅延が数時間から場合によっては1日以上に及び、データを利用しているシステムに影響が出始めていました。

[^2]: Storage Explorerからフォルダー統計を取得したところ、現時点で1,000万ファイルを超えていました。

# 結論

以下の記事を参考にして、Event Gridを経由してBlob Triggerを起動することで、ほぼ遅延がなくなりました。
- [チュートリアル: イベントサブスクリプションを使用してBLOBコンテナーでAzure Functionsをトリガーする](https://learn.microsoft.com/ja-jp/azure/azure-functions/functions-event-grid-blob-trigger)
    - Event Gridを有効化
    - Storage Accountのイベントをサブスクライブできるよう設定する
    - `Microsoft.Azure.Functions.Worker.Extensions.Storage.Blobs`のバージョンを5.x以上にする
    - Blob Trigger属性のパラメータに`source = BlobTriggerSource.EventGrid`を追加する
- [Azure Event Gridを使用してBLOBストレージイベントをWebエンドポイントにルーティングする (Azure portal)](https://learn.microsoft.com/ja-jp/azure/event-grid/blob-event-quickstart-portal)
    - 本番環境でデバッグする場合（フィルタ設定をテストする場合など）は、ページ内の「Deploy to Azure」ボタンを使ってデプロイできるアプリケーションが便利です

# 原因

Blob Triggerは、[ポーリングと待ち時間](https://learn.microsoft.com/ja-jp/azure/azure-functions/functions-bindings-storage-blob-trigger#polling-and-latency)に記載されているように、10,000ファイルごとにポーリングを実施し、変化を検出しています。つまり、保存されているファイルが多くなると、それだけ遅延が増加することになります。

Blob Triggerの設定は`host.json`に記載することができます。そこで、ポーリング間隔を短くしたり、スキャンするファイル数を変更できないか調べましたが、[host.json 設定](https://learn.microsoft.com/ja-jp/azure/azure-functions/functions-bindings-storage-blob#hostjson-settings)にある通り、これらの設定を変更することはできないようでした。

# 実際の作業

前提としてFunctions、Blob Storageは作成済みです。そのため、Event Gridの有効化、Blob Storageのイベント設定、Functionsのパラメータ追加を実施しました。

## Event Gridの有効化

最初の時点ではEvent Gridが有効化されていないので、`Microsoft.EventGrid`を検索し、三点リーダから「登録」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7886/c2610cf7-4176-af27-75d9-cdf4a0810174.png)

1分ほどすると設定が完了し、状態がregisteredに変わります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7886/71ce8172-a082-2dea-371f-2e5516c032cf.png)

これで準備はOKです。

## Functionsのパラメータ追加

Functionsの設定を行う前に`Microsoft.Azure.Functions.Worker.Extensions.Storage.Blobs`のバージョンを5.x以上にします。

次にBlob Trigger属性のパラメータに`Source = BlobTriggerSource.EventGrid`を追加します。
```diff_c_sharp
  [FunctionName("LogToDatabase")]
  public void Run(
      [BlobTrigger(
          "logs/{id}/{year}-{month}-{day}-{hour}-{minute}-{second}.txt",
+         Source  = BlobTriggerSource.EventGrid,
          Connection = "LogConnectionString"
      )] Stream myBlob,
      string id,
      int year,
      int month,
      int day,
      int hour,
      int minute,
      int second,
      int blobTrigger,
      ILogger log)
  {
  }
```
## Azure Event Grid Viewerのデプロイ

[Azure-Samples/azure-event-grid-viewer](https://github.com/Azure-Samples/azure-event-grid-viewer) を利用すると、Event Gridが発行するイベントを簡単に確認できます。リソースもApp ServiceプランとApp Serviceしかデプロイしないので、後片付けも簡単です。

## Blob Storageの設定

ストレージアカウント左メニューの「イベント」でイベントの発行を設定できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7886/6554c096-8c1a-bbe4-adb0-ff90d59ef119.png)

「＋イベントサブスクリプション」から、イベントを作成する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7886/b47fd338-3e9b-03f4-528b-69361320992d.png)

### エンドポイント
#### デバッグ環境
エンドポイントを先ほど作成したAzure Event Grid ViewerのURL+`/api/updates`とします。
#### 本番環境

> ```
> https://<FUNCTION_APP_NAME>.azurewebsites.net/runtime/webhooks/blobs?functionName=BlobTriggerEventGrid&code=<BLOB_EXTENSION_KEY>
> ```
> この例で、<FUNCTION_APP_NAME> を関数アプリの名前に置き換え、<BLOB_EXTENSION_KEY> をポータルから取得した値に置き換えます。 関数に別の名前を使用した場合は、必要に応じて functionName クエリ文字列を変更する必要もあります。

- [エンドポイント URL を作成する](https://learn.microsoft.com/ja-jp/azure/azure-functions/functions-event-grid-blob-trigger#build-the-endpoint-url-1) 参照

### フィルター

Functionsが1つだけであったとしても、Triggerのフォーマットと異なる形式を送ってしまうとエラーになってしまうため、正しく指定します。デバッグ環境で正しく送信されることを確認してからエンドポイントを本番環境に変更することをお勧めします[^3]。

[^3]: Functionsで設定したフォーマットと異なる場合でも実行されるということに気づかず、数千回のエラーを発生させてしまいました…。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7886/ae9361b5-3e24-dbbd-227a-be7b606a1793.png)

### Azure Event Grid Viewerでの確認

ストレージアカウントにファイルをアップロードすると、Azure Event Grid Viewerにイベントが発行され、データが確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7886/68dc793f-8664-6a5c-0af7-2799f428a411.png)

コンテナを指定したい場合は「次で始まるサブジェクト」に `/blobServices/default/containers/<container name>`を指定しておくといいです。高度なフィルターでも様々な設定ができるので便利でした。


# 雑感

FunctionsのBlob Triggerを改善しないといけないと思いつつ、放置してしまっていました。今回、Event Grid経由にしたことでシステムの遅延を大幅に改善できてよかったと思います。