Snowflake MigrationClickHouse Workshops

Snowflake의 Superset

소스 측 운영 대시보드 세 개 만들기: 연결, 데이터셋, 차트, 조립.

이 가이드는 http://localhost:8088 (admin / admin)의 Apache Superset에서 세 개의 대시보드를 모두 만드는 과정을 안내합니다.

작업 순서:

  1. Superset을 시작하고 Snowflake 연결을 등록합니다
  2. 모든 데이터셋을 생성합니다(이름이 지정된 데이터셋으로 저장된 SQL 쿼리)
  3. 차트를 만들고 대시보드를 조립합니다

1단계: Superset 시작하고 Snowflake에 연결하기

Superset 시작

Superset 이미지는 Snowflake와 ClickHouse 드라이버를 포함하도록 커스터마이즈되어 있습니다. 첫 실행 시에는 Docker가 이미지를 빌드하도록 --build를 사용하세요:

cd "$(git rev-parse --show-toplevel)/workshop_public/snowflake_migration_lab/01-setup-snowflake/superset"
source ../.env
docker compose up -d --build

데이터베이스 연결 등록 (자동)

superset/ 디렉터리에서 다음을 실행하세요:

source ../.env && ./init_superset.sh

스크립트는 Superset이 준비될 때까지 기다린 다음 NYC Taxi — Snowflake (Source)를 자동으로 등록합니다. 예상 출력:

>>> Superset is up.
>>> Authenticated.
>>> CSRF token obtained.
>>> Registering: NYC Taxi — Snowflake (Source)
    Registered successfully.

데이터베이스 연결 등록 (수동 대안)

UI를 통해 등록하고 싶다면:

  1. Settings → Database Connections → + Database로 이동합니다
  2. Snowflake를 선택합니다
  3. SQLAlchemy URI를 입력합니다 — 비밀번호에 있는 특수 문자는 URL 인코딩하세요(# → %23, ! → %21, @ → %40 등):
snowflake://<USER>:<URL_ENCODED_PASSWORD>@<SNOWFLAKE_ORG>-<SNOWFLAKE_ACCOUNT>/NYC_TAXI_DB/ANALYTICS?warehouse=ANALYTICS_WH&role=ANALYST_ROLE
  1. Display Name 설정: NYC Taxi — Snowflake (Source)
  2. Advanced → SQL Lab에서: Allow this database to be explored와 Allow DML을 활성화합니다
  3. Test Connection을 클릭 → "Connection looks good!"이 표시되어야 합니다
  4. Connect를 클릭합니다

2단계: 모든 데이터셋 생성

모든 차트는 가상 데이터셋을 사용합니다 — 이름이 지정된 데이터셋으로 저장된 SQL 쿼리입니다.

각 데이터셋 생성 방법:

  1. Datasets → + Dataset으로 이동합니다
  2. 데이터베이스 선택: NYC Taxi — Snowflake (Source)
  3. Create dataset from SQL query를 클릭하고 아래 SQL을 붙여넣습니다
  4. 표시된 이름으로 저장합니다
  5. 저장 후: Datasets → 연필 아이콘 → Columns 탭 → "Sync columns from source" → Save로 이동합니다. 이 단계를 건너뛰면 차트 빌더에 컬럼이 0개로 표시됩니다.

대시보드 1 데이터셋

ops_hourly_revenue — 자치구별 시간당 매출

스키마: ANALYTICS

SELECT
    DATE_TRUNC('hour', pickup_at)                      AS hour_bucket,
    pickup_borough,
    COUNT(*)                                           AS trip_count,
    SUM(total_amount_usd)                              AS total_revenue,
    AVG(tip_amount_usd / NULLIF(fare_amount_usd, 0))  AS avg_tip_rate,
    AVG(trip_distance_miles)                           AS avg_distance_miles
FROM ANALYTICS.FACT_TRIPS
WHERE pickup_at >= DATEADD('day', -7, CURRENT_TIMESTAMP())
  AND pickup_borough IS NOT NULL
GROUP BY 1, 2
ORDER BY 1 DESC, total_revenue DESC

ops_zone_agg — 존 집계

스키마: ANALYTICS

SELECT
    hour_bucket,
    zone_id,
    trips,
    revenue,
    avg_distance
FROM ANALYTICS.AGG_HOURLY_ZONE_TRIPS
WHERE hour_bucket >= DATEADD('day', -7, CURRENT_TIMESTAMP())

ops_payment_split — 결제 유형 분포

스키마: ANALYTICS

SELECT
    payment_type,
    COUNT(*)              AS trip_count,
    SUM(total_amount_usd) AS total_revenue
FROM ANALYTICS.FACT_TRIPS
WHERE pickup_at >= DATEADD('day', -7, CURRENT_TIMESTAMP())
GROUP BY 1

대시보드 2 데이터셋

exec_rolling_avg — 롤링 7일 평균

스키마: ANALYTICS

SELECT
    pickup_at::DATE                                          AS trip_date,
    COUNT(*)                                                 AS daily_trip_count,
    AVG(trip_distance_miles)                                 AS daily_avg_distance,
    AVG(AVG(trip_distance_miles)) OVER (
        ORDER BY pickup_at::DATE
        ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
    )                                                        AS rolling_7d_avg_distance,
    SUM(total_amount_usd)                                    AS daily_revenue,
    SUM(SUM(total_amount_usd)) OVER (
        ORDER BY pickup_at::DATE
        ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
    )                                                        AS rolling_7d_revenue
FROM ANALYTICS.FACT_TRIPS
GROUP BY 1
ORDER BY 1 DESC
LIMIT 365

exec_top_trips — 자치구별 최고 금액 트립 상위 10건

스키마: ANALYTICS

Snowflake의 QUALIFY를 사용합니다 — 핵심 마이그레이션 과제입니다. ClickHouse로 다시 작성할 때는 서브쿼리가 필요합니다.

SELECT
    trip_id,
    pickup_at,
    pickup_borough,
    total_amount_usd,
    tip_amount_usd,
    trip_distance_miles,
    ROW_NUMBER() OVER (
        PARTITION BY pickup_borough
        ORDER BY total_amount_usd DESC
    ) AS rank_in_borough
FROM ANALYTICS.FACT_TRIPS
WHERE pickup_at::DATE = CURRENT_DATE() - 1
QUALIFY rank_in_borough <= 10
ORDER BY pickup_borough, rank_in_borough

exec_surge — 서지 요금 영향

스키마: ANALYTICS

SELECT
    CASE
        WHEN surge_multiplier >= 2.0 THEN 'High Surge (2x+)'
        WHEN surge_multiplier >= 1.5 THEN 'Medium Surge (1.5–2x)'
        WHEN surge_multiplier > 1.0  THEN 'Low Surge (1–1.5x)'
        ELSE 'No Surge (1x)'
    END                                    AS surge_category,
    COUNT(*)                               AS trip_count,
    ROUND(AVG(total_amount_usd), 2)        AS avg_total_fare,
    ROUND(AVG(fare_amount_usd), 2)         AS avg_base_fare,
    ROUND(AVG(surge_multiplier), 2)        AS avg_surge
FROM ANALYTICS.FACT_TRIPS
WHERE surge_multiplier IS NOT NULL
GROUP BY 1
ORDER BY avg_surge DESC

대시보드 3 데이터셋

dqa_rating_dist — 기사 평점 분포

스키마: RAW ← 데이터셋을 생성할 때 이 값을 변경하세요

VARIANT 콜론 경로 문법을 통해 RAW.TRIPS_RAW를 직접 쿼리합니다. 이것이 의도적으로 느린 쿼리이며 — ClickHouse 벤치마크의 대상입니다.

SELECT
    ROUND(TRIP_METADATA:driver.rating::FLOAT, 1)                           AS rating_bucket,
    COUNT(*)                                                               AS trip_count,
    ROUND(AVG(TOTAL_AMOUNT), 2)                                            AS avg_fare,
    ROUND(AVG(DATEDIFF('minute', PICKUP_DATETIME, DROPOFF_DATETIME)), 1)   AS avg_duration_minutes
FROM RAW.TRIPS_RAW
WHERE TRIP_METADATA:driver IS NOT NULL
  AND TRIP_METADATA:driver.rating IS NOT NULL
GROUP BY 1
ORDER BY 1

dqa_vehicle — 차량 유형별 매출

스키마: ANALYTICS

SELECT
    vehicle_type,
    COUNT(*)                 AS trip_count,
    SUM(total_amount_usd)    AS total_revenue,
    AVG(total_amount_usd)    AS avg_fare,
    AVG(trip_distance_miles) AS avg_distance
FROM ANALYTICS.FACT_TRIPS
WHERE vehicle_type IS NOT NULL
GROUP BY 1
ORDER BY total_revenue DESC

dqa_traffic — 교통 혼잡도 대비 트립 소요 시간

스키마: ANALYTICS

SELECT
    traffic_level,
    COUNT(*)                   AS trip_count,
    AVG(duration_minutes)      AS avg_duration_minutes,
    AVG(trip_distance_miles)   AS avg_distance_miles,
    AVG(total_amount_usd)      AS avg_fare
FROM ANALYTICS.FACT_TRIPS
WHERE traffic_level IS NOT NULL
GROUP BY 1
ORDER BY avg_duration_minutes DESC

dqa_platform — 앱 플랫폼 추세

스키마: ANALYTICS

SELECT
    pickup_at::DATE       AS trip_date,
    app_platform,
    COUNT(*)              AS trip_count,
    AVG(surge_multiplier) AS avg_surge
FROM ANALYTICS.FACT_TRIPS
WHERE app_platform IS NOT NULL
  AND pickup_at >= DATEADD('day', -30, CURRENT_TIMESTAMP())
GROUP BY 1, 2
ORDER BY 1 DESC

3단계: 대시보드 만들기

이제 10개의 데이터셋이 모두 준비되었습니다. 차트를 만들어 대시보드에 추가하세요.


대시보드 1: Operations Command Center

목적: 최근 7일을 보여주는 실시간 운영 뷰. Part 2에서 파트너가 ClickHouse로 가장 먼저 전환하는 대시보드입니다.

대시보드 생성:

  1. Dashboards → + Dashboard
  2. 제목: Operations Command Center
  3. 자동 갱신: 15분마다 (··· → Edit dashboard → Auto-refresh)

차트 1: 시간당 트립 수 (선 차트)

  • Chart type: Line Chart
  • Dataset: ops_hourly_revenue
  • X-axis: hour_bucket
  • Metrics: SUM(trip_count)
  • Series: pickup_borough
  • Title: Trips per Hour — Last 7 Days

차트 2: 자치구별 매출 (막대 차트)

  • Chart type: Bar Chart
  • Dataset: ops_hourly_revenue
  • X-axis: pickup_borough
  • Metrics: SUM(total_revenue)
  • Sort: 지표 기준 내림차순
  • Title: Total Revenue by Borough — Last 7 Days

차트 3: 결제 유형 분포 (원형 차트)

  • Chart type: Pie Chart
  • Dataset: ops_payment_split
  • Dimension: payment_type
  • Metric: SUM(trip_count)
  • Show labels: 켜기
  • Title: Trip Count by Payment Type

차트 4: 총 트립 수 (Big Number)

  • Chart type: Big Number with Trendline
  • Dataset: ops_hourly_revenue
  • Metric: SUM(trip_count)
  • Title: Total Trips (Last 7 Days)

차트 5: 자치구 성과 요약 (표)

  • Chart type: Table
  • Dataset: ops_hourly_revenue
  • Columns: pickup_borough, SUM(trip_count), SUM(total_revenue), AVG(avg_tip_rate)
  • Row limit: 10
  • Sort: SUM(total_revenue) 내림차순
  • Title: Borough Performance Summary

레이아웃:

[ Total Trips — Big Number ]  [ Total Revenue — Big Number (add 2nd)  ]
[ Trips per Hour — Line chart (full width)                            ]
[ Revenue by Borough — Bar ]  [ Payment Type Split — Pie             ]
[ Borough Performance — Table (full width)                            ]

대시보드 2: Executive Weekly Report

목적: 주간 비즈니스 리뷰용 전략 뷰. ClickHouse에서 다시 작성해야 하는 윈도 함수와 Snowflake 고유 문법(QUALIFY)을 보여줍니다.

대시보드 생성:

  1. Dashboards → + Dashboard
  2. 제목: Executive Weekly Report
  3. 자동 갱신: 1시간

차트 6: 롤링 7일 매출 추세 (선 차트)

  • Chart type: Line Chart
  • Dataset: exec_rolling_avg
  • X-axis: trip_date
  • Metrics: MAX(daily_revenue), MAX(rolling_7d_revenue)
  • Title: Daily Revenue with 7-Day Rolling Average

차트 7a: 일일 트립 볼륨 (Big Number)

  • Chart type: Big Number with Trendline
  • Dataset: exec_rolling_avg
  • Metric: MAX(daily_trip_count)
  • Title: Daily Trip Volume (Last Year)

차트 7b: 롤링 7일 평균 거리 (선 차트)

  • Chart type: Line Chart
  • Dataset: exec_rolling_avg
  • X-axis: trip_date
  • Metrics: MAX(rolling_7d_avg_distance)
  • Title: Rolling 7-Day Average Distance (miles)

차트 8: 자치구별 상위 10개 트립 (표)

  • Chart type: Table
  • Dataset: exec_top_trips
  • Query Mode: RAW RECORDS ← 중요: 데이터셋이 QUALIFY를 사용하므로 Superset이 재집계하지 않아야 합니다
  • Columns: pickup_borough, rank_in_borough, total_amount_usd, tip_amount_usd, trip_distance_miles, pickup_at
  • Sort By: total_amount_usd 내림차순
  • Row limit: 60
  • Title: Top 10 Trips per Borough — Yesterday
  • 참고: QUALIFY를 사용합니다 — Snowflake 고유 문법이며, ClickHouse에서는 서브쿼리로 다시 작성해야 합니다

차트 9: 서지 요금 분해 (혼합 차트)

  • Chart type: Mixed Chart ← Bar Chart가 아니라 이것을 사용하세요; Bar Chart는 보조 축을 지원하지 않습니다
  • Dataset: exec_surge
  • X-axis: surge_category
  • Query A — Bar: 지표 SUM(trip_count), 레이블 Trip Count
  • Query B — Line: 지표 MAX(avg_total_fare), 레이블 Avg Total Fare, Y축: Right
  • Sort: SUM(trip_count) 내림차순
  • Title: Trip Volume and Average Fare by Surge Category

차트 10: 서지 분포 (원형 차트)

  • Chart type: Pie Chart
  • Dataset: exec_surge
  • Dimension: surge_category
  • Metric: SUM(trip_count)
  • Title: Surge Pricing Distribution

레이아웃:

[ Rolling Revenue — Line chart (full width)                              ]
[ Daily Trip Volume — Big Number (50%) ]  [ Avg Distance — Line (50%)   ]
[ Top 10 Trips — Table (60%) ]  [ Surge Distribution — Pie (40%)        ]
[ Surge Breakdown — Bar chart (full width)                               ]

대시보드 3: Driver & Quality Analytics

목적: 기사 성과와 트립 품질에 대한 심층 분석. 의도적으로 가장 느린 대시보드입니다 — VARIANT 접근을 통해 RAW.TRIPS_RAW를 직접 쿼리합니다. 여기서의 쿼리 시간을 Part 2의 ClickHouse 성능 벤치마크 기준선으로 기록하세요.

대시보드 생성:

  1. Dashboards → + Dashboard
  2. 제목: Driver & Quality Analytics
  3. 자동 갱신: 1시간

차트 11: 기사 평점별 트립 수 (막대 차트)

  • Chart type: Bar Chart
  • Dataset: dqa_rating_dist
  • X-axis: rating_bucket
  • Metrics: SUM(trip_count)
  • Title: Trip Count by Driver Rating
  • 참고: VARIANT 접근으로 RAW.TRIPS_RAW를 스캔합니다 — ClickHouse와의 쿼리 시간 차이를 관찰하세요

차트 12: 평점별 평균 요금 (선 차트)

  • Chart type: Line Chart
  • Dataset: dqa_rating_dist
  • X-axis: rating_bucket
  • Metrics: MAX(avg_fare)
  • Title: Average Fare by Driver Rating

차트 13: 차량 유형별 매출 (수평 막대)

  • Chart type: Bar Chart (horizontal)
  • Dataset: dqa_vehicle
  • X-axis: vehicle_type
  • Metrics: SUM(total_revenue), SUM(trip_count) (보조 축)
  • Title: Revenue and Trip Count by Vehicle Type

차트 14: 교통 혼잡도 영향 (막대 차트)

  • Chart type: Bar Chart
  • Dataset: dqa_traffic
  • X-axis: traffic_level
  • Metrics: MAX(avg_duration_minutes), MAX(avg_distance_miles) (보조 축)
  • Title: Average Trip Duration and Distance by Traffic Level

차트 15: 앱 플랫폼별 일일 트립 (선 차트)

  • Chart type: Line Chart
  • Dataset: dqa_platform
  • X-axis: trip_date
  • Metrics: SUM(trip_count)
  • Series: app_platform
  • Title: Daily Trips by App Platform — Last 30 Days

차트 16: 플랫폼별 서지 (표)

  • Chart type: Table
  • Dataset: dqa_platform
  • Columns: app_platform, SUM(trip_count), AVG(avg_surge)
  • Row limit: 10
  • Title: Surge by Platform

레이아웃:

[ Trip Count by Rating — Bar ]  [ Avg Fare by Rating — Line            ]
[ Revenue by Vehicle Type — Horizontal bar (full width)                ]
[ Traffic Level Impact — Bar (50%) ]  [ Surge by Platform — Table (50%)]
[ Daily Trips by Platform — Line chart (full width)                    ]

4단계: 검증

  1. 각 대시보드를 열어 모든 차트가 오류 없이 로드되는지 확인합니다
  2. 대시보드 3에서는 Snowflake UI → Activity → Query History에서 dqa_rating_dist 쿼리 실행 시간을 기록합니다 — 이것을 마이그레이션 벤치마크로 저장하세요

Snowflake 데이터셋 전체의 기사 평점 분포를 보여주는 Superset 차트로, 4.0에서 5.0 사이에 집중되어 있음

5단계: 재사용을 위한 내보내기

대시보드가 완성되면, 이후 실행에서 자동으로 가져올 수 있도록 내보내세요:

  1. 각 대시보드를 열고 → ··· → Export (.zip으로 저장됨)
  2. 파일을 superset/dashboards/에 배치합니다:
    • 01_operations_command_center.zip
    • 02_executive_weekly_report.zip
    • 03_driver_quality_analytics.zip
  3. ./init_superset.sh를 다시 실행하세요 — 이후 설정에서 자동으로 가져옵니다

중요 — 커밋된 ZIP 안의 플레이스홀더 자격 증명.

커밋된 각 *.zip은 databases/*.yaml에서 데이터베이스 연결이 마스킹되어 있습니다:

sqlalchemy_uri: snowflake://LAB_USER:XXXXXXXXXX@MYORG-MYACCOUNT/NYC_TAXI_DB/ANALYTICS?role=ANALYST_ROLE&warehouse=ANALYTICS_WH
  • 자동 가져오기(./init_superset.sh) — 그대로 작동합니다. 스크립트는 가져오기 전에 .env의 실제 Snowflake 연결을 등록하고, 각 가져오기 후에 올바른 URI를 다시 적용하므로(init_superset.sh의 _update_db 참고) 플레이스홀더 값이 실제 자격 증명으로 덮어써집니다.
  • Superset UI를 통한 수동 가져오기 — 가져온 데이터베이스는 플레이스홀더 URI로 생성되어 연결되지 않습니다. 가져온 후 Settings → Database Connections → Edit로 이동해 해당 항목의 sqlalchemy_uri를 실제 Snowflake URI로 교체하세요(예: snowflake://<USER>:<PASSWORD>@<ORG>-<ACCOUNT>/NYC_TAXI_DB/ANALYTICS?role=ANALYST_ROLE&warehouse=ANALYTICS_WH).
  • 직접 만든 대시보드를 다시 내보내기 — Superset은 내보낼 때 databases/*.yaml에 여러분의 계정 로케이터와 사용자명을 그대로 넣습니다. 다시 내보낸 ZIP을 커밋하기 전에 그 값들을 MYORG-MYACCOUNT / LAB_USER로 되돌려 마스킹해서 계정 식별자가 git 히스토리로 유출되지 않게 하세요.

이 페이지의 내용

1단계: Superset 시작하고 Snowflake에 연결하기Superset 시작데이터베이스 연결 등록 (자동)데이터베이스 연결 등록 (수동 대안)2단계: 모든 데이터셋 생성대시보드 1 데이터셋ops_hourly_revenue — 자치구별 시간당 매출ops_zone_agg — 존 집계ops_payment_split — 결제 유형 분포대시보드 2 데이터셋exec_rolling_avg — 롤링 7일 평균exec_top_trips — 자치구별 최고 금액 트립 상위 10건exec_surge — 서지 요금 영향대시보드 3 데이터셋dqa_rating_dist — 기사 평점 분포dqa_vehicle — 차량 유형별 매출dqa_traffic — 교통 혼잡도 대비 트립 소요 시간dqa_platform — 앱 플랫폼 추세3단계: 대시보드 만들기대시보드 1: Operations Command Center차트 1: 시간당 트립 수 (선 차트)차트 2: 자치구별 매출 (막대 차트)차트 3: 결제 유형 분포 (원형 차트)차트 4: 총 트립 수 (Big Number)차트 5: 자치구 성과 요약 (표)대시보드 2: Executive Weekly Report차트 6: 롤링 7일 매출 추세 (선 차트)차트 7a: 일일 트립 볼륨 (Big Number)차트 7b: 롤링 7일 평균 거리 (선 차트)차트 8: 자치구별 상위 10개 트립 (표)차트 9: 서지 요금 분해 (혼합 차트)차트 10: 서지 분포 (원형 차트)대시보드 3: Driver & Quality Analytics차트 11: 기사 평점별 트립 수 (막대 차트)차트 12: 평점별 평균 요금 (선 차트)차트 13: 차량 유형별 매출 (수평 막대)차트 14: 교통 혼잡도 영향 (막대 차트)차트 15: 앱 플랫폼별 일일 트립 (선 차트)차트 16: 플랫폼별 서지 (표)4단계: 검증5단계: 재사용을 위한 내보내기
KO