本文へ移動
cccskills
無料GitHub で公開

trino-airflow-lakehouse-pipelines

Airflow-orchestrated Iceberg lakehouse ETL pipelines — Bronze/Silver/Gold layer DAGs, Iceberg maintenance jobs (optimize/expire_snapshots/remove_orphan_files) as Airflow tasks, snapshot expiration scheduling, compaction DAG patterns, partition-by-partition backfill with dynamic task mapping, late-arriving data handling, watermark tracking table, post-load data quality gates (row count/freshness/null rate checks via TrinoHook), full medallion pipeline DAG example

日本語の概要は準備中です。原文の説明を表示しています。

ivanshamaev/de-agent-skillsスター 22(GitHub 22 + cccskills 0)

実行スコア 未検証

インストール方法を見る

含まれるファイル(1)

  • SKILL.md13.4 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

Airflow Lakehouse Pipelines (Iceberg + Trino)

When to Use

  • Building multi-layer Bronze→Silver→Gold ETL orchestrated by Airflow
  • Scheduling Iceberg maintenance (compaction, snapshot expiry, orphan cleanup)
  • Handling late-arriving data with reprocessing logic
  • Implementing data quality gates between pipeline stages
  • Backfilling historical partitions with Airflow dynamic task mapping

Full Medallion Pipeline DAG

from airflow.decorators import dag, task
from airflow.providers.trino.operators.trino import TrinoOperator
from airflow.providers.trino.hooks.trino import TrinoHook
from datetime import datetime, timedelta

CONN = 'trino_default'

@dag(
    dag_id            = 'lakehouse_medallion_pipeline',
    start_date        = datetime(2024, 1, 1),
    schedule_interval = '0 3 * * *',
    catchup           = True,
    max_active_runs   = 2,
    default_args      = {'retries': 2, 'retry_delay': timedelta(minutes=5)},
    tags              = ['lakehouse', 'medallion'],
)
def medallion_pipeline():

    # ── BRONZE: raw ingest quality gate ──────────────────────────────
    @task
    def check_bronze_freshness(ds: str, **ctx) -> int:
        hook = TrinoHook(trino_conn_id=CONN)
        count = hook.get_first(f"""
            SELECT COUNT(*) FROM iceberg.bronze.orders_raw
            WHERE ingested_date = DATE '{ds}'
        """)[0]
        if count == 0:
            raise ValueError(f"No bronze data for {ds}")
        return count

    # ── SILVER: cleaned/deduplicated ─────────────────────────────────
    load_silver = TrinoOperator(
        task_id       = 'load_silver_orders',
        trino_conn_id = CONN,
        sql = """
            MERGE INTO iceberg.silver.orders t
            USING (
                SELECT
                    order_id,
                    customer_id,
                    CAST(order_date AS DATE)          AS order_date,
                    TRIM(UPPER(status))               AS status,
                    CAST(amount AS DECIMAL(18,2))     AS amount,
                    COALESCE(region, 'UNKNOWN')       AS region,
                    updated_at
                FROM iceberg.bronze.orders_raw
                WHERE ingested_date = DATE '{{ ds }}'
                  AND order_id IS NOT NULL
            ) s ON t.order_id = s.order_id
            WHEN MATCHED AND s.updated_at > t.updated_at THEN
                UPDATE SET status     = s.status,
                           amount     = s.amount,
                           updated_at = s.updated_at
            WHEN NOT MATCHED THEN
                INSERT (order_id, customer_id, order_date, status, amount, region, updated_at)
                VALUES (s.order_id, s.customer_id, s.order_date, s.status, s.amount, s.region, s.updated_at)
        """,
    )

    # ── SILVER DQ GATE ────────────────────────────────────────────────
    @task
    def dq_silver_gate(ds: str, **ctx) -> None:
        hook = TrinoHook(trino_conn_id=CONN)
        # Row count check
        count = hook.get_first(f"""
            SELECT COUNT(*) FROM iceberg.silver.orders
            WHERE order_date = DATE '{ds}'
        """)[0]
        if count < 100:
            raise ValueError(f"Silver row count {count} too low for {ds}")
        # Null rate check
        null_rate = hook.get_first(f"""
            SELECT CAST(COUNT(*) FILTER (WHERE customer_id IS NULL) AS DOUBLE) / COUNT(*)
            FROM iceberg.silver.orders
            WHERE order_date = DATE '{ds}'
        """)[0]
        if null_rate > 0.01:
            raise ValueError(f"Null rate {null_rate:.2%} exceeds 1% threshold")

    # ── GOLD: aggregate metrics ───────────────────────────────────────
    load_gold = TrinoOperator(
        task_id       = 'load_gold_revenue',
        trino_conn_id = CONN,
        sql = """
            DELETE FROM iceberg.gold.daily_revenue
            WHERE order_date = DATE '{{ ds }}';

            INSERT INTO iceberg.gold.daily_revenue
            SELECT
                order_date,
                region,
                COUNT(*)                                          AS order_count,
                SUM(amount)                                       AS gross_revenue,
                COUNT(*) FILTER (WHERE status = 'completed')     AS completed_orders
            FROM iceberg.silver.orders
            WHERE order_date = DATE '{{ ds }}'
            GROUP BY order_date, region
        """,
    )

    # ── ANALYZE for CBO ──────────────────────────────────────────────
    analyze = TrinoOperator(
        task_id       = 'analyze_silver',
        trino_conn_id = CONN,
        sql = "ANALYZE iceberg.silver.orders WITH (columns = ARRAY['customer_id','order_date','region','status'])",
    )

    bronze_count = check_bronze_freshness()
    bronze_count >> load_silver >> dq_silver_gate() >> load_gold >> analyze

medallion_pipeline()

Iceberg Maintenance DAG

Schedule daily after the main pipeline completes.

from airflow.decorators import dag, task
from airflow.providers.trino.operators.trino import TrinoOperator
from airflow.providers.trino.hooks.trino import TrinoHook
from datetime import datetime, timedelta

CONN    = 'trino_default'
TABLES  = [
    ('iceberg', 'bronze', 'orders_raw'),
    ('iceberg', 'silver', 'orders'),
    ('iceberg', 'silver', 'customers'),
    ('iceberg', 'gold',   'daily_revenue'),
]

@dag(
    dag_id            = 'iceberg_maintenance',
    start_date        = datetime(2024, 1, 1),
    schedule_interval = '0 6 * * *',   # 6 AM — after main pipeline
    catchup           = False,
    default_args      = {'retries': 1},
    tags              = ['iceberg', 'maintenance'],
)
def iceberg_maintenance():

    for catalog, schema, table in TABLES:
        fqn = f"{catalog}.{schema}.{table}"

        # 1. Compact small files
        optimize = TrinoOperator(
            task_id       = f'optimize_{schema}_{table}',
            trino_conn_id = CONN,
            sql           = f"ALTER TABLE {fqn} EXECUTE optimize(file_size_threshold => '128MB')",
        )

        # 2. Expire old snapshots (keep 7 days, minimum 2 snapshots)
        expire = TrinoOperator(
            task_id       = f'expire_snapshots_{schema}_{table}',
            trino_conn_id = CONN,
            sql = f"""
                ALTER TABLE {fqn}
                EXECUTE expire_snapshots(retention_threshold => '7d', retain_last => 2)
            """,
        )

        # 3. Remove orphan files (after snapshot expiry)
        remove_orphans = TrinoOperator(
            task_id       = f'remove_orphans_{schema}_{table}',
            trino_conn_id = CONN,
            sql = f"""
                ALTER TABLE {fqn}
                EXECUTE remove_orphan_files(retention_threshold => '7d')
            """,
        )

        optimize >> expire >> remove_orphans

iceberg_maintenance()

Partition Backfill with Dynamic Task Mapping

from airflow.decorators import dag, task
from airflow.providers.trino.hooks.trino import TrinoHook
from airflow.providers.trino.operators.trino import TrinoOperator
from datetime import datetime

CONN = 'trino_default'

@dag(
    dag_id     = 'backfill_silver_orders',
    start_date = datetime(2024, 1, 1),
    schedule   = None,   # manual trigger only
    params     = {'start_date': '2024-01-01', 'end_date': '2024-03-31'},
    catchup    = False,
    tags       = ['backfill', 'silver'],
)
def backfill_dag():

    @task
    def get_date_range(start_date: str, end_date: str) -> list[str]:
        hook = TrinoHook(trino_conn_id=CONN)
        rows = hook.get_records(f"""
            SELECT CAST(d AS VARCHAR) FROM (
                SELECT sequence(
                    DATE '{start_date}',
                    DATE '{end_date}',
                    INTERVAL '1' DAY
                )
            ) t(dates)
            CROSS JOIN UNNEST(dates) AS t(d)
        """)
        return [r[0] for r in rows]

    @task
    def process_partition(partition_date: str) -> dict:
        hook = TrinoHook(trino_conn_id=CONN)

        # Delete existing data for idempotency
        hook.run(f"""
            DELETE FROM iceberg.silver.orders
            WHERE order_date = DATE '{partition_date}'
        """)

        # Re-load from bronze
        hook.run(f"""
            INSERT INTO iceberg.silver.orders
            SELECT order_id, customer_id, order_date, status, amount, region, updated_at
            FROM iceberg.bronze.orders_raw
            WHERE ingested_date = DATE '{partition_date}'
              AND order_id IS NOT NULL
        """)

        count = hook.get_first(f"""
            SELECT COUNT(*) FROM iceberg.silver.orders
            WHERE order_date = DATE '{partition_date}'
        """)[0]
        return {'date': partition_date, 'rows': count}

    dates = get_date_range(
        start_date="{{ params.start_date }}",
        end_date="{{ params.end_date }}"
    )
    process_partition.expand(partition_date=dates)

backfill_dag()

Watermark Table for Late-Arriving Data

Track the last successfully processed watermark per table to handle late arrivals:

-- Create watermark tracking table
CREATE TABLE IF NOT EXISTS iceberg.platform.pipeline_watermarks (
    pipeline_name  VARCHAR  NOT NULL,
    table_name     VARCHAR  NOT NULL,
    last_processed DATE     NOT NULL,
    processed_at   TIMESTAMP(6),
    row_count      BIGINT
)
WITH (format = 'PARQUET', format_version = 2);
@task
def get_watermark(pipeline: str, table: str) -> str:
    hook = TrinoHook(trino_conn_id=CONN)
    result = hook.get_first(f"""
        SELECT CAST(last_processed AS VARCHAR)
        FROM iceberg.platform.pipeline_watermarks
        WHERE pipeline_name = '{pipeline}' AND table_name = '{table}'
        ORDER BY processed_at DESC LIMIT 1
    """)
    return result[0] if result else '2020-01-01'

@task
def update_watermark(pipeline: str, table: str, ds: str, row_count: int) -> None:
    hook = TrinoHook(trino_conn_id=CONN)
    hook.run(f"""
        INSERT INTO iceberg.platform.pipeline_watermarks
        VALUES ('{pipeline}', '{table}', DATE '{ds}', CURRENT_TIMESTAMP, {row_count})
    """)

Data Quality Gate Pattern

@task
def dq_gate(table_fqn: str, ds: str, checks: dict) -> None:
    """
    checks = {
        'min_rows': 1000,
        'max_null_pct': 0.01,
        'freshness_hours': 25
    }
    """
    hook = TrinoHook(trino_conn_id=CONN)

    # Row count
    count = hook.get_first(
        f"SELECT COUNT(*) FROM {table_fqn} WHERE order_date = DATE '{ds}'"
    )[0]
    if count < checks.get('min_rows', 0):
        raise ValueError(f"Row count {count} < min {checks['min_rows']}")

    # Null rate on primary key
    null_pct = hook.get_first(f"""
        SELECT CAST(COALESCE(SUM(CASE WHEN order_id IS NULL THEN 1 END), 0) AS DOUBLE) / COUNT(*)
        FROM {table_fqn}
        WHERE order_date = DATE '{ds}'
    """)[0]
    max_null = checks.get('max_null_pct', 0.0)
    if null_pct > max_null:
        raise ValueError(f"Null rate {null_pct:.3%} > {max_null:.3%}")

    # Freshness
    age_hours = hook.get_first(f"""
        SELECT EXTRACT(HOUR FROM (CURRENT_TIMESTAMP - MAX(updated_at)))
        FROM {table_fqn}
        WHERE order_date = DATE '{ds}'
    """)[0]
    max_age = checks.get('freshness_hours', 48)
    if age_hours and age_hours > max_age:
        raise ValueError(f"Data is {age_hours}h old, threshold {max_age}h")

Anti-Patterns

  1. Running OPTIMIZE inside the main ingest DAG on every run — compaction on every micro-batch multiplies I/O overhead; schedule OPTIMIZE in a separate maintenance DAG (e.g. daily at 6 AM).
  2. EXPIRE_SNAPSHOTS before OPTIMIZE completes — if optimize creates new snapshots and expiry runs concurrently, it may expire the fresh snapshot; always chain optimize >> expire >> remove_orphans.
  3. Dynamic task mapping with hundreds of date partitions — expand() with 365 dates creates 365 Airflow task instances, overwhelming the scheduler DB; chunk large backfills into weekly batches.
  4. No DQ gate between Bronze and Silver — silently loading malformed bronze data into silver causes downstream model failures that are hard to trace; always check row count and null rate before promoting data.
  5. No max_active_runs limit on catchup DAGs — a pipeline with catchup=True and no max_active_runs can spawn hundreds of concurrent DAG runs all querying Trino simultaneously; set max_active_runs=3.

References

  • Iceberg maintenance: trino.io/docs/current/connector/iceberg.html
  • Airflow Trino provider: airflow.apache.org/docs/apache-airflow-providers-trino/
  • Related skills: [[trino-airflow-orchestration]], [[trino-iceberg-best-practices]], [[trino-dbt-platform]]

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

コメント

ログインなしで書き込めます。使ってみた感想、質問、動かなかった環境などを気軽にどうぞ。

まだコメントはありません。最初のコメントを書いてみませんか。

送信するとすぐに公開されます。メールアドレスや電話番号などの個人情報は書かないでください。URL は 2 つまでです。

0 / 1000 文字

このフォームは reCAPTCHA で保護されており、Google のプライバシーポリシーと利用規約が適用されます。

同じリポジトリのスキル

AIOps autonomous incident response agent — LLM-driven diagnosis loop (Claude tool-use agent with kubectl/SQL/Prometheus tools), alert-to-action pipeline (PagerDuty webhook → agent trigger), automated RCA generation (failure taxonomy + log correlation), self-healing action executor with approval gate, incident severity classification, runbook automation (structured YAML runbooks executed by agent), escalation logic, incident timeline auto-generation, Slack integration for human-in-the-loop approvals

日本語の概要は準備中です。原文の説明を表示しています。

ivanshamaev/de-agent-skills222026年5月20日 更新

AIOps capacity planning agent — predictive resource forecasting (Prophet/linear regression on Prometheus metrics), Kubernetes HPA/VPA right-sizing recommendations, node capacity headroom calculator, Kafka partition and broker capacity model, Spark executor sizing from job history, data lake storage growth forecast (S3/GCS), cluster autoscaler simulation, automatic HPA threshold tuning, cost-per-job attribution, capacity planning report generator

日本語の概要は準備中です。原文の説明を表示しています。

ivanshamaev/de-agent-skills222026年5月20日 更新

AIOps infrastructure anomaly detection — statistical baseline (z-score/IQR/seasonal decomposition), Prometheus anomaly rules (predict_linear/stddev_over_time/MAD), ML-based detection (Isolation Forest/Prophet/LSTM autoencoder on metrics), metric correlation clustering, automated alert threshold tuning from historical data, Kubernetes resource anomaly detection, data pipeline anomaly signals (row count z-score/freshness drift/throughput drop), Python sklearn/Prophet integration, Grafana anomaly panel

日本語の概要は準備中です。原文の説明を表示しています。

ivanshamaev/de-agent-skills222026年5月20日 更新

AIOps observability copilot — natural language to PromQL/LogQL translation, alert explanation in plain English (what fired/why/impact), Grafana dashboard auto-generation from service topology, anomaly narrative generation from metric patterns, on-call context enrichment (related alerts/recent deploys/runbook links), log pattern clustering for noise reduction, SLO status explanation, automated weekly observability health digest

日本語の概要は準備中です。原文の説明を表示しています。

ivanshamaev/de-agent-skills222026年5月20日 更新

AIOps continuous platform optimization agent — autonomous optimization loop (observe→analyze→recommend→apply), Kubernetes resource rightsizing (VPA recommendation reader + apply), Kafka consumer lag auto-scale, Spark job configuration tuning from history, idle resource detection (unused deployments/PVCs/topics), compaction and VACUUM scheduling, auto-ANALYZE stale statistics, cost savings attribution, optimization audit trail, human approval gate for high-risk changes

日本語の概要は準備中です。原文の説明を表示しています。

ivanshamaev/de-agent-skills222026年5月20日 更新

AIOps autonomous query cost analyzer — Trino system.runtime.query_history cost SQL (CPU time/peak memory/bytes scanned), ClickHouse system.query_log top expensive queries, Spark UI cost attribution by user/job/DAG, BigQuery INFORMATION_SCHEMA.JOBS cost analysis, LLM-driven query rewrite recommendations (missing partition filters/broadcast hints/aggregation pushdown), cost anomaly detection (user over daily budget), automated query tagging, chargeback report by team/project

日本語の概要は準備中です。原文の説明を表示しています。

ivanshamaev/de-agent-skills222026年5月20日 更新

ivanshamaev のスキルをすべて見る

このスキルの問題を報告する