Azure CosmosDB (Core) Serverless のコストの考え方

Azure Cosmos DB を使っていて、コストがいつどんな条件で生じるのかよくわからなかったので調べてみました。  
※Microsoft のサポートの方にも助けていただきました。ありがとうございました！
# 結論
## 従量課金モデルです
- コンセプトどおり、 **使用量(消費したRequest Unit[RU])に応じてコストが発生** します。
  - コストの計算方法は 合計RU数 × 100万RU あたりの料金 / 100万
    - たとえば、2023/04/10 の Azure 料金計算ツール上での価格に基づけば、1RUのコストは次のようになります。
      1RUのコスト = 1 × ¥33.13 / 100万 = ¥0.00003313
    - ただ、実際にはこれで課金が発生することはなく、「<¥0.01」として¥0とみなされるはずとのことです。
  - ストレージ部分はこれとは別で、格納しているデータ量に応じた従量課金です
- Azure 料金計算ツールでは最低100万RUからしか設定できないですが、これは料金計算ツールの仕様であり価格モデルとは異なるので、混同しないようにしましょう。
  - 100万RU未満の料金見積もりをしたい場合は、前述の計算式「合計RU数 × 100万RU あたりの料金 / 100万」を使って計算しましょう。
## コストと課金(請求)を区別しよう
Microsoft のサポートの方と会話している時に気付いたのですが、コストと課金(請求)は異なる概念であることに留意が必要だなと思いました。
- コストとは、そのリソースに対して発生する請求予定の料金です。
- 課金(請求)とは、請求期間が完了した後に実際に請求される料金です。
- つまり、日々コストは発生しているが、課金(請求)されるのは請求期間の完了後だということです。
  - これが効いてくるのは小さなコストが積み上がってきた時です
  - 例）毎日「<¥0.01」となるような使い方をすると仮定した場合、日々のコストはほぼ¥0なので課金されないように思えますが、請求期間で累積した時に¥0.01を超えると課金されるということが起きます
## ただちょっとよくわからないところも...
### 消費RUとコストの傾向の不一致
今回検証するために作成したリソースの中で、消費RUとコストが見合ってないリソースがありました。  
下図を参照してください。1,000RU消費したリソースと10,000RU消費したリソースでコストの表示が逆転しています。  
これについては問い合わせ中なので解決次第記事を更新したいと思います。
※2023/04/19 Updated：サポートを通じて、この現象はAzureサービス側の不具合だということが判明しました。~~鋭意対応中とのことで、いずれ修正されるでしょう。~~
※2023/06/07 Updated：本件不具合の修正がデプロイされたらしいです。
![azure_cosmosdb_cost_comparison_table.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3118632/0b352378-7815-19cd-44fc-27b5da7d2cf2.png)

### 明示的にアクセスしていないのにRUを消費している
今回検証するために作成したリソースの中で、明示的にアクセスしていないのにRUを消費しているリソースがありました。  
![azure_cosmosdb_implicit_access.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3118632/c38571dd-a371-7a2a-85e2-acae830627bb.png)
これ、原因不明で消費RUとしてコストの対象になってしまってたら不安で使えないので確認しました。  
結果、これは **Azureのシステム側でデータベースやコレクションに対してメタデータ操作のためにアクセスしているもの** だということでした。  
メタデータ操作のためのRUはシステム的に予約されており(アカウントあたり240RUとのこと)、この操作で使用したRUは課金対象ではないそうです。  
ただ、現状コスト分析画面にはこのRUも表示されてしまうので、ユーザが明確に識別できない状態です。
# 検証について
筆者がコストの発生状況を確認するために作成したリソースと、それらを使用した検証方法を簡単に解説します。
## 動機
そもそも、何故コストについて検証しようと思ったのか。  
下図のように、明示的にアクセスしていないタイミングで定期的にRUを消費していることを発見したのですが、これがコストに反映されておらずどういうことかわからん。。。となったのがきっかけです。

※再掲：定期的に消費されるRUの図
![azure_cosmosdb_implicit_access.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3118632/c38571dd-a371-7a2a-85e2-acae830627bb.png)
## 検証用に用意したリソース
|リソース名|説明|検証内容|
|:--|:--|:--|
|cosmosdb-cost-research-001-1-item|アカウントを作成して1アイテムだけ登録した|コストが発生するタイミングを調べる|
|cosmosdb-cost-research-002-nodata|アカウントを作成しただけでその後一切触れない|コストの発生条件を調べる|
|cosmosdb-cost-research-003-million-ru|アカウントを作成して1アイテム登録し、100万RU消費するまでReadした|コストの発生条件を調べる|
|cosmosdb-cost-research-004-1000-ru|アカウントを作成して1アイテム登録し、1,000RU消費するまでReadした|コストの発生条件を調べる|
|cosmosdb-cost-research-005-10000-ru|アカウントを作成して1アイテム登録し、10,000RU消費するまでReadした|コストの発生条件を調べる|
## 各リソースの課金状況
![azure_cosmosdb_result_costs.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3118632/f9804361-cff0-e7b5-adcb-7d487426e220.png)
## 各リソースのRU消費状況
![azure_cosmosdb_verify_resources.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3118632/1507c644-54cd-701e-a80e-3c71691d790a.png)
## CosmosDBにアクセスして1アイテムReadして、消費したRUを計測するプログラムの断片
Azure Cosmos DB アカウントを作成したときに Azure portal からダウンロードできる Node.js 向けのサンプルコードを少し改造したものを使いました。
関連部分だけ抜粋して掲載します。
消費したRUを確認するには **headers['x-ms-request-charge']** を見るというのがポイントでした。
```JavaScript
createDatabase()
  .then(() => readDatabase())
  .then(() => createContainer())
  .then(() => readContainer())
  .then(async () => {
    console.log(`Querying container:\n${config.container.id}`)

    const limitRUs = 10000
    let totalRUs = 0
    const promiseArray = [];
    while (totalRUs <= limitRUs) {
      const item = await client
        .database(databaseId)
        .container(containerId)
        .item('Wakefield.7', 'Italy')
        .read()
      totalRUs += parseFloat(item.headers['x-ms-request-charge'])
    }
    console.log(`Finally totalRUs${totalRUs}`)
  })
exit(`Completed successfully`)
```