ETL vs ELT: Modern Data Pipeline Architecture Guide 2026
ETL vs ELT data pipeline comparison 2026: traditional ETL limitations, cloud ELT advantages, dbt for transformation, tools comparison (Fivetran/Airbyte/dbt), and when to use each approach.
The ETL vs ELT debate has largely been settled in favor of ELT for most modern data teams โ but the reasoning matters more than the conclusion. Understanding why the industry shifted helps you make better architectural decisions for your specific context, and there are still legitimate cases where traditional ETL is the right call.
What ETL Actually Is
ETL (Extract, Transform, Load) describes a pipeline where data is:
- Extracted from source systems (databases, APIs, files)
- Transformed in a staging area or dedicated ETL server (cleansed, joined, aggregated)
- Loaded into the destination data warehouse in final, analysis-ready form
The key constraint was economics: historical data warehouses (Oracle, Teradata, IBM Db2) charged by compute. Loading raw, dirty data into them and running heavy transformations there was prohibitively expensive. The ETL server was cheap compute; the warehouse was expensive storage + compute. So you minimized what went into the warehouse by transforming before loading.
Traditional ETL Architecture
Source DBs โโโ
โโโโบ ETL Server โโโบ Staging Area โโโบ Transform โโโบ Data Warehouse
APIs โโโโโโโโโโ (Java/Python (raw tables) (SQL/SSIS) (clean, modeled)
SSIS/Talend)
Traditional ETL Code Example (PySpark ETL pattern)
from pyspark.sql import SparkSession
from pyspark.sql.functions import col, trim, upper, to_date, coalesce
spark = SparkSession.builder.appName("OrdersETL").getOrCreate()
# EXTRACT: read from source Postgres
raw_orders = spark.read \
.format("jdbc") \
.option("url", "jdbc:postgresql://source-db:5432/ecommerce") \
.option("dbtable", "orders") \
.option("user", "etl_user") \
.option("password", "secret") \
.load()
# TRANSFORM: clean and reshape BEFORE loading
transformed = raw_orders \
.filter(col("status") != "test") \
.withColumn("customer_id", trim(upper(col("customer_id")))) \
.withColumn("order_date", to_date(col("created_at"))) \
.withColumn("amount_usd", col("amount_cents") / 100.0) \
.select("order_id", "customer_id", "order_date", "amount_usd", "status") \
.dropDuplicates(["order_id"])
# LOAD: write final clean data to warehouse
transformed.write \
.format("jdbc") \
.option("url", "jdbc:postgresql://warehouse:5432/analytics") \
.option("dbtable", "fact_orders") \
.mode("append") \
.save()
Problems with Traditional ETL
- Transformation logic is buried in code โ Java, Python, SSIS packages โ not SQL that analysts understand
- No lineage โ which transformation produced this column? Who knows
- Reprocessing is expensive โ if a transformation rule changes, you re-run the entire pipeline
- Raw data is discarded โ once transformed and loaded, the original raw data is gone (or stored separately at cost)
- Brittle pipelines โ a schema change in a source breaks the ETL job silently
What ELT Is and Why It Won
ELT (Extract, Load, Transform) reverses steps 2 and 3:
- Extract raw data from sources
- Load it into the data warehouse as-is (raw, uncleaned)
- Transform inside the warehouse using SQL and tools like dbt
The shift was made possible by cloud data warehouses โ Snowflake, BigQuery, and Redshift โ which separate compute from storage and offer near-unlimited, cheap, on-demand compute. Suddenly running a heavy SQL transformation on 10TB of data in the warehouse was fast and cheap. The ETL server became the bottleneck, not the warehouse.
Modern ELT Architecture
Source DBs โโโ
โโโโบ Fivetran/Airbyte โโโบ Raw Layer โโโบ dbt โโโบ Mart Layer
APIs โโโโโโโโโโ (Extract + Load (Snowflake/ (SQL (clean models
SaaS tools โโโ unchanged) BigQuery) trans) analysts use)
ELT in Practice
In an ELT stack, raw data lands in the warehouse first:
-- Raw table: exactly as extracted from source, no transformations
-- schema: raw.ecommerce.orders
SELECT * FROM raw.ecommerce.orders LIMIT 3;
-- order_id | customer_id | amount_cents | status | created_at | _fivetran_synced
-- ord-001 | cust_42 | 9999 | PLACED | 2026-04-02 10:00:00 | 2026-04-02 10:01:23
-- ord-002 | CUST_42 | 5000 | placed | 2026-04-02 10:01:00 | 2026-04-02 10:01:23
Then dbt transforms within the warehouse:
-- dbt model: models/staging/stg_orders.sql
-- Runs as SQL inside Snowflake/BigQuery โ uses warehouse compute
with source as (
select * from {{ source('ecommerce', 'orders') }}
),
cleaned as (
select
order_id,
upper(trim(customer_id)) as customer_id,
round(amount_cents / 100.0, 2) as amount_usd,
lower(status) as status,
created_at::date as order_date
from source
where lower(status) != 'test'
)
select * from cleaned
Tool Comparison
Ingestion Layer (Extract + Load)
| Tool | Type | Best For | Pricing |
|---|---|---|---|
| Fivetran | Managed SaaS | Enterprise, 300+ connectors, zero maintenance | Per row (expensive at scale) |
| Airbyte | Open source / Cloud | Custom connectors, cost control, self-host | Free OSS, $600/mo cloud |
| Stitch | Managed SaaS | Simple pipelines, quick setup | Per row, cheaper than Fivetran |
| Meltano | Open source | Singer taps, GitOps-friendly | Free |
| Kafka + Flink | Custom streaming | High-throughput real-time | Infrastructure cost |
Transformation Layer
| Tool | Type | Best For | Pricing |
|---|---|---|---|
| dbt Core | Open source | All SQL warehouses, CI/CD integration | Free |
| dbt Cloud | Managed SaaS | Teams needing scheduling + docs UI | $50/seat/month |
| SQLMesh | Open source | dbt alternative with virtual environments | Free |
| Spark | Open source | Python-heavy transformations, large-scale | Infrastructure cost |
Orchestration Layer
| Tool | Best For |
|---|---|
| Apache Airflow | Mature, rich ecosystem, complex DAGs |
| Prefect | Pythonic, dynamic tasks, cloud managed |
| Dagster | Asset-centric, strong observability |
| dbt Cloud | dbt-only workflows (built-in scheduler) |
ETL vs ELT: Direct Comparison
| Dimension | Traditional ETL | Modern ELT |
|---|---|---|
| Where transformation happens | External server (Spark/SSIS) | Inside data warehouse |
| Raw data preserved? | Often no | Yes (always in raw layer) |
| Transformation language | Java/Python/SSIS | SQL (dbt) |
| Reprocessing cost | High (re-run pipeline) | Low (re-run dbt model) |
| Schema change handling | Manual, brittle | dbt handles with on_schema_change |
| Analyst access to raw data | No | Yes |
| Lineage tracking | Manual / none | Automatic (dbt DAG) |
| Latency | High (batch overnight) | Low (incremental, near-real-time) |
| Cost model | Fixed ETL server cost | Variable warehouse compute |
| Best warehouse type | Any (row-based OK) | Cloud columnar (Snowflake/BQ/Redshift) |
When ETL Still Makes Sense
Despite ELT winning the default argument, ETL remains the right choice in several scenarios:
1. PII and compliance requirements: If personal data must never enter the warehouse unmasked, pre-transformation before load is required. The raw layer in ELT may not be acceptable to compliance.
2. Data volume reduction: If you are ingesting 100TB of logs but only need 1% of it, transforming and filtering before load avoids storing 99TB of data you will never use.
3. Legacy warehouse infrastructure: Row-based warehouses (Postgres, MySQL) are inefficient for running dbt-style transformations. If you cannot migrate to a columnar cloud warehouse, ETL may be necessary.
4. Real-time streaming with complex enrichment: If enrichment requires joining with external APIs or ML models in real-time, you need an ETL-style pipeline (Kafka + Flink + enrichment) before the data lands anywhere.
5. Source system protection: Some source databases cannot handle the query load of a CDC (change data capture) connector. A dedicated ETL extraction layer with throttling may be necessary.
Reverse ETL: Operational Analytics
One increasingly important pattern in 2026 is Reverse ETL โ pushing data from the warehouse back into operational systems.
Data Warehouse โโโบ Census / Hightouch โโโบ Salesforce / HubSpot / Zendesk
(analytics models) (Reverse ETL tools) (operational CRM/support tools)
After transforming and analyzing data in the warehouse, you push enriched customer segments, churn scores, or LTV predictions back into the CRM so sales reps see it. This closes the loop between analytics and operations โ the warehouse becomes a source of truth for operational decisions, not just reports.
Popular reverse ETL tools: Census, Hightouch, Grouparoo (deprecated), and custom scripts using warehouse APIs.
A Complete ELT Stack Example
Here is a production-realistic ELT stack configuration:
Airbyte: Configure Postgres source
# airbyte/connections/postgres-to-snowflake.yaml
source:
type: postgres
config:
host: source-db.internal
port: 5432
database: ecommerce
username: airbyte_reader
replication_method:
method: CDC # Change Data Capture for near-real-time
replication_slot: airbyte_slot
publication: airbyte_pub
destination:
type: snowflake
config:
account: myorg.us-east-1
warehouse: AIRBYTE_WH
database: RAW_DB
schema: ECOMMERCE
username: airbyte_loader
role: AIRBYTE_ROLE
streams:
- name: orders
sync_mode: incremental_append_deduped
cursor_field: updated_at
primary_key: [id]
- name: customers
sync_mode: incremental_append_deduped
cursor_field: updated_at
primary_key: [id]
Snowflake: Layer architecture
-- Three-layer architecture in Snowflake
CREATE DATABASE RAW_DB; -- Airbyte dumps here (untouched)
CREATE DATABASE TRANSFORM_DB; -- dbt staging/intermediate models
CREATE DATABASE ANALYTICS_DB; -- dbt mart models (what BI tools connect to)
-- Grant Airbyte write access only to raw layer
GRANT USAGE ON DATABASE RAW_DB TO ROLE AIRBYTE_ROLE;
GRANT CREATE TABLE ON SCHEMA RAW_DB.ECOMMERCE TO ROLE AIRBYTE_ROLE;
-- Grant dbt transform access
GRANT USAGE ON DATABASE RAW_DB TO ROLE DBT_ROLE;
GRANT SELECT ON ALL TABLES IN DATABASE RAW_DB TO ROLE DBT_ROLE;
GRANT CREATE TABLE ON ALL SCHEMAS IN DATABASE TRANSFORM_DB TO ROLE DBT_ROLE;
GRANT CREATE TABLE ON ALL SCHEMAS IN DATABASE ANALYTICS_DB TO ROLE DBT_ROLE;
The pattern that has emerged as the 2026 default for most data teams: Airbyte (or Fivetran) โ Snowflake/BigQuery โ dbt โ BI tool (Metabase/Looker/Mode). It is not always the cheapest option, and it requires a cloud data warehouse, but it delivers the fastest time-to-insight with the lowest operational burden for teams of 1-20 data engineers.
50 CLI Commands That Save Hours Every Week
The exact Git shortcuts, file search tricks, Docker commands, and productivity hacks that senior developers use daily. Compiled from 5+ years of shipping production code.
- Git mastery (10 cmds)
- File & search wizardry
- Docker & DevOps tricks
- Process & network tools
- Productivity boosters
- macOS & Linux install guide
No spam. 1 email/week max. Unsubscribe anytime.
Found this guide useful? Check out our free developer tools.
Affiliate disclosure: Some links below are affiliate links โ we may earn a small commission at no extra cost to you. Learn more.
Recommended Tools & Resources
DigitalOcean
$200 credit for new users. Simple, affordable cloud hosting for developers.
GitHub Student Pack
Free access to 100+ developer tools. Perfect for students and new devs.
Vercel
Deploy frontend apps instantly. Free tier is generous for side projects.
DevPlaybook Products
Boilerplates, scripts & AI toolkits to 10x your dev workflow.