agentsclimarketplace

Dbt starrocks performance

Skill ivanshamaev/de-agent-skills/group_skills/starrocks_group_skills/dbt_starrocks_performance

Профессиональные Data Engineering Agent Skills для разработки AI Agentic Data Platform

Install
npx -y skills add ivanshamaev/de-agent-skills --skill dbt_starrocks_performance

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
  • 13 stars13 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

dbt + StarRocks performance tuning — model DAG optimization (fan-in/fan-out), incremental merge window sizing, partition-aware incremental filters, avoiding full table rebuilds, query plan hints in dbt SQL (LEADING/JOIN hints), materialized view as dbt model target, concurrent model execution with threads, pre/post hooks for ANALYZE TABLE, dbt run selectors to minimize rebuilt models

SKILL.md

9.0 KB, as published. Nobody here has run it

dbt + StarRocks Performance Tuning

When to Use

  • Incremental dbt models that run slowly due to poor partition filtering
  • dbt DAG with wide fan-out causing excessive table rebuilds
  • Need to run ANALYZE TABLE after dbt model refreshes
  • Optimizing join order and query plans for complex dbt models
  • Tuning thread count and model concurrency for large dbt projects

Partition-Aware Incremental Filters

The most impactful optimization: ensure incremental models only scan new partitions.

-- BAD: scans entire source table even in incremental mode
{{ config(
    materialized='incremental',
    incremental_strategy='append',
    partition_by={"field": "created_at", "data_type": "date", "granularity": "day"}
) }}

SELECT * FROM {{ source('raw', 'events') }}
{% if is_incremental() %}
WHERE event_ts > (SELECT MAX(event_ts) FROM {{ this }})
{% endif %}

The MAX(event_ts) subquery scans this — fine for small tables but expensive for billions of rows.

-- GOOD: use partition column for both filter and watermark
{{ config(
    materialized='incremental',
    incremental_strategy='insert_overwrite',
    partition_by={"field": "event_date", "data_type": "date", "granularity": "day"}
) }}

{% set lookback_days = 3 %}

SELECT
    DATE(event_ts) AS event_date,
    user_id,
    event_type,
    event_ts
FROM {{ source('raw', 'events') }}

{% if is_incremental() %}
-- Reprocess last N days to handle late-arriving data
WHERE DATE(event_ts) >= DATE_SUB(CURDATE(), INTERVAL {{ lookback_days }} DAY)
{% endif %}

With insert_overwrite, dbt replaces only the matching partitions — no full scan of this.


Late-Arriving Data Window

Balance completeness vs performance:

{% set incremental_lookback_days = var('lookback_days', 3) %}

{% if is_incremental() %}
WHERE DATE(created_at) >= DATE_SUB(
    -- Use a fixed reference point, not NOW(), for reproducibility
    DATE('{{ var("ds", modules.datetime.date.today().isoformat()) }}'),
    INTERVAL {{ incremental_lookback_days }} DAY
)
{% endif %}

Run with override:

dbt run --vars '{"lookback_days": 7, "ds": "2024-01-15"}'

ANALYZE TABLE via Post-Hook

Always refresh CBO statistics after materializing a table:

-- models/gold/orders_daily.sql
{{ config(
    materialized='table',
    post_hook=[
        "ANALYZE TABLE {{ this }} WITH ASYNC MODE",
    ]
) }}

SELECT ...

For incremental models with partition granularity:

{{ config(
    materialized='incremental',
    incremental_strategy='insert_overwrite',
    partition_by={"field": "dt", "data_type": "date", "granularity": "day"},
    post_hook=[
        -- Analyze only today's partition (much faster than full table)
        "ANALYZE TABLE {{ this }} PARTITION (p{{ run_started_at.strftime('%Y%m%d') }}) WITH ASYNC MODE",
    ]
) }}

Bulk ANALYZE in post-hook for large projects:

# dbt_project.yml
models:
  my_project:
    gold:
      +post-hook: "ANALYZE TABLE {{ this }} WITH ASYNC MODE"

Query Hints in dbt Models

Embed StarRocks query hints directly in model SQL for complex join optimization:

-- models/gold/revenue_report.sql
{{ config(materialized='table') }}

-- Hint: force join order (orders first, then products)
SELECT /*+ LEADING(o p c) JOIN_METHOD(o, p, BROADCAST) */
    p.category,
    c.region,
    COUNT(*) AS order_count,
    SUM(o.amount) AS revenue
FROM {{ ref('orders') }} o
JOIN {{ ref('dim_products') }} p USING (product_id)
JOIN {{ ref('dim_customers') }} c USING (customer_id)
WHERE o.dt = CURDATE()
GROUP BY p.category, c.region

Pre-Hook: Ensure Partition Exists

For models that INSERT OVERWRITE into manually managed partitions:

{{ config(
    materialized='incremental',
    incremental_strategy='insert_overwrite',
    pre_hook=[
        """
        ALTER TABLE {{ this }}
        ADD PARTITION IF NOT EXISTS p{{ run_started_at.strftime('%Y%m%d') }}
        VALUES [("{{ run_started_at.strftime('%Y-%m-%d') }}"),
                ("{{ (run_started_at + modules.datetime.timedelta(days=1)).strftime('%Y-%m-%d') }}"))
        """
    ]
) }}

Materialized View as dbt Model

Create a StarRocks async MV using a dbt custom materialization or post-hook:

-- Approach 1: Create MV in a post-hook on the base table
{{ config(
    materialized='table',
    post_hook=[
        """
        CREATE MATERIALIZED VIEW IF NOT EXISTS {{ this }}_mv
        DISTRIBUTED BY HASH(region) BUCKETS 8
        REFRESH ASYNC EVERY (INTERVAL 1 HOUR)
        AS SELECT region, COUNT(*) AS orders, SUM(amount) AS revenue
           FROM {{ this }}
           GROUP BY region
        """
    ]
) }}

Thread and Concurrency Tuning

profiles.yml Thread Config

starrocks_project:
  outputs:
    prod:
      type: starrocks
      threads: 8           # concurrent model builds (default: 1)
      # ...

Which Models Can Run in Parallel

dbt parallelizes models with no dependencies between them. Ensure your DAG is as wide as possible:

staging/stg_orders    ─┐
staging/stg_customers ─┼──► silver/orders (sequential join)
staging/stg_products  ─┘

silver/orders_by_region ─┐
silver/orders_by_product ─┼──► gold/revenue_report
silver/customer_segments ─┘

Avoid chains where each model depends on the previous — this prevents parallelism.


Model Selection for Minimal Rebuilds

# Only run models that changed + their downstream dependents
dbt run --select state:modified+

# Run only changed models, no downstream
dbt run --select state:modified

# Run only a specific model and its upstream dependencies
dbt run --select +orders

# Run a specific tag group
dbt run --select tag:daily

# Exclude expensive models from CI
dbt run --exclude tag:expensive

var() for Environment-Specific Config

{{ config(
    materialized='incremental',
    incremental_strategy='insert_overwrite',
    partition_by={
        "field": "dt",
        "data_type": "date",
        "granularity": var('partition_granularity', 'day')
    },
    properties={
        "replication_num": var('replication_num', '3')
    }
) }}
# dbt_project.yml
vars:
  partition_granularity: "day"
  replication_num: "3"    # override to "1" in dev
dbt run --vars '{"replication_num": "1"}' --target dev

Materialization Choice vs Performance

Model TypeMaterializationRebuild FrequencyPerformance
Staging (raw cleanup)tableDailyFull rebuild is cheap
Silver (CDC upsert)incremental (unique_key)ContinuousOnly processes delta
Intermediate heavy joinstableDailyAvoid ephemeral for >100M rows
Gold partitionsincremental (insert_overwrite)DailyReplace partitions, not full table
Real-time metricsMV via post-hookAuto (async)Near-real-time without DAG run
Rarely queried reportsviewOn queryNo storage cost

Profiling Slow Models

# Show timing for each model
dbt run --select orders_daily --log-level debug 2>&1 | grep "Completed"

# Use dbt artifacts for timing analysis
cat target/run_results.json | python3 -c "
import json, sys
r = json.load(sys.stdin)
for n in sorted(r['results'], key=lambda x: x.get('execution_time', 0), reverse=True)[:10]:
    print(f\"{n['execution_time']:.1f}s  {n['unique_id']}\")
"

Anti-Patterns

  1. incremental_strategy='append' on a growing table without watermark — re-runs append all historical rows repeatedly; always add an is_incremental() filter.
  2. MAX(event_ts) FROM {{ this }} for watermark on billion-row tables — scans the entire previous model; use partition column bounds instead.
  3. threads: 1 — serial execution for large dbt projects can take hours; set to 4-16 based on BE count.
  4. No post-hook ANALYZE — CBO uses stale stats after model rebuild, producing bad plans for downstream queries; always ANALYZE after table materialization.
  5. Using view for heavy aggregations — view recomputes the aggregation on every BI query; materialize as table or MV.
  6. on_schema_change='sync_all_columns' on large tables — triggers full table rebuild every time a column is added; use append_new_columns instead.

References

  • dbt-starrocks incremental: github.com/StarRocks/starrocks/tree/main/contrib/dbt-connector
  • ANALYZE TABLE: docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/ANALYZE_TABLE/
  • dbt model selection: docs.getdbt.com/reference/node-selection/syntax
  • Related skills: [[dbt-starrocks-models]], [[starrocks-cbo]], [[starrocks-query-optimizer]], [[dbt-core]]

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.