[Monte Carlo] SnowflakeとMonte Carloの接続を設定する #MonteCarloData

2023.08.15

Monte CarloとSnowflakeを接続する方法を紹介します。

Monte Carloとデータウェアハウスを接続すると、テーブルとパイプラインの状態を追跡できるようになります。メタデータ、クエリ ログ、メトリクスをウェアハウスから自動的に取得することで、データパイプラインの状態を可視化しています。

1. SnowflakeでMonte Carlo用のサービスアカウントを作成する

公式ドキュメントにある以下のクエリを実行すれば作成できます。

まず、下記クエリでこれ以降に使用するクエリの変数を定義します。ハイライトを入れている3行目と7行目はユーザー側で任意のパスワードとデータベース名を入力します。他にもユーザー名、ウェアハウス名、ロール名も任意の名前にしたい場合には=(イコール)の右側を書き換えます。

-- Configuration
set mc_username='MONTE_CARLO';
set mc_password='<monte_carlo_password>';
set mc_warehouse_size='XSMALL';
set mc_warehouse_name='MONTE_CARLO_WH';
set mc_role_name='MONTE_CARLO_ROLE';
set database_to_monitor='<your_database>';

パスワードとデータベースを入力したら、以下のクエリをすべて実行します。

-- 権限付与用にロールを設定
USE ROLE ACCOUNTADMIN;

-- Monte Carloのモニタリングワークロード用にウェアハウスを作成
CREATE WAREHOUSE IF NOT EXISTS identifier($mc_warehouse_name) WAREHOUSE_SIZE=$mc_warehouse_size INITIALLY_SUSPENDED=TRUE
    AUTO_SUSPEND = 5 AUTO_RESUME = TRUE;

-- ユーザーMONTE_CARLOが使用するロールを作成
CREATE ROLE IF NOT EXISTS identifier($mc_role_name);

-- モンテカルロ用ユーザー(MONTE_CARLO)の作成と、ロール(MONTE_CARLO_ROLE)の付与
CREATE USER IF NOT EXISTS identifier($mc_username) PASSWORD=$mc_password DEFAULT_ROLE=$mc_role_name;
GRANT ROLE identifier($mc_role_name) TO USER identifier($mc_username);

-- 新たなウェアハウス(MONTE_CARLO_WH)を使用する権限を付与
GRANT OPERATE, USAGE, MONITOR ON WAREHOUSE identifier($mc_warehouse_name) TO ROLE identifier($mc_role_name);

-- クエリ履歴へのアクセス権を付与
GRANT IMPORTED PRIVILEGES ON DATABASE "SNOWFLAKE" TO ROLE identifier($mc_role_name);

-- モニター対象のデータベースおよびスキーマのメタデータにアクセスできる権限を付与
GRANT USAGE,MONITOR ON DATABASE identifier($database_to_monitor) TO ROLE identifier($mc_role_name);
GRANT USAGE,MONITOR ON ALL SCHEMAS IN DATABASE identifier($database_to_monitor) TO ROLE identifier($mc_role_name);

-- スキーマにfuture grantsが付与されているかを確認 ※参考を参照

USE DATABASE identifier($database_to_monitor);
CREATE OR REPLACE PROCEDURE GRANT_REFERENCES_TO_MONTE_CARLO()
    RETURNS VARCHAR
    LANGUAGE javascript
EXECUTE AS CALLER
AS
$$ 
snowflake.createStatement({sqlText: `use database identifier($database_to_monitor)`}).execute();
var show_future_grants = snowflake.createStatement({sqlText: `SHOW FUTURE GRANTS IN DATABASE identifier($database_to_monitor)`}).execute();
var schema_future_grants = snowflake.createStatement({sqlText: `select * from TABLE(RESULT_SCAN('${show_future_grants.getQueryId()}')) where "grant_on" = 'SCHEMA'`}).execute();
if (schema_future_grants.getRowCount() > 0) {
    var schemas_to_grant = snowflake.createStatement({ sqlText:`select * from information_schema.SCHEMATA where SCHEMA_NAME <> 'INFORMATION_SCHEMA'`}).execute();
    var granted_schemas = "";
    while(schemas_to_grant.next()) {
      table_schema = schemas_to_grant.getColumnValue("SCHEMA_NAME");

      snowflake.createStatement({ sqlText:`GRANT REFERENCES ON ALL TABLES IN SCHEMA ${table_schema} TO ROLE identifier($mc_role_name)`}).execute();
      snowflake.createStatement({ sqlText:`GRANT REFERENCES ON ALL VIEWS IN SCHEMA ${table_schema} TO ROLE identifier($mc_role_name)`}).execute();
      snowflake.createStatement({ sqlText:`GRANT REFERENCES ON ALL EXTERNAL TABLES IN SCHEMA ${table_schema} TO ROLE identifier($mc_role_name)`}).execute();

      snowflake.createStatement({ sqlText:`GRANT REFERENCES ON FUTURE TABLES IN SCHEMA ${table_schema} TO ROLE identifier($mc_role_name)`}).execute();
      snowflake.createStatement({ sqlText:`GRANT REFERENCES ON FUTURE VIEWS IN SCHEMA ${table_schema} TO ROLE identifier($mc_role_name)`}).execute();
      snowflake.createStatement({ sqlText:`GRANT REFERENCES ON FUTURE EXTERNAL TABLES IN SCHEMA ${table_schema} TO ROLE identifier($mc_role_name)`}).execute();
      granted_schemas += table_schema + "; "
    }
    return `Granted references for schemas ${granted_schemas}`;
}
snowflake.createStatement({ sqlText: `GRANT REFERENCES ON ALL TABLES IN DATABASE identifier($database_to_monitor) TO ROLE identifier($mc_role_name)`}).execute();
snowflake.createStatement({ sqlText: `GRANT REFERENCES ON ALL VIEWS IN DATABASE identifier($database_to_monitor) TO ROLE identifier($mc_role_name)`}).execute();
snowflake.createStatement({ sqlText: `GRANT REFERENCES ON ALL EXTERNAL TABLES IN DATABASE identifier($database_to_monitor) TO ROLE identifier($mc_role_name)`}).execute();

snowflake.createStatement({ sqlText: `GRANT REFERENCES ON FUTURE TABLES IN DATABASE identifier($database_to_monitor) TO ROLE identifier($mc_role_name)`}).execute();
snowflake.createStatement({ sqlText: `GRANT REFERENCES ON FUTURE VIEWS IN DATABASE identifier($database_to_monitor) TO ROLE identifier($mc_role_name)`}).execute();
snowflake.createStatement({ sqlText: `GRANT REFERENCES ON FUTURE EXTERNAL TABLES IN DATABASE identifier($database_to_monitor) TO ROLE identifier($mc_role_name)`}).execute();
return `Granted references for database`;
$$;
CALL GRANT_REFERENCES_TO_MONTE_CARLO();

-- モニター対象のデータベースに読み取り専用(SELECT権限のみ)の権限を付与
GRANT SELECT ON ALL TABLES IN DATABASE identifier($database_to_monitor) TO ROLE identifier($mc_role_name);
GRANT SELECT ON ALL VIEWS IN DATABASE identifier($database_to_monitor) TO ROLE identifier($mc_role_name);
GRANT SELECT ON ALL EXTERNAL TABLES IN DATABASE identifier($database_to_monitor) TO ROLE identifier($mc_role_name);
GRANT SELECT ON ALL STREAMS IN DATABASE identifier($database_to_monitor) TO ROLE identifier($mc_role_name);


GRANT USAGE ON FUTURE SCHEMAS IN DATABASE identifier($database_to_monitor) TO ROLE identifier($mc_role_name);

-- スキーマにfuture grantsが付与されているかを確認 ※参考を参照

USE DATABASE identifier($database_to_monitor);
CREATE OR REPLACE PROCEDURE GRANT_SELECT_FUTURES_TO_MONTE_CARLO()
    RETURNS VARCHAR
    LANGUAGE javascript
EXECUTE AS CALLER
AS
$$
snowflake.createStatement({sqlText: `use database identifier($database_to_monitor)`}).execute();
var show_future_grants = snowflake.createStatement({sqlText: `SHOW FUTURE GRANTS IN DATABASE identifier($database_to_monitor)`}).execute();
var schema_future_grants = snowflake.createStatement({sqlText: `select * from TABLE(RESULT_SCAN('${show_future_grants.getQueryId()}')) where "grant_on" = 'SCHEMA'`}).execute();
if (schema_future_grants.getRowCount() > 0) {
    var schemas_to_grant = snowflake.createStatement({ sqlText:`select SCHEMA_NAME from information_schema.SCHEMATA where SCHEMA_NAME <> 'INFORMATION_SCHEMA'`}).execute();
    var granted_schemas = "";
    while(schemas_to_grant.next()) {
      table_schema = schemas_to_grant.getColumnValue("SCHEMA_NAME");
      snowflake.createStatement({ sqlText:`GRANT SELECT ON FUTURE TABLES IN SCHEMA ${table_schema} TO ROLE identifier($mc_role_name)`}).execute();
      snowflake.createStatement({ sqlText:`GRANT SELECT ON FUTURE VIEWS IN SCHEMA ${table_schema} TO ROLE identifier($mc_role_name)`}).execute();
      snowflake.createStatement({ sqlText:`GRANT SELECT ON FUTURE EXTERNAL TABLES IN SCHEMA ${table_schema} TO ROLE identifier($mc_role_name)`}).execute();
      snowflake.createStatement({ sqlText:`GRANT SELECT ON FUTURE STREAMS IN SCHEMA ${table_schema} TO ROLE identifier($mc_role_name)`}).execute();
      granted_schemas += table_schema + ";"
    }
    return `Granted future select for schemas ${granted_schemas}`;
}
snowflake.createStatement({ sqlText:`GRANT SELECT ON FUTURE TABLES IN DATABASE identifier($database_to_monitor) TO ROLE identifier($mc_role_name)`}).execute();
snowflake.createStatement({ sqlText:`GRANT SELECT ON FUTURE VIEWS IN DATABASE identifier($database_to_monitor) TO ROLE identifier($mc_role_name)`}).execute();
snowflake.createStatement({ sqlText:`GRANT SELECT ON FUTURE EXTERNAL TABLES IN DATABASE identifier($database_to_monitor) TO ROLE identifier($mc_role_name)`}).execute();
snowflake.createStatement({ sqlText:`GRANT SELECT ON FUTURE STREAMS IN DATABASE identifier($database_to_monitor) TO ROLE identifier($mc_role_name)`}).execute();
return `Granted future select for database`;
$$;
CALL GRANT_SELECT_FUTURES_TO_MONTE_CARLO();

※参考(https://docs.snowflake.com/ja/sql-reference/sql/grant-privilege#considerations

データベースとスキーマに対するfuture grantが重複する場合、スキーマに対するfuture grantが優先されるというのがSnowflakeの仕様としてあるため、対象のデータベースにすでにスキーマに対するfuture grantがあるかどうかを確認しています。

2. Monte CarloでSnowflakeの情報を入力して接続する

Snowflake側でMonte Carlo用のユーザーが作成できたら、Monte Carloの画面でSnowflakeの接続情報を入力します。

Settings > Integrations > Data Lake and Warehouses > Create > Snowflake を選択

以下の入力フォームが出てくるので、ユーザー名などSnowflakeで作成したものを入力します。コネクション名はMonte Carloで管理しやすい任意の名前を入力します。

  • Connection Name
  • Account
  • Warehouse
    • MONTE_CARLO_WH
  • User
    • MONTE_CARLO
  • Password
    • ユーザー作成時に設定したパスワード

すべて入力して、テストが通れは接続は完了です。

アカウント名取得に苦戦

今回トライアルアカウントからの設定だったため、Account名でかなり引っかかってしまいました。アカウント名なのでログイン時のURLのsnowflakecomputing.comの前を持ってくればいいと思っていたのですが、ずっとError: 250001 (n/a): Could not connect to Snowflake backend after 0 attempt(s).Abortingというエラーが出続けて心が折れました。

試しにselect current_account();を実行したところ、ログイン用のURLとは全然違うものが出てきて自分の間違いに気づきました。

こういうAPIのアカウント名で引っかかった際には、まず、Snowsight画面の管理 > アカウント で対象のアカウントの行のロケーターという項目にマウスオーバーするとコピーマークが出てくるので、それをコピーします。コピーしたURLの`https://`以降と`.snowflakecomputing.com`より前の部分がアカウント名となります。(Snowflakeをホストしているクラウドとリージョンが入っていればそれがアカウント名です。)

参考:こちらからSnowflakeをホストしているクラウドとリージョンを見つけて、Account Identifierを確認できます。(例:AWSの東京リージョンならap-northeast-1.aws

自動でテーブル情報を収集

接続して2週間ほどでMonte Carloが接続しているデータベースの情報を学習して、Assetsページにデータの取得情報などをまとめてくれます。

まとめ

Monte CarloをSnowflakeと連携する方法のご紹介でした!