Skip to main content
← All Articles ยท ยท 11 min read

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.

etleltdata-pipelinedata-warehousefivetranairbytedbtdata-engineering

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:

  1. Extracted from source systems (databases, APIs, files)
  2. Transformed in a staging area or dedicated ETL server (cleansed, joined, aggregated)
  3. 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:

  1. Extract raw data from sources
  2. Load it into the data warehouse as-is (raw, uncleaned)
  3. 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)

ToolTypeBest ForPricing
FivetranManaged SaaSEnterprise, 300+ connectors, zero maintenancePer row (expensive at scale)
AirbyteOpen source / CloudCustom connectors, cost control, self-hostFree OSS, $600/mo cloud
StitchManaged SaaSSimple pipelines, quick setupPer row, cheaper than Fivetran
MeltanoOpen sourceSinger taps, GitOps-friendlyFree
Kafka + FlinkCustom streamingHigh-throughput real-timeInfrastructure cost

Transformation Layer

ToolTypeBest ForPricing
dbt CoreOpen sourceAll SQL warehouses, CI/CD integrationFree
dbt CloudManaged SaaSTeams needing scheduling + docs UI$50/seat/month
SQLMeshOpen sourcedbt alternative with virtual environmentsFree
SparkOpen sourcePython-heavy transformations, large-scaleInfrastructure cost

Orchestration Layer

ToolBest For
Apache AirflowMature, rich ecosystem, complex DAGs
PrefectPythonic, dynamic tasks, cloud managed
DagsterAsset-centric, strong observability
dbt Clouddbt-only workflows (built-in scheduler)

ETL vs ELT: Direct Comparison

DimensionTraditional ETLModern ELT
Where transformation happensExternal server (Spark/SSIS)Inside data warehouse
Raw data preserved?Often noYes (always in raw layer)
Transformation languageJava/Python/SSISSQL (dbt)
Reprocessing costHigh (re-run pipeline)Low (re-run dbt model)
Schema change handlingManual, brittledbt handles with on_schema_change
Analyst access to raw dataNoYes
Lineage trackingManual / noneAutomatic (dbt DAG)
LatencyHigh (batch overnight)Low (incremental, near-real-time)
Cost modelFixed ETL server costVariable warehouse compute
Best warehouse typeAny (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.

Free Download

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.