Azure IoT HubとEvent HubsとBlob StorageとDevice Provisioning Serviceを上手に使ってコストを最適化するお話し

# はじめに
今回は、IoT Hubのご利用に際して、運用は楽しつつもコストを最適化(要するに安く)する方法を書きたいと思います。
以前の投稿でも若干触れている内容ですが、今回はつくり方とかの話題は抜きにして継続的な運用・現実的なコストを目指す上で必要となる考え方を書いてみます。

結論を先に書きます

- メッセージ量とクォータへの配慮がIoT Hubコスト適正化のカギ
- 急がないメッセージはファイルアップロードでよくないですか？
- 急ぐけどメッセージ送料を削減するならEvent Hubsも併用いかがですか？
- IoT Hub、Event Hubs、Blob Storage、DPSの併用でコスト最適化と運用の簡素化を実現できます


# IoT Hubのコストの考え方
詳細は価格のページにてご確認ください。
https://azure.microsoft.com/ja-jp/pricing/details/iot-hub/
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/439826/ed56e94a-d567-7a55-df8c-2ac2ae261889.png)
IoT Hubのコストはレベル(Basic or Standard)とエディション(無料、B1～B3、S1～S3)で分かれており、そのユニット単位のコストをお支払いいただくこととなっています。
いつどれだけのデバイスが接続するか不明瞭なので、あらかじめユニットを購入いただいた上で、ユニットごとの上限に達するまでのメッセージを受け入れる方式になっています。受信した分が無限に課金される方式ではありません。
(余談ですが、上限に達するとエラーコードが返却されて受信拒否します。あと日割り清算だった記憶・・・。)

こちらのクォータ(制限事項)も参考になります。
https://docs.microsoft.com/ja-jp/azure/iot-hub/iot-hub-devguide-quotas-throttling
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/439826/09f3d2e6-7300-ccac-dffa-ea1900478aa6.png)
コストに含まれる機能を確認しますと、メッセージ以外にも多くの機能を包含していることが見てとれます。
例えばデバイスのIDをはじめとした管理、クラウドからデバイスへのメッセージ送信、デバイスツイン、DPSなどがあります。
これらIoT Hubに期待する有用な機能の多くはコストと関係なく利用可能です。（安価な使い捨てデバイスでない限り、Standardレベルを使いましょう）

- **基本は想定メッセージ量に沿ったレベル・エディション・ユニットを事前購入する**
- **メッセージ以外のクォータも存在するので確認はする**
- **機能とエディション(コスト)は直接関係していない**

IoT Hubの機能をしっかり使い倒して（デバイスを正しく制御して）、しかもコストを低く抑えるためには、
 **メッセージ量を減らす** 
必要があるとわかります。
（というか、それにつきます。）

# そのメッセージそもそも必要ですか？
メッセージで送信するということは、メッセージ個別でニアリアルタイムに誰かが(もしくは何かが)反応するということを意味します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/439826/0dd24597-4779-1f0a-4107-92d7548bab8b.png)

- デバイス変化を秒単位でスマホに送り続ける(Device To Cloud：C2D)
- スマホでデバイスのOnOffをPush通知で制御する(Cloud To Device：D2C)

例えばこのような"まぁまぁリアルタイムである"事が、サービスの価値を生み出す場合、メッセージの送信が必要であると思います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/439826/e8819fc3-03cd-3402-3994-cfe882cf6227.png)

- デバイスの負担を減らしたい
- 5分に1回くらいの情報が把握できればいい
- 指示があったら最新取得するけど、普段は1時間に1回データ送れば良い

例えばこのような"リアルタイムにしなくても大丈夫"なサービスを提供する場合、メッセージ送信は必ずしも必要とは言えません。

作るサービスによっては、5分に1回なら安価(もしくは無料)、ニアリアルタイムなら高価(もしくは有料)といった構成も考えられると思います。

# メッセージの減らし方１：ファイルアップロードにゆだねる

これで済むなら１番楽な方法です。
上記の話しでいくところの後者、急がなくて良い場合に有用なパターンです。

ドキュメントはこちらになります。
https://docs.microsoft.com/ja-jp/azure/iot-hub/iot-hub-devguide-file-upload ←概要
https://docs.microsoft.com/ja-jp/azure/iot-hub/iot-hub-csharp-csharp-file-upload ←サンプル
https://github.com/Azure-Samples/azure-iot-samples-csharp/blob/master/iot-hub/Samples/device/FileUploadSample/FileUploadSample.cs ←SDKの関係上、最近サンプルのままは動かないので修正するための情報

機能をざっくり書きます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/439826/40aa8994-9540-5642-9893-c5e392af490a.png)

- IoT Hubと連携するBlob Storageを設定する
- デバイスは"ファイルアップロードしたいよ"メッセージを投げつける(1メッセージ)
- IoT Hubは"君のSASはこれだ"と限定SAS URIをくれる
- 限定SAS URIを用いてBlob Storageにファイルをアップロードする
- デバイスは"アップロードが終わった"メッセージを投げつける(1メッセージ)

全体はこのような動作になります。
SAS URI？？な方はとりあえず、デバイス単位で時間制限を用いた投げ込み専用ID・PWD込みのURLくらいに考えてください。
それを使ってBlobに投げ込むとBlobは受け取りだけしてくれます。上書き等もオプションで指定できた(記憶があります・・・)。
この方法を用いれば1ファイルのアップロードは2メッセージ(Upしたい＆おわった)となります。
ファイル1つにまとまっていれば、大容量データも送付可能です。(あまり大きいとネットワークとか心配ですが。)
"1.67 ファイル アップロード開始/秒/ユニット (100/分/ユニット)"みたいな制約がありますので、制約事項の詳細はこちらをご確認ください。
https://docs.microsoft.com/ja-jp/azure/iot-hub/iot-hub-devguide-quotas-throttling#quotas-and-throttling
デバイス単位じゃなくてユニット単位ですのでお間違えのないようお願いします。

- Zipにしてファイルで送る
- ファイル配置をEvent GridでトリガーしてLogic Apps起動
- Logic AppsがZipを解凍して別Blobに再配置
- 再配置したイベントをEvent GridでトリガーしてLogic Apps起動
- Logic AppsがDBとかに書き込む

といったピタゴラ装置を作っている方も結構いらっしゃいます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/439826/56c381aa-8da0-3526-96bf-2ff89e3657cf.png)
安く済みますし、イベントステータスだけ把握しておけばどこか落ちても再処理が簡単ですのでお勧めです。
余談ですが、元のZipファイルをColdデータとして保持しておくと、例えば集計プログラムがバグってた時の再処理とかも簡単ですし、中間ファイルはBlobの自動削除ルール入れておけば勝手に消えていきます。
[Azure Blob Storage アクセス層の自動化によるコストの最適化](https://docs.microsoft.com/ja-jp/azure/storage/blobs/storage-lifecycle-management-concepts?tabs=azure-portal)

# メッセージの減らし方２：メッセージをまとめる(非推奨)
計算しないといけませんし後続で分割処理が必要ですが、メッセージ容量次第では効果を発揮します。
先ほどから"メッセージ"と書いていますが、具体的な内容を価格ページのFAQで確認してみます。
https://azure.microsoft.com/ja-jp/pricing/details/iot-hub/#faq
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/439826/2a525538-af13-5f14-32f2-1ea73d8fe8df.png)
有料の場合、4KBに満たないものは4KBとして1メッセージ、4KB超過は4KB単位で分割してメッセージとしてカウントするということです。
よって、例えばS1を1ユニット購入した場合１日あたり、1KBメッセージをそのまま送ると40万メッセージ×1KBですが、デバイス側で4つずつまとめて送れば40万メッセージ×1KB×4となり、単純計算では同じコストで4倍のメッセージが送信できます。
**実際には、送付メッセージの前後に色々とオマジナイが付きますのでご注意ください**

非推奨としたのは、計算が面倒なのと、4KB以上のメッセージを送っている方にとってはメリットが薄いからです。

# メッセージの減らし方３：Event Hubs を併用する
IoT Hubをご利用の方でしたら、Event Hubsの名前くらいは聞いたことがあるかと思います。
https://azure.microsoft.com/ja-jp/services/event-hubs/
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/439826/238aa24d-536d-3bb8-430b-02973ac2b823.png)
このような感じで、大量のストリーミングデータを受け取る専門家です。
IoT Hubとの差についても、こちらのドキュメントで確認します。

https://docs.microsoft.com/ja-jp/azure/iot-hub/iot-hub-compare-event-hubs
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/439826/65639fdd-d2c5-3afe-977d-098eeeb5929d.png)
IoTで使う場合は、デバイス管理とクラウドからのPush通信ができないのは問題となりますのでIoT Hubの利用を推奨しています。
そこでデバイスとクラウドの接続にはIoT Hubを利用しつつ、デバイスからクラウドへのメッセージ送信のみEvent Hubsに作業を委託することで安全性とコストを両立させることが可能です。
Event Hubsの接続情報はIoT Hub経由で入手するように作りましょう。
例えばDPSを使って１番近接地のIoT Hubに接続するように作り、IoT Hubごとに通知するEvent Hubsの接続情報を変えることで、デバイスからのメッセージも１番近接地のEvent Hubsに送信するように作ることが可能です。

# デバイスの初期のプロビジョニングとDRを楽にするためにDPSも併用

- メッセージをEvent Hubsに任せる
- Event Hubsの接続情報をIoT Hubのデバイスツインに任せる
- IoT Hubの接続情報をIoT Hub Device Provisioning Service(DPS)に任せる
- Event Hubsのgeoディザスタリカバリー(DR対策)や可用性ゾーン(HA対策)を利用する
- IoT Hubをモニターし、状況に応じてDPS配下から切り離す(DR対策)
- デバイス内には、指数関数的な話題に考慮をした再接続、再送機能を作っておく
- 送信不可が続く場合の最終手段として、最新データを破棄するか最古データを破棄するか決めておく（契約書とか注意事項にも書いておく）

このような形で全体を組んでおけば、基本的にはAzureの問題に振り回されることもなくなりますし、デバイスは電源を入れたら設定不要でメッセージの送信を始めます。
メッセージそのものはEvent Hubで安価に送り、デバイス管理系はIoT Hubを使って安全を確保できます。
IoT Hubは、デバイス数とコストに相関関係が無いので、こちらも安価に保てます。
Event Hubsを利用してもメッセージは送料がかかるので、バランスを見てファイルアップロードも利用しましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/439826/3a2e7c93-4ad4-ff6d-7eec-df6d906c0f3a.png)