❄️

Frosty Friday Live Challenge@SnowVillage 第40回参加レポート

に公開

先日、Frosty Friday Live Challenge@SnowVillageの第40回に参加させていただきました!

前回の参加から少し間が空きましたが、今回も素晴らしいホストメンバー、参加者の皆さんと一緒に楽しい時間を過ごさせていただきました!

第40回のホスト役は、tomoさん、がくさん、あれさん、そしてゲストは、みゆうさんと私の5人です。

そんな収録を元にした配信内容はこちらから!

https://www.youtube.com/watch?v=EDqQKQmhHfA

さて、この記事ではFrosty Friday Live Challengeの紹介と私が担当した、Frosty-Friday Week_081の回答解説をお伝えしようと思います。

先に配信を観ても良いですし、こちらの記事をそのまま読み進んでいただいても構いません!


Frosty Friday Live Challenge @SnowVillageとは

ご存じの方も多いと思いますが、番組の紹介をさせていただきます!

Frosty Friday Live Challengeは、Snowflakeユーザーコミュニティ「SnowVillage」が運営する、エンジニア向けのスキルアップ配信チャレンジ番組です!

Frosty Fridayが提供する、Snowflakeの実践的な課題に隔週で挑戦し、SQLやPython、Snowpark、Streamlitなどの最新機能を駆使した課題解決や、Snowflakeの最新機能やコミュニティ内外での新しい事例や記事などの紹介を行う、皆さんの

Snowflake活用をレベルアップしてくれる超お役立ちチャンネルです!

特に今回のWeek 81は、実務で必ず必要になるタスクDAGの構築という、非常に実践的なテーマでした。


Frosty-Friday Week_081の解説

話変わって、ここからは私がこの収録で担当したFrosty Fridayチャレンジは、WEEK_081の

タスクDAG構築チャレンジ: データパイプラインを設計せよ というテーマとその回答の解説を行っていきたいと思います!

このセッションでは、生データ(顧客、製品、売上)を段階的に投入し、最終的に集約ビューを作成するという、実務でよくあるデータパイプラインの構築が求められています。

「タスクを使ったデータパイプライン」というのは、本番運用では必須の技術なのですが、

「どう設計するのが正解なのか」「本番運用で気をつけることは何か」という点が明確でないことも多いのです。

このWeek 81では、公式解答を実装した後に、実運用で発生する問題点を体験し、それを段階的に改善していくという、非常に学びの多い構成となっています。

それでは、順を追って解説していきます!


Week 81の課題概要

課題の目的

複数のデータソース(顧客、製品、売上)を正しい順序で投入し、
最終的に集約レポートを作成するタスクDAGの構築

求められる成果物

  1. テーブル作成:3つの生データテーブル(製品、顧客、売上)
  2. タスクDAG:5つのタスクを使った依存関係のあるパイプライン
  3. 新規データ投入:製品2件、顧客2件、売上5件を追加
  4. 集約ビュー:顧客名、製品名、数量、売上金額を表示

注: 記事作成時に一時的に公式のstarter_codeが利用できなかったため、この記事ではテーブル定義と初期データを独自に準備しています。

タスク構造(公式解答)

INSERT_INTO_RAW (ルートタスク、手動実行)
  ├─ INSERT_INTO_CUSTOMERS
  ├─ INSERT_INTO_PRODUCT
  └─ INSERT_INTO_SALES
       └─ AGGREGATE_SALES

一見シンプルな構造ですが、この構造には実運用で問題となる要素が隠れています。


公式解答の実装

まずは、公式解答通りに実装してみます。

初期セットアップ

USE ROLE ACCOUNTADMIN;
USE DATABASE FROSTY_FRIDAY;
USE SCHEMA WEEK_081;

CREATE WAREHOUSE IF NOT EXISTS week81_wh
  WAREHOUSE_SIZE = 'XSMALL'
  AUTO_SUSPEND = 60
  AUTO_RESUME = TRUE;

-- 初期セットアップSQL
CREATE STAGE frosty_aws_stage
  URL = 's3://frostyfridaychallenges/';

EXECUTE IMMEDIATE FROM @frosty_aws_stage/challenge_81/starter_code.sql;  

ちなみに収録日当日、このS3に全員アクセス出来なくなり、色々焦りましたが、手動でテーブル作成などを行い、臨機応変に対応しました笑

タスクの作成(公式解答: INSERT使用)

-- Task 1: ルートタスク
CREATE OR REPLACE TASK INSERT_INTO_RAW
  WAREHOUSE = week81_wh
AS
  SELECT 'Raw data processing initiated' as status;

-- Task 2: 顧客データ投入(INSERT使用)
CREATE OR REPLACE TASK INSERT_INTO_CUSTOMERS
  WAREHOUSE = week81_wh
  AFTER INSERT_INTO_RAW
AS
  INSERT INTO w81_raw_customer (data)
  SELECT parse_json(column1) FROM VALUES
  ('{"customer_id": 6, "customer_name": "Frank"}'),
  ('{"customer_id": 7, "customer_name": "Grace"}');

-- Task 3-4: 製品・売上データ投入(同様にINSERT)
-- Task 5: 集約処理(AGGREGATE_SALES)

実行と結果確認

-- タスク有効化
ALTER TASK AGGREGATE_SALES RESUME;
ALTER TASK INSERT_INTO_SALES RESUME;
ALTER TASK INSERT_INTO_PRODUCT RESUME;
ALTER TASK INSERT_INTO_CUSTOMERS RESUME;

-- 実行
EXECUTE TASK INSERT_INTO_RAW;
SELECT SYSTEM$WAIT(10, 'SECONDS');

-- 結果確認
SELECT * FROM aggregated_sales ORDER BY customer_name, product_name;

出力結果

CUSTOMER_NAME PRODUCT_NAME TOTAL_QUANTITY TOTAL_REVENUE
Frank Product U 4 483.96
Grace Product V 2 70.00
  • Frank(customer_id 6)が Product U(product_id 21)を合計4個購入
  • Grace(customer_id 7)が Product V(product_id 22)を合計2個購入

期待通り5行の結果が得られました!

と、これで完了なのですが、この問題通りのタスクを実装すると色々ムズムズしてきました。

さて、皆さん、これ複数回実行した場合、どうなるでしょうか?
...


問題点:べき等性の欠如

公式解答を2回実行してみます。

-- 2回目実行
EXECUTE TASK INSERT_INTO_RAW;
SELECT SYSTEM$WAIT(10, 'SECONDS');

-- データ件数確認
SELECT 
  (SELECT COUNT(*) FROM w81_raw_customer WHERE data:customer_id::NUMBER IN (6,7)) as customers,
  (SELECT COUNT(*) FROM w81_raw_product WHERE data:product_id::NUMBER IN (21,22)) as products,
  (SELECT COUNT(*) FROM w81_raw_sales WHERE data:sale_id::NUMBER BETWEEN 11 AND 15) as sales;

結果

customers: 4件(期待は2件)
products: 4件(期待は2件)
sales: 10件(期待は5件)

データが重複してしまいました!

単純Insertの処理方式なので、複数回実行するだけ重複していきます。
まぁ、タスクを理解するのが目的なので、これでも良いのですが、せっかくなので改めて実装をちゃんとしたくなりました。

問題1:INSERT文による重複

-- 1回目
INSERT INTO table VALUES (6, 'Frank');  -- 1件追加

-- 2回目(リトライ時)
INSERT INTO table VALUES (6, 'Frank');  -- また1件追加

-- 結果:Frankが2件存在

問題2:単一親タスクによる整合性リスク

また公式解答では、AGGREGATE_SALESがINSERT_INTO_CUSTOMERSの完了のみを待っています。

INSERT_INTO_CUSTOMERS (3秒) → AGGREGATE_SALES 開始
INSERT_INTO_PRODUCT (10秒)  ← まだ実行中
INSERT_INTO_SALES (15秒)    ← まだ実行中

タイミングによっては、製品・売上データが不完全な状態で集約が始まってしまいます。

ジョブフローで連続して後続処理が開始する場合、問題になる可能性大です。


改善策の検討:運用強化版の実装

「ではどう改善すべきか?」という訳で、収録では2つの改善アプローチを提示しました。

改善点1:INSERT → MERGE

INSERT文の問題

  • 無条件でデータ追加
  • 主キーチェックなし
  • べき等性なし

MERGE文の解決

  • 既存データをチェック
  • 存在しない場合のみ追加
  • べき等性あり
-- MERGE文による改善
CREATE OR REPLACE TASK INSERT_INTO_CUSTOMERS
  WAREHOUSE = week81_wh
  AFTER INSERT_INTO_RAW
AS
  MERGE INTO w81_raw_customer t
  USING (
    SELECT parse_json(column1) as data FROM VALUES
    ('{"customer_id": 6, "customer_name": "Frank"}'),
    ('{"customer_id": 7, "customer_name": "Grace"}')
  ) s
  ON t.data:customer_id::NUMBER = s.data:customer_id::NUMBER
  WHEN NOT MATCHED THEN INSERT (data) VALUES (s.data);

改善点2:複数親タスクによる整合性保証

データ検証タスク(VALIDATE_DATA)を追加し、3つのデータ投入タスクすべての完了を待つ構造に変更します。

-- Task 5: データ検証(複数親タスク)
CREATE OR REPLACE TASK VALIDATE_DATA
  WAREHOUSE = week81_wh
  AFTER INSERT_INTO_CUSTOMERS, INSERT_INTO_PRODUCT, INSERT_INTO_SALES
AS
  CREATE OR REPLACE TRANSIENT TABLE data_validation_log AS
  SELECT 
    CURRENT_TIMESTAMP() as validation_time,
    (SELECT COUNT(*) FROM w81_raw_customer WHERE data:customer_id::NUMBER IN (6,7)) as new_customers,
    (SELECT COUNT(*) FROM w81_raw_product WHERE data:product_id::NUMBER IN (21,22)) as new_products,
    (SELECT COUNT(*) FROM w81_raw_sales WHERE data:sale_id::NUMBER BETWEEN 11 AND 15) as new_sales,
    CASE 
      WHEN ... THEN 'PASSED'
      ELSE 'FAILED'
    END as validation_status;

-- Task 6: 集約処理(VALIDATE_DATA完了後)
CREATE OR REPLACE TASK AGGREGATE_SALES
  WAREHOUSE = week81_wh
  AFTER VALIDATE_DATA
AS
  CREATE OR REPLACE VIEW aggregated_sales AS ...

改善後のタスク構造

INSERT_INTO_RAW
  ├─ INSERT_INTO_CUSTOMERS ┐
  ├─ INSERT_INTO_PRODUCT   ├─→ VALIDATE_DATA → AGGREGATE_SALES
  └─ INSERT_INTO_SALES     ┘

改善効果の確認

-- 2回実行してもデータ重複なし
SELECT 
  (SELECT COUNT(*) FROM w81_raw_customer WHERE data:customer_id::NUMBER IN (6,7)) as customers,
  (SELECT COUNT(*) FROM w81_raw_product WHERE data:product_id::NUMBER IN (21,22)) as products,
  (SELECT COUNT(*) FROM w81_raw_sales WHERE data:sale_id::NUMBER BETWEEN 11 AND 15) as sales;

-- 結果:常に customers=2, products=2, sales=5

この結果を見て、収録中も「おお!」という反応が。


さらなる最適化:Serverless版の実装

ここまでで運用レベルの実装は完成しましたが、この問題が当時発表された以降、Snowflakeの進化は著しく、最近になってServerlessタスクというものが登場しています。

Serverlessタスクとは

2025年現在、Snowflakeでは小規模タスクのコンピュートリソースをSnowflakeが自動管理する方式が標準となって来ています。

従来方式(固定ウェアハウス)

CREATE TASK task1
  WAREHOUSE = week81_wh  -- 明示的に指定
  ...
  • ウェアハウス管理が必要
  • アイドル時間も課金対象
  • サイズを手動調整

Serverless方式(2025年標準)

CREATE TASK task1
  USER_TASK_MANAGED_INITIAL_WAREHOUSE_SIZE = 'XSMALL'
  ...
  • ウェアハウス管理不要
  • 実行時間のみ課金
  • 自動スケーリング

Serverless版の実装

-- ウェアハウス指定を削除し、Serverless指定に変更
CREATE OR REPLACE TASK INSERT_INTO_RAW
  USER_TASK_MANAGED_INITIAL_WAREHOUSE_SIZE = 'XSMALL'
AS
  SELECT 'Raw data processing initiated' as status;

-- 他のタスクも同様に変更
CREATE OR REPLACE TASK INSERT_INTO_CUSTOMERS
  USER_TASK_MANAGED_INITIAL_WAREHOUSE_SIZE = 'XSMALL'
  AFTER INSERT_INTO_RAW
AS
  MERGE INTO w81_raw_customer t ...

Serverless版の確認

-- タスク状態確認
SHOW TASKS IN FROSTY_FRIDAY.WEEK_081;

SELECT 
  "name" as task_name,
  "warehouse" as warehouse_setting
FROM TABLE(RESULT_SCAN(LAST_QUERY_ID()));

-- 期待結果:warehouse_setting = NULL(Serverlessの証拠)

Serverless適用の判断基準

条件 Serverless推奨 固定WH推奨
実行時間 1~5分以内 10分以上
即時性 不要(5~30秒待機OK) 必須
並列数 20個未満 50個以上
実行頻度 散発的(日次・週次) 常時(分単位)

Week 81のケース:

  • 実行時間: 各2~5秒(合計20秒)✅
  • 即時性: 不要(スケジュール実行)✅
  • 並列数: 5タスク ✅
  • 実行頻度: 日次想定 ✅

→ Serverlessが最適


3つのアプローチの比較

収録では、3つの実装方法を実際に動かしながら比較しました。

項目 公式解答 運用強化版 Serverless版
学習目的 ✅ 最適 ○ 良 ○ 良
本番運用 △ 要改善 ✅ 適 ✅ 最適
べき等性 ❌ なし ✅ あり ✅ あり
整合性保証 △ 単一親 ✅ 複数親 ✅ 複数親
コスト効率 ○ 標準 ○ 標準 ✅ 最適
運用工数 中 中 ✅ 低

という訳で、もはや問題そのものから、だいぶ話も逸れてきましたが、タスクの商用実装における実装パターンを説明させていただきました!

Week 81を拡張して学べる重要概念

1. べき等性(Idempotency)

定義:同じ操作を何度実行しても結果が変わらない性質

重要性:

  • スケジュール実行での安全性
  • リトライ時の一貫性
  • 運用ミスの防止

実装方法:

  • INSERT → MERGE
  • 主キーによる重複チェック
  • WHEN NOT MATCHED 条件

2. データ整合性(Data Consistency)

定義:関連するすべてのデータが正しく揃っている状態

重要性:

  • 並列処理の同期
  • トランザクション的一貫性
  • ビジネスロジックの正確性

実装方法:

  • 複数親タスク(CONVERGENCE)
  • データ検証タスク
  • ログによる追跡

3. コスト最適化

重要性:

  • 継続的な運用コスト
  • スケーラビリティ
  • ROIの向上

実装方法:

  • Serverlessタスク
  • 自動リソース管理
  • 実行時間課金

まとめ

元々のお題はシンプルなタスクDAGの構築でしたが、収録を通じて以下の3段階での実装を体験しました:

Step 1:公式解答

  • タスクDAGの基本を理解
  • 問題点を実際に体験

Step 2:運用強化版

  • MERGE文で重複防止
  • 複数親タスクで整合性保証
  • 本番運用レベルの実装

Step 3:Serverless版

  • 最新技術の活用
  • コスト最適化
  • 2025年の推奨実装

これは公式解答が間違っているという指摘ではなく、実運用を見据えた段階的な改善の重要性を学ぶことができる、非常に良い教材だと感じました。

Week 81を通じて、単にタスクを作るだけでなく、なぜその実装が必要なのか、どう改善すべきかを体系的に理解できました。

収録では、他のホストメンバーの実務経験も聞けて、より深い学びになりました。収録時には、それ以外にも先日書いた記事のお話もさせてもらったり、様々な知見を共有しながら、みんなで楽しい時間を過ごさせていただきました!

https://zenn.dev/dataheroes/articles/5a8d647f7a9103

タイミングがあえば、またいつか収録に参加したいと思いました!

興味をもっていただいた皆さんもぜひゲスト参加にチャレンジしてみてください!


SnowVillageについて

さて、このような記事を書くきっかけとなったのは、SnowflakeのユーザーコミュニティであるSnowVillageやFrosty Fridayを運営しているコミュニティメンバーとの出会いがあったからです。

コミュニティに参加するだけで様々な知見や学びを得られますし、イベントに参加することでより実践的で深い技術やノウハウを学ぶことが出来ます。それらを通じてデータエンジニアとして成長する機会も大きく増えると思いますので、少しでも興味を持った方はぜひご参加ください。

またいきなりコミュニティに入るのは・・という方もまずはSnowVillageが運営するYoutubeチャネルでアーカイブを観たり、配信を視聴してはいかがでしょうか?最新のアップデートや技術ニュースなど様々なことを楽しく学べると思います!

SnowVillage
https://usergroups.snowflake.com/snowvillage/

SnowVillageチャネル &Frosty Friday Live Challenge
https://www.youtube.com/@snowvillage

Snowflakeコミュニティイベント
https://techplay.jp/community_group/snowflake_users

Discussion