❄️

Snowflake Task 設計・運用ガイド:よくある落とし穴とベストプラクティス

に公開

🚀 はじめに

Snowflake Taskは、Snowflakeネイティブの強力なスケジューラ機能です。「毎日AM8時に実行」「Streamにデータが来たら実行」「Task Aの完了後に実行」といったデータパイプラインの自動化(オーケストレーション)を可能にします。

しかし、その手軽さとは裏腹に、Taskの設計には特有の制約知っておかないと必ずハマる落とし穴 が存在します。

  • 「Taskを作ったのに、なぜか実行されない(または失敗している)」
  • 「複数のSQLを実行させたいのに、エラーになる」
  • 「Task実行に必要な権限がわからない」

この記事では、Snowflake Taskを安定運用するために設計・運用方針を決める上で、絶対に押さえておくべき5つの重要な注意点と、その解決策を体系的に整理をしたので紹介させていただきます。

1. 制約:「1タスク = 1SQL」の原則と「例外」

まず、Taskの基本的な実行単位に関する制約です。
1つのTaskが AS 句の後に定義できるSQLステートメントは、原則として1つだけ です。

解決策:ストアドプロシージャ (Stored Procedure)(推奨)

この制約の最も推奨される解決策 は、実行したい複数のSQLロジックをストアドプロシージャ としてカプセル化し、Taskからはそのプロシージャを CALL するだけにする方法です。

-- 1. 実行したい処理をプロシージャにまとめる
CREATE OR REPLACE PROCEDURE my_etl_procedure()
RETURNS VARCHAR
LANGUAGE SQL
AS
$$
BEGIN
  INSERT INTO log_table VALUES ('start');
  
  MERGE INTO target_table ... ;
  
  INSERT INTO log_table VALUES ('end');
  
  RETURN 'Success';
END;
$$;

-- 2. Taskからは、そのプロシージャをCALLするだけ
CREATE TASK my_task
  WAREHOUSE = my_wh
  SCHEDULE = '5 MINUTES'
AS
  CALL my_etl_procedure(); -- これでOK

【設計方針】

  • Taskは「いつ実行するか(トリガー)」だけ を定義する。
  • Procedureは「何を実行するか(ロジック)」 を定義する。
  • このように責務を分離することで、Taskの定義はシンプルに保たれ、複雑なロジックはProcedure側で一元管理・テストできます。

2. 依存関係(DAG):「タスクグラフ」の2大制約

Taskは AFTER 句を使って、他のTaskの完了をトリガーに実行する「タスクグラフ(DAG: 有向非巡回グラフ)」を組むことができます。このDAGには厳格な制約があります。

-- NG: スキーマをまたいだ依存関係
CREATE TASK task_d
  WAREHOUSE = my_wh
  AFTER my_db.another_schema.task_a -- ERROR!
AS
  -- (SQL...)
;

-- NG: 所有者が異なるタスク間の依存関係
-- (task_b の OWNER が ETL_ROLE の場合)
GRANT OWNERSHIP ON TASK task_b TO ROLE ETL_ROLE;

-- (task_c を DEV_ROLE で作成しようとすると...)
USE ROLE DEV_ROLE;
CREATE TASK task_c
  WAREHOUSE = my_wh
  AFTER my_db.my_schema.task_b -- ERROR! (Ownerが異なるため)
AS
  -- (SQL...)
;

【設計方針】

  • 一連のパイプラインをTaskで構築する場合、それらの Taskはすべて単一のスキーマ(例: TASK_SCHEMA に集約します。
  • さらに、それらすべてのTaskの OWNERSHIPを、単一のサービスロール(例: TASK_OWNER_ROLE)に統一 します。

3. 最重要:「誰の権限で動くか?」 (OWNERSHIP と 必須グローバル権限)

これがTask運用で最も多い失敗原因 です。

実行ロールの決定

Taskは、スケジュールされた時刻になると、そのTaskの OWNERSHIP (所有者) が持つロールの権限 で実行されます。
EXECUTE AS USER を指定した場合を除く)

必須のグローバル権限

Taskを実行するには、Taskの所有者ロールに対して、アカウントレベルのグローバル権限 が必要です。

  • EXECUTE TASK ON ACCOUNT: すべてのTask(ユーザー管理WH、サーバーレス両方)で必須 です。
  • EXECUTE MANAGED TASK ON ACCOUNT: サーバーレスTaskを実行する場合に追加で必須 です。(ユーザー管理WHのTaskでは不要)

【設計方針】

  • 専用のサービスロールを作成する: Taskを所有するためだけの専用ロール(例: TASK_OWNER_ROLE)を作成します。
  • 権限の集約: この TASK_OWNER_ROLE に対して、以下の権限を集約させます。
    1. GRANT EXECUTE TASK ON ACCOUNT TO ROLE TASK_OWNER_ROLE; (必須)
    2. (サーバーレスTaskを使う場合)GRANT EXECUTE MANAGED TASK ON ACCOUNT TO ROLE TASK_OWNER_ROLE;
    3. パイプライン実行に必要なすべてのオブジェクト権限(WHへのUSAGE、DB/SchemaへのUSAGE、テーブルへのSELECT/INSERT/MERGE等)
  • 所有権の移譲: 開発者がテスト用に作成したTaskを本番化する際は、必ず所有権をこのサービスロールに移譲します。
  • (補足) EXECUTE AS USER <user_name> を指定して、Taskを特定のユーザー権限で実行することも可能です。この場合、Taskの所有者ロールは、対象ユーザーに対する IMPERSONATE 権限が必要になります。
-- 1. サービスロールに必要な権限を付与
GRANT EXECUTE TASK ON ACCOUNT TO ROLE TASK_OWNER_ROLE;
GRANT EXECUTE MANAGED TASK ON ACCOUNT TO ROLE TASK_OWNER_ROLE; -- サーバーレスTask用
GRANT USAGE ON WAREHOUSE my_wh TO ROLE TASK_OWNER_ROLE;
GRANT USAGE ON DATABASE my_db TO ROLE TASK_OWNER_ROLE;
-- ... (その他必要な権限) ...

-- 2. 本番化の際、所有権をサービスロールに移譲
GRANT OWNERSHIP ON TASK my_task TO ROLE TASK_OWNER_ROLE;

4. コンピュート戦略:Serverless vs. ユーザーWH

TaskはSQLを実行するためにコンピューティングリソース(Warehouse)を必要とします。

A. ユーザー管理Warehouse (従来型)

WAREHOUSE = my_wh と、既存のWHを指定する方法です。

  • メリット:
    • コスト管理がWHに集約される。
    • 負荷が予測可能で大規模なバッチ処理に向いている。
  • デメリット:
    • Task実行のためにWHが RESUME する際の待機時間(数秒〜)が発生する。
    • Taskが短時間で終わる場合、WHの最低課金時間(60秒) が非効率になる。
    • 他のクエリとWHを共有すると、リソースの競合が発生する。

B. サーバーレスTask (推奨)

Snowflakeが管理するコンピュートリソース(サーバーレス)でTaskを実行する方法です。

設定方法:
WAREHOUSE を指定しない ことで、自動的にサーバーレスモデルが使用されます。必要に応じて、Snowflakeが使用するコンピュートの最小/最大サイズをパラメータで指定できます。

-- Snowflake管理のコンピュートで実行 (サイズはSnowflakeが自動管理)
CREATE TASK my_serverless_task
  -- WAREHOUSE = ... を指定しない
  SCHEDULE = '5 MINUTES'
AS
  CALL my_etl_procedure();

-- サイズのヒントを指定する場合
ALTER TASK my_serverless_task SET
  SERVERLESS_TASK_MIN_STATEMENT_SIZE = 'SMALL'
  SERVERLESS_TASK_MAX_STATEMENT_SIZE = 'MEDIUM';

メリット:

  • ウェアハウス管理が不要。RESUME/SUSPEND を気にする必要がない。
  • 実使用量(per-second)に応じた課金 となり、ユーザー管理WHのような「起動ごとの60秒最低課金」の影響を受けません。短時間タスクとの相性が抜群です。
  • 他のワークロードとリソースが競合しない。

デメリット:

  • コストがTask実行(サーバーレスクレジット)として別途発生するため、監視が必要。

【設計方針】

  • 短時間・高頻度・散発的なTask(例:5分ごとにStreamをチェック)には、サーバーレスTaskが最適です。
  • 長時間・低頻度・大規模なTask(例:深夜のDWH構築バッチ)には、ユーザー管理WH(例: Lサイズのバッチ専用WH)が適している場合があります。

5. 運用と監視:失敗の検知と再実行

Taskは自動で実行されますが、「自動で失敗」もします。

Taskの「RESUME」忘れ

CREATE TASK しただけではTaskは Suspended(一時停止)状態です。必ず RESUME しないと実行されません

-- 作成しただけでは動かない
CREATE TASK my_task ... ;

-- タスクを再開する必要がある
ALTER TASK my_task RESUME;

-- DAGの場合は「ルートタスク」をRESUMEする必要があります
ALTER TASK root_task RESUME;

実行履歴と状態の確認

Taskの実行状況は TASK_HISTORY (実行履歴)、現在の状態は SHOW TASKS で確認します。STATE カラム(SUCCEEDED, FAILED, SKIPPED, CANCELLED)を監視することが重要です。

-- 現在のタスクの状態 (Suspended, Startedなど) を確認
SHOW TASKS LIKE 'MY_TASK%' IN SCHEMA my_db.my_schema;

-- 失敗したタスクの履歴を直近1日で確認
SELECT *
FROM TABLE(SNOWFLAKE.INFORMATION_SCHEMA.TASK_HISTORY(
    SCHEDULED_TIME_RANGE_START => DATEADD('day', -1, CURRENT_TIMESTAMP())
))
WHERE STATE = 'FAILED'
ORDER BY SCHEDULED_TIME DESC;

エラー通知と自動サスペンド

  • 通知: Taskが失敗したときに能動的に検知するため、ERROR_INTEGRATION パラメータに通知統合(Notification Integration) を設定できます。これにより、Amazon SNS, Azure Event Grid, または Google Pub/Sub へ通知が送信されます。(そこからEmail/Slack等へ連携するには、クラウドプロバイダー側での追加設定が必要です)。
    • (補足: 同様に SUCCESS_INTEGRATION で成功通知も設定可能です。)
  • 自動停止: SUSPEND_TASK_AFTER_NUM_FAILURES = N を設定すると、N回連続で失敗したTaskを自動的に Suspended 状態にし、無駄なクレジット消費を防げます(デフォルトは10です)。
  • 自動リトライ: TASK_AUTO_RETRY_ATTEMPTS パラメータ(デフォルト0)をルートタスク に設定することで、失敗した子タスクの自動リトライ回数を指定できます。

実行の重複制御

ALLOW_OVERLAPPING_EXECUTION = FALSE(デフォルト)の場合、あるTaskが実行中に次のスケジュール時刻が来ても、新しい実行は SKIPPED(スキップ)されます。バッチ処理が想定より長引いた場合の安全装置として機能します。

😌 おわりに

Snowflake Taskは強力な自動化ツールですが、その特性を理解して設計することが安定運用の鍵となります。
以下の設計方針を守り、データパイプラインを構築していくようにしましょう。

  • ロジックは Stored Procedure にカプセル化する(推奨)。
  • Taskグラフは同一スキーマ & 同一オーナーで統一する。
  • 所有権 (OWNERSHIP) は専用のサービスロールに集約し、そのロールに EXECUTE TASK権限 (および必要ならEXECUTE MANAGED TASK権限)を付与する。
  • Serverless Task を活用し、短時間タスクのWH管理を効率化する。
  • TASK_HISTORY と通知で、失敗を確実に検知する。

📚 参考出典

Discussion