Snowflakeのリソースモニター設定をTerraformで行う

破産したくないので、個人の検証用 Snowflake にリソースモニターを設定します。せっかくなので、SQL ではなく Terraform を使って設定します。加えて、内部で発行されている SQL 文も確認します。

# 1. はじめに

Snowflake は計算リソース（仮想ウェアハウス）を細かい単位で起動/停止でき、それがコスト面の優位性になっています。ただ、単位時間当たりの単価は比較的高く、AWS 東京リージョンの Snowflake だと 2023/3/31 時点で以下になります。

- Standard：$2.85 / 時間（= 約 430 円）
- Enterprise：$4.30 / 時間（= 約 640 円）

会社や実プロジェクトで使うのであれば予算もある程度あるのですが、個人で検証用に持っている環境（Snowflake アカウント）だと、とても怖い。

そこで日々の消費コストを監視するとともに、想定以上にコストが増えた場合に自動で通知や停止する仕組みを準備しておくことが重要になります。後者を実現する方法として Snowflake にはリソースモニターという機能があるので、それを個人の検証用 Snowflake アカウントに設定したいと思います。

ついでに、そろそろ Terraform での Snowflake 環境管理も学んでおきたいので、SQL ではなく Terraform でリソースモニターの設定を行いたいと思います。

加えて、こういうツールでは内部で Snowflake にどういうコマンドが発行されているか確認しないと気が済まないので、それも追いかけたいと思います。

# 2. 準備

この章は Terraform を Snowflake 用に使うための設定です。Terraform と Snowflake の経験がある方は読み飛ばして問題ありません。

今回、Terraform の CLI を Oracle Linux 9.2 にインストールして使いたいと思います。インストールは以下の [CentOS/RHEL] の通りに実施しました。

https://developer.hashicorp.com/terraform/install#linux

Terraform から Snowflake の接続ではキーペア認証を用いるので、Snowflake QuickStarts の以下を参考に Terraform 専用の Snowflake ユーザーの作成とキーペア認証の設定を行います。

[Terraforming Snowflake - 3. Create a Service User for Terraform](https://quickstarts.snowflake.com/guide/terraforming_snowflake/index.html?index=..%2F..index#2)

この QuickStart では 仮想ウェアハウス/DB とその権限管理を行うため、SYSADMIN と SECURITYADMIN の２つのロール利用していますが、リソースモニターの作成は ACCOUNTADMIN ロールが必要なので、今回は ACCOUNTADMIN ロールを代わりに付与しています。

```sql
create user tf_snow
DEFAULT_ROLE=PUBLIC
MUST_CHANGE_PASSWORD=FALSE
RSA_PUBLIC_KEY= 'MIIBI...';

grant role accountadmin to user tf_snow;
```

Terraform から Snowflake への接続情報は .bash_profile に環境変数として設定しています。

```sh
export SNOWFLAKE_USER=tf_snow
export SNOWFLAKE_AUTHENTICATOR=JWT
export SNOWFLAKE_PRIVATE_KEY=`cat ~/.ssh/snowflake_tf_snow_key.p8`
export SNOWFLAKE_ACCOUNT="<組織名>-<アカウント名>"
```

最後に、Snowflake アカウントオーナーのユーザーのプロファイル設定画面で通知を有効化しておきます。

![通知有効化.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/388675/d8d1cc7a-887c-c467-d615-32c096b938f5.png)

以上で準備は完了です。

# 3. 設定＆確認

ファイル main.tf に以下の内容を記載します。

```terraform:main.tf
terraform {
  required_providers {
    snowflake = {
      source = "Snowflake-Labs/snowflake"
      version = "~> 0.87"
    }
  }
}

provider "snowflake" {
    role = "ACCOUNTADMIN"
}

resource "snowflake_resource_monitor" "monitor" {
  name = "account_credit_limit"
  credit_quota = 3
  frequency = "WEEKLY"
  start_timestamp = "IMMEDIATELY"

  notify_triggers = [1]
  suspend_trigger = 80
  suspend_immediate_trigger = 90

  set_for_account = true
}
```

- Snowflake 用の Terraform Provider として ```Snowflake-Labs/snowflake``` を利用します。
    - Snowflake + Terraform に関するもう一つの Snowflake Quickstarts [DevOps: Database Change Management with Terraform and GitHub](https://quickstarts.snowflake.com/guide/devops_dcm_terraform_github/index.html?index=..%2F..index#5) では ```chanzuckerberg/snowflake``` を利用していますが、現時点では Snowflake-Labs の方に置き換わている？
- リソースモニターの管理に利用するロールとして ACCOUNTADMIN を指定します。
- リソースモニタの設定は以下です。
    - クレジットクォーター = 3, チェック頻度は週次 (WEEKLY) を選択します。
        - 1週間で 1,000 円ちょっとぐらいなら良いかと思い、1,000円 ÷ 150円/ドル ÷ $2.85/credit = 約 2.3クレジットを目安にクォーターを設定しています。
    - ```start_timestamp = "IMMEDIATELY"``` で監視を今すぐ開始します。
    - クレジットを消費した際の動作を以下のようにしました。
        - notify_triggers：クレジットクォーターの 1 % に達したらメールで通知する（通知のテストのため極端に低い値に設定）。
        - suspend_trigger：クレジットクォーターの 80% に達したら仮想ウェアハウスを停止する。ただし実行中の処理があった場合は完了を待つ。
        - suspend_immediate_trigger：クレジットクォーターの 90% に達したら仮想ウェアハウスを即時停止する（実行中の処理は強制終了）。
    - リソースモニターはアカウントレベルで設定する ( = 全仮想ウェアハウス＋クラウドサービスの消費クレジット合計が対象）。
        - サーバーレスで消費するクレジットは対象外なので注意。

ちなみに、[Snowflake Providerのドキュメント](https://registry.terraform.io/providers/Snowflake-Labs/snowflake/latest/docs/resources/resource_monitor) では、Example　Usage として ```suspend_triggers``` と ```suspend_immediate_triggers```（複数形）を使っていますが、これは Deprecated のようです（そもそも複数のしきい値を設定する意味はないはずですが）。

main.tf を作成後、以下の手順を踏んで実行します。

```
terraform init -upgrade
terraform plan
terraform apply
```

適用後に少しクレジットを消費すると、以下のような通知がメールで届きます。

![通知.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/388675/c1ec73e9-4e81-df81-1bf2-0c97e2fba00c.png)

想定通り通知は動いているようなので、通知のしきい値を 50% に変更するため、main.tf に以下の変更を加え再実行します。

```terraform:main.tf（抜粋）
  notify_triggers = [50]
```

```
terraform plan
terraform apply
```

# 4. 気になる点

Terraform を実行した際に内部で発行されている SQL 文を account_usage.query_history から確認しました。以下に発行されている SQL 文を示します。

- ```SELECT 1```　や ```SELECT CURRENT_ACCOUNT() as CURRENT_ACCOUNT``` など影響のない SQL 文は除外します。
- 読みやすいように整形しています。

初回（通知しきい値 = 1%）

```sql
CREATE RESOURCE MONITOR "account_credit_limit"
WITH CREDIT_QUOTA = 3
FREQUENCY = WEEKLY 
START_TIMESTAMP = 'IMMEDIATELY' 
TRIGGERS 
    ON 80 PERCENT DO SUSPEND 
    ON 90 PERCENT DO SUSPEND_IMMEDIATE 
    ON 1 PERCENT DO NOTIFY
```

```sql
ALTER ACCOUNT SET RESOURCE_MONITOR = "account_credit_limit"
```

```sql
SHOW RESOURCE MONITORS LIKE 'account_credit_limit'
```

再実行（通知しきい値 = 50%）

```sql
ALTER RESOURCE MONITOR "account_credit_limit" 
SET
FREQUENCY = WEEKLY
START_TIMESTAMP = 'IMMEDIATELY' 
TRIGGERS 
    ON 80 PERCENT DO SUSPEND 
    ON 90 PERCENT DO SUSPEND_IMMEDIATE 
    ON 50 PERCENT DO NOTIFY
```

```sql
SHOW RESOURCE MONITORS LIKE 'account_credit_limit'
```

気になる点は以下３点です。

- 再実行で ```FREQUENCY``` と ```START_TIMESTAMP``` が再設定
    - これは、監視期間の起点が再実行時にリセットされることを意味しています。通常では問題にはならないと思いますが、そういう挙動だと認識はしておいた方が良さそうです。
- 識別子が二重引用符付き
    - つまり、大文字/小文字を区別するようになります。今回のリソースモニターの場合は問題にならないと思いますが、テーブル名やカラム名などでこれをやられると、対応していないサードパーティーツール（ETLツール、データカタログツールなど）があった時に悩ましいので、できれば回避したいところ。
- ```SHOW RESOURCE MONITORS LIKE '...'``` でエスケープ未処理
    - 現実としては滅多に問題にはならないですが、```account-credit-limit``` というような識別子のリソースモニターもあった場合、SHOW コマンドは 2 件ヒットするはずなのですが（LIKE の ```_``` は任意の１文字にマッチするため）、大丈夫なんでしょうか。

特に、２番目と３番目については回避できないか追加調査したいです。