Production data engineering reference covering ELT/ETL patterns, dbt project structure, orchestration (Airflow/Dagster/Prefect), Python data stack (pandas/Polars/DuckDB/Spark), cloud warehouses (Snowflake/BigQuery/Azure Fabric), lakehouse formats (Iceberg/Delta/Hudi), CDC with Debezium, SCD2, data quality, feature stores, and ML productionization with MLflow. Use when answering questions about data pipelines, SQL optimization, warehouse cost control, Python data tools, gradient boosting, A/B ...
Scanned 9/19/2026
npx -y skills add the-vibey-project/vibey --skill data-engineering --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Data Engineering?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/the-vibey-project-data-engineering)More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.
---
name: data-engineering
description: "Production data engineering reference covering ELT/ETL patterns, dbt project structure, orchestration (Airflow/Dagster/Prefect), Python data stack (pandas/Polars/DuckDB/Spark), cloud warehouses (Snowflake/BigQuery/Azure Fabric), lakehouse formats (Iceberg/Delta/Hudi), CDC with Debezium, SCD2, data quality, feature stores, and ML productionization with MLflow. Use when answering questions about data pipelines, SQL optimization, warehouse cost control, Python data tools, gradient boosting, A/B testing, or modern data stack architecture."
---
# Data Engineering & Data Science Production Reference
## ELT vs ETL — Decision Framework
**ELT is the production default** on modern cloud warehouses (Snowflake, BigQuery, Databricks). Load raw data first, transform in-warehouse with dbt. Storage/compute decoupling makes this cheaper and operationally simpler than a separate transformation tier.
**ETL still wins when:**
- Compliance requires pre-load masking/tokenization (HIPAA, PCI DSS)
- Source data must be filtered before reaching governed storage
- Destination is an operational system, not a warehouse
**ELT advantages:** preserves raw data for reprocessing when business logic changes; no transformation infrastructure to manage; one pipeline to operate.
---
## Pipeline Design Principles
- **Idempotency is non-negotiable** — re-running a job must produce the same result; enables safe backfills and retries
- **At-least-once semantics** (with idempotent writes/dedup) for most analytics; exactly-once is expensive and rarely necessary
- **Late-arriving data**: handle with watermarks and windowing
- **Late-arriving dimensions** (fact arrives before its dimension): use placeholder/inferred dimension rows rather than dropping facts or accepting null FKs
- **Watermark storage**: store in an audit table in the target DB, not just an orchestrator variable — makes it debuggable
---
## Incremental Loads & CDC
**Watermark-based incremental**: pull rows where `updated_at > last_watermark`. Store the watermark in the target DB audit table.
**Debezium** (dominant open-source CDC):
- Reads the database transaction log (WAL/binlog) via Kafka Connect — not polling
- Captures every insert/update/delete in order with minimal source impact
- Supports MySQL, Postgres, MongoDB, SQL Server, Oracle
- **Incremental snapshots** (v1.6+): interleaves snapshotting with streaming using watermark approach (from Netflix's DBLog paper) — no long table locks
- Note: log-based CDC on Postgres may require config changes and a restart
---
## SCD2 Implementation
Use `effective_from` / `effective_to` / `is_current` columns.
**Reliable cross-platform pattern (two steps):**
1. Close changed rows (set `effective_to`, `is_current = 0`)
2. Insert new current versions
A single MERGE cannot do both UPDATE and INSERT from one source row without the nested INSERT-over-MERGE-OUTPUT trick.
**Options:**
- **dbt snapshots**: cleaner when Silver layer is dbt-managed
- **Manual MERGE**: more control (e.g., Spark Delta MERGE in lakehouse pipelines)
**Query patterns:**
- Current state: `WHERE is_current = 1`
- Historical: `WHERE order_date BETWEEN effective_from AND effective_to`
- Forgetting these joins duplicates fact rows
---
## dbt Project Structure
Three-layer architecture:
| Layer | Description | Materialization | Rules |
|---|---|---|---|
| **Staging** | 1:1 with sources; rename/cast/basic categorization | Views | No joins; prefix `stg_` |
| **Intermediate** | Business logic, joins, re-graining | Ephemeral | Referenced by only one downstream (if more, make it a macro) |
| **Marts** | Wide/denormalized entities | Tables | ≤4–6 joins; prefix by domain |
**Materialization progression:** view → table (when query is slow) → incremental (only when table builds are too slow).
**Anti-patterns to avoid:**
- `finance_orders` vs `marketing_orders` — build one source of truth instead
- Splitting ML vs reporting marts
- Using seeds to load source data
- Using tags instead of folders as primary selectors
**dbt tests**: `not_null`, `unique`, `accepted_values`, `relationships` on every model. Using thresholds/severities to suppress failing tests hides anomalies and erodes audit trust.
---
## Orchestration — Tool Selection
| Tool | Best for | Weakness |
|---|---|---|
| **Airflow** | Enterprise, 100+ pipelines, 1,000+ providers (MWAA/Cloud Composer/Astronomer) | Steeper learning curve; needs running instance to test DAGs |
| **Dagster** | Greenfield/dbt-centric platforms; software-defined assets, local testing (swap Snowflake for DuckDB) | ~1/4 of Airflow's integrations |
| **Prefect** | Python-native, fastest laptop-to-production | Serverless cold starts 5–15s; near-real-time workloads feel this |
**Migration rule:** 50+ working Airflow DAGs → stay and adopt TaskFlow incrementally; Dagster migration runs ~10–20 DAGs/month via `dagster-airflow`.
---
## Data Quality — Layered Approach
- **dbt tests**: shift-left checks inside transformation layer
- **Great Expectations**: 300+ expectations, automated profiling, human-readable Data Docs; expressive Python validation-as-code
- **Soda Core**: YAML-based, SQL-native, lightweight
- **Deequ** (Amazon, Spark-native): massive-scale checks without sampling
- **Pandera**: DataFrame schema validation (pandas, Polars, PySpark, Ibis backends); use for feature-engineering checks, drift detection, ETL decorators
Most mature teams combine dbt tests (model layer) + Great Expectations or Soda Core (distribution/profiling checks dbt can't express).
---
## Python Data Stack
### pandas
- Optimize memory: categorical dtypes for low-cardinality strings, downcast numerics, chunk large CSVs (`chunksize`), Arrow-backed dtypes (pandas 2.x)
- Avoid row-wise `apply`/`iterrows` — vectorize
- Single-threaded for most ops (GIL-bound)
### Polars (Rust-based, columnar Arrow)
- Multi-threaded by default
- **Lazy API**: predicate pushdown, projection pruning, operation fusion, streaming mode for larger-than-RAM data
- Benchmarks vs pandas (NYC taxi 12.7M rows): 25× faster CSV reads, 5–10× faster aggregations, joins up to ~13.75×
- 650GB Delta test on 32GB EC2: Polars 12 min vs PySpark >1 hour
- Parquet write performance converges (both delegate to PyArrow C++)
### DuckDB (in-process OLAP)
- Vectorized execution; reads Parquet/S3 directly with columnar pushdown
- 1M-row query: ~3.84s vs pandas' ~19.57s
- 5–10× faster group-bys on >100M rows
- Best for local/embedded analytics and as a dbt/test backend
### PySpark
- Use DataFrame API (Catalyst optimizer) over RDDs
- Avoid data skew (salting, AQE); prefer broadcast joins when one side fits in memory
- **Only reach for Spark when data won't fit on a single node** or you need distributed/streaming/MLlib
### Arrow/PyArrow
Zero-copy columnar IPC — the lingua franca between Polars, DuckDB, pandas 2.x, and Spark.
---
## Data Quality at Scale — Pydantic vs Pandera
**Pandera**: tabular validation (DataFrameSchema or class-based DataFrameModel); use for DataFrame-level checks.
**Pydantic V2**: record/object validation; core rewritten in Rust as `pydantic-core` — "about 17× faster than V1." Use for API/record validation.
Integration: Pandera uses Pydantic for coercion; embed Pydantic models row-wise only for very small (~100-row) frames.
---
## Batch vs Streaming Decision
| Need | Recommendation |
|---|---|
| Sub-second latency, business acts on it (fraud, dynamic pricing) | True streaming (Flink or Kafka + consumer) |
| Seconds of latency acceptable | Micro-batch (Spark Structured Streaming) |
| Minutes/hours acceptable | Batch ELT |
**Flink**: true event-at-a-time, p99 latencies <100ms; more resource-efficient for low-latency.
**Spark Streaming**: ~2–5s; wins when you share resources across batch and streaming or already run Databricks/Delta.
**Cost lever**: BigQuery streaming costs $0.01/200MB; micro-batch loading is free — a major difference at scale.
---
## SQL & CTE Best Practices
### CTE vs subquery vs temp table
- CTEs and subqueries are **performance-equivalent** in modern engines (Postgres 12+, BigQuery, Snowflake)
- CTEs win on readability and avoiding repeated scans
- Postgres ≤11 materialized CTEs (optimization fence); Postgres 12+ inlines them
- Snowflake: CTE referenced twice scanned 1.3MB vs 2.7MB for repeated subquery
- Temp tables benefit from indexing for complex multi-step manipulation
### Window Functions
- `ROW_NUMBER` (unique sequential), `RANK` (gaps on ties), `DENSE_RANK` (no gaps)
- `LAG`/`LEAD` (prior/next row), `FIRST_VALUE`/`LAST_VALUE`
- **Critical**: `ROWS` vs `RANGE` frames — RANGE includes all peer rows with equal ordering values; the default `RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW` can produce surprising results with ties
### Query Optimization
- Read EXPLAIN ANALYZE plans; confirm partition pruning fires and predicate pushdown reaches scans
- **BigQuery**: partition on DATE/TIMESTAMP/INT, cluster on high-cardinality filter columns; filtering one month of 5-year table scans ~1/60th
- **Snowflake**: automatic micro-partitioning with min/max metadata; manual clustering keys for large frequently-filtered tables (50–70% data reduction); note clustering/elimination uses only first ~5 characters (so YYYYMMDD clusters by YYYYM effectively)
### Anti-patterns
- Correlated subqueries (re-execute per outer row — rewrite as joins)
- `DISTINCT` to paper over join fan-out (fix the join grain instead)
- Implicit type coercion (silently kills index/partition usage)
- `SELECT *` on columnar stores (BigQuery bills full column scan regardless of LIMIT)
---
## Cloud Warehouse Cost Controls
### Snowflake
- **Auto-suspend at 60s** for ETL/short bursts; 5–10 min for cache-sensitive BI
- Enable auto-resume; use **Economy scaling policy** for batch to avoid cluster thrashing
- Multi-cluster scales out for concurrency, not single-query speed
- Capacity pricing (1–3 yr) saves ~15–25% vs on-demand
- **Cost killers**: zombie warehouses (`AUTO_SUSPEND=0`), over-provisioned dev XL warehouses, excessive Time Travel retention on truncate-reload tables, runaway reclustering on high-churn tables
### BigQuery
- On-demand: $6.25/TiB scanned (first 1 TiB/month free); `SELECT *` on 10TB = $62.50; LIMIT does not reduce cost
- Editions (Standard/Enterprise/Enterprise Plus) charge per slot-hour; autoscaling GA Feb 2025
- Break-even from on-demand to slots: ~300–500 TiB/month steady scanning
- Use `INFORMATION_SCHEMA.JOBS` / `JOBS_TIMELINE` for cost attribution
- Tables untouched 90 days → long-term storage at half price automatically
### Azure / Microsoft Fabric
- **Fabric Lakehouse** (Spark-primary) vs **Fabric Warehouse** (T-SQL-primary, full read/write)
- All data in OneLake (Delta Parquet) — Spark notebooks, T-SQL, and Power BI Direct Lake read the same table without copies
- Synapse dedicated pool → Fabric Warehouse migration: ~30–50% cost reduction vs always-on Synapse; expect 4–8 weeks per pool
- Databricks: reserved/committed compute + predictive optimization + right-sized clusters = 30–70% savings
---
## Lakehouse Table Formats
| Format | Best for | Notes |
|---|---|---|
| **Iceberg** | Interoperability standard; partition evolution as metadata operation | Databricks acquired Tabular (Iceberg creators); AWS S3 Tables, Snowflake Polaris catalog |
| **Delta** | Largest installed base; Microsoft Fabric default | Most large enterprises; Databricks default |
| **Hudi** | Streaming/CDC upserts (Merge-on-Read first-class) | Vendor (Onehouse) benchmarks favor it for upserts — run your own TPC-DS |
---
## Azure Databricks Optimization
- `OPTIMIZE`: compacts small files (~1GB target)
- `ZORDER BY`: co-locates data for high-cardinality filter columns
- `VACUUM`: removes unreferenced files (default 7-day retention via `delta.deletedFileRetentionDuration`)
- **Liquid clustering** (`CLUSTER BY`, GA DBR 15.2+): redefine clustering keys without rewriting data
- **Automatic liquid clustering** (`CLUSTER BY AUTO`, DBR 15.4+) + **predictive optimization**: Unity Catalog auto-selects keys and runs OPTIMIZE/VACUUM/ANALYZE on serverless compute
- Limit clustering to 1–4 high-value filter/join columns; don't partition tables under ~1TB
---
## Schema Evolution & Data Contracts
- Use a schema registry (Confluent) with Avro for Kafka to enforce backward/forward compatibility
- **Open Data Contract Standard (ODCS) v3.1.0** (Apache 2.0, maintained by Bitol under Linux Foundation AI & Data): machine-readable producer-consumer agreements covering schema + semantics + SLAs
- **Data Contract CLI**: lint, test against Snowflake/BigQuery/Databricks, detect breaking changes, export to dbt/Avro/JSON Schema
- **Rule of thumb**: one person → skip the contract; two+ teams depend on it in production → write one
---
## Feature Stores
**Solve:** training-serving skew, online/offline consistency, feature reuse.
| Store | Type | Best for |
|---|---|---|
| **Feast** (Linux Foundation) | Open-source, bring-your-own-infra | Cost/flexibility/no lock-in; you own pipeline and writes to online store |
| **Tecton** (ex-Uber Michelangelo team) | Managed; includes transformation/compute | Mission-critical real-time; eliminates skew "by construction"; sub-10ms p99 online serving |
**Offline store**: BigQuery/Snowflake/S3 for point-in-time-correct training sets.
**Online store**: Redis/DynamoDB for low-latency inference.
Use Feast for flexibility; Tecton for managed SLAs on real-time use cases.
---
## MLflow — Production Patterns
Four components: **Tracking** (log params/metrics/artifacts per run), **Projects** (reproducible packaging), **Models** (flavors for deployment), **Model Registry** (versioning + lineage + lifecycle).
**Modern champion/challenger pattern (MLflow 2.9.0+):**
- Fixed stages (`Staging/Production/Archived`) are deprecated
- Use **aliases**: mutable named pointers like `champion`/`challenger`
- Reference: `models:/MyModel@champion`
- Promote by reassigning the alias
**Production deployment patterns:**
- Batch scoring for non-latency-sensitive use
- Online inference behind managed endpoint for real-time
- **Shadow mode**: run new model on live traffic without acting, before promotion
- **Champion/challenger**: controlled rollout via alias reassignment
---
## ML Modeling Best Practices
### Feature Engineering
- Target encoding for high-cardinality categoricals (with CV/smoothing to prevent leakage)
- One-hot for low cardinality; embeddings for very high cardinality
- Temporal features (lags, rolling windows): build carefully to avoid leakage
- **Never compute encodings/scalers on full dataset before splitting**
### Model Validation
- **Time-series CV** (expanding/rolling window) for temporal data — never shuffle-split
- **Group-k-fold** when records cluster (same user/entity)
- Calibrate probabilities (Platt/isotonic) for reliable scores
- scikit-learn `Pipeline + ColumnTransformer`: canonical leakage guard — preprocessing fit only on training folds
### Gradient Boosting (Tabular Data Default)
All three are competitive; no significant differences under Wilcoxon–Holm analysis (arXiv 2407.00956):
| Library | Strength | Weakness |
|---|---|---|
| **LightGBM** | Fastest (~7× vs XGBoost); best for very large datasets | Leaf-wise growth overfits small data |
| **CatBoost** | Best with many categorical features (native handling); strong defaults | |
| **XGBoost** | Slight accuracy/generalization edge in some benchmarks; Kaggle workhorse | Slowest grid search on large data |
**Tuning sequence**: benchmark all three with defaults → tune the frontrunner: lower `learning_rate` + more estimators with early stopping, reduce `max_depth`/`num_leaves`, raise L1/L2, subsample.
### A/B Testing
- Pre-compute sample size from MDE, alpha, and power (typically 80%)
- **Peeking problem**: checking results repeatedly inflates Type I error
- **Fixed-horizon tests**: no peeking, decide at planned N — maximum rigor
- **Sequential testing** (O'Brien-Fleming or Pocock alpha-spending): valid peeking and early stopping
- A/A tests validate randomization
- Multi-armed bandits: good for many-armed, low-stakes optimization; not for clean causal readouts
---
## Data Mesh — What Actually Works
Incremental product mindset (in order):
1. **Ownership**: every dataset has a named accountable owner
2. **Contracts**: written quality/freshness/completeness SLAs in YAML
3. **Discoverability**: catalog registration
4. **Governance**: automatic access control, audit, lineage
The "full mesh topology" without a self-serve platform fails — domain teams default to ad-hoc pipelines. The self-serve platform is the most underinvested principle.
---
## Staged Implementation Roadmap
**Stage 1 (weeks 0–8):** ELT on cloud warehouse + dbt (staging/intermediate/marts). Enable cost guardrails: Snowflake auto-suspend 60s + Economy; BigQuery partitioning/clustering; Databricks Unity Catalog + predictive optimization. Add dbt tests on every model.
**Stage 2 (months 2–4):** Layer Great Expectations or Soda Core for distribution/profiling checks. Data contracts (ODCS + Data Contract CLI) for datasets two+ teams depend on. Deploy orchestrator matched to team. SCD2 via dbt snapshots or two-step MERGE.
**Stage 3 (months 4–8):** Replace pandas bottlenecks with Polars/DuckDB before reaching for Spark — only adopt Spark when data genuinely exceeds single-node memory. CDC via Debezium with incremental snapshots. Streaming only where sub-second latency drives business action.
**Stage 4 (months 6–12):** scikit-learn Pipeline + ColumnTransformer to prevent leakage; time-series/group CV; MLflow Registry aliases (champion/challenger). Gradient boosting (benchmark LightGBM/CatBoost/XGBoost). Feature store only when reuse and training-serving skew are real pain points.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!