Blog

Building Incremental Data Pipeline with Airflow & BigQuery

Published

August 24, 2026

Author

Kuldeep Chahar

Type

Insights Article

Reading Time

8 min

Data pipelines often start simple.

You have a source system, you extract the data, load it into a warehouse, and run a few transformations. Everything works nicely when the dataset is small.

Then the data grows.

Your table has 10 million rows. Then 50 million. Then 500 million.

Suddenly, running the same full-load pipeline every day starts wasting time, compute, and money.

This is where incremental data loading becomes important.

Instead of processing the entire dataset every time, an incremental pipeline identifies only the new or changed records and processes those records.

In this guide to Building Incremental Data Pipeline architectures, we’ll build a practical incremental ELT pipeline using Google Cloud Storage (GCS), Apache Airflow, and BigQuery. We’ll also explore the engineering decisions that make an incremental data pipeline reliable enough for a production environment.

The Problem With Full Data Loads

Imagine an e-commerce company receives 100,000 new or updated orders every day.

Its existing BigQuery table contains 100 million records.

A simple daily pipeline might do this:

Source
   ↓
Extract everything
   ↓
Load 100 million rows
   ↓
Transform everything
   ↓
BigQuery

But most of those 100 million records haven’t changed.

We are doing work that doesn’t need to be done.

An incremental pipeline takes a different approach:

Existing Data:       100,000,000 rows
New/Changed Data:          100,000 rows
                              ↓
                    Incremental Processing
                              ↓
                         BigQuery

The goal is simple:

Process only what changed, while keeping the target dataset correct.


What We Are Building

For this example, assume a source system produces daily order files.

The architecture looks like this:

                 ┌──────────────────┐
                 │   Source System  │
                 │  Orders / APIs   │
                 └────────┬─────────┘
                          │
                          ▼
                 ┌──────────────────┐
                 │       GCS        │
                 │   Raw Landing    │
                 └────────┬─────────┘
                          │
                          ▼
                 ┌──────────────────┐
                 │     Airflow      │
                 │   Orchestration  │
                 └────────┬─────────┘
                          │
                          ▼
                 ┌──────────────────┐
                 │ BigQuery Staging │
                 │     Table        │
                 └────────┬─────────┘
                          │
                       MERGE
                          │
                          ▼
                 ┌──────────────────┐
                 │ BigQuery Target  │
                 │  Curated Table   │
                 └──────────────────┘

Each component has a specific responsibility:

  • GCS stores the raw incoming files.
  • Airflow schedules and orchestrates the workflow.
  • BigQuery staging temporarily holds the incremental batch.
  • BigQuery target contains the final dataset.
  • SQL/MERGE applies inserts and updates.

This is an ELT-style architecture because the data is loaded first and transformed inside BigQuery.

Building Incremental Data Pipeline with Apache Airflow & BigQuery

Step 1: Design the Raw Data Layer for Incremental Processing

The first rule of a reliable pipeline is:

Don’t throw away the raw data after loading it.

A simple GCS structure could be:

gs://company-data/

raw/
└── orders/
    ├── 2026-08-20/
    │   └── orders.csv
    ├── 2026-08-21/
    │   └── orders.csv
    └── 2026-08-22/
        └── orders.csv

Date-based paths make the data easier to find and replay.

If the pipeline fails for August 21, we should be able to process that day’s file again without asking the source system to regenerate it.

The raw layer therefore becomes our recovery point.

Step 2: Decide What “Incremental” Means

This is actually the most important design decision.

How will the pipeline know which records are new or changed?

There are several approaches.

Timestamp-based incremental loading

Suppose every order has:

order_id
customer_id
amount
status
updated_at

We can use updated_at to identify records that changed since the previous successful run.

For example:

Last successful timestamp:
2026-08-21 23:59:59

Load records where:

updated_at > 2026-08-21 23:59:59

This is commonly called a watermark.

The pipeline remembers where the previous successful processing ended and starts from there.

Step 3: Load Incremental Data Into a BigQuery Staging Table

Airflow can trigger a BigQuery load from GCS.

Instead of directly modifying the production table, load the batch into a staging table first:

analytics_staging.orders_increment

For example:

GCS file
   ↓
BigQuery staging
   ↓
Validation
   ↓
MERGE
   ↓
Production table

The staging layer is useful because we can validate the incoming data before it affects the main dataset.

We can check:

  • Is the file empty?
  • Are required columns present?
  • Are there duplicate IDs?
  • Are timestamps valid?
  • Are numeric fields within expected ranges?

Only after these checks pass should the data move forward.

Step 4: Use BigQuery MERGE for Incremental Updates

Now comes the heart of the incremental pipeline.

Suppose our target table is:

analytics.orders

and our incoming records are in:

analytics_staging.orders_increment

We don’t simply append everything.

Some records may already exist but have changed.

That’s where MERGE becomes useful.

MERGE analytics.orders AS target
USING analytics_staging.orders_increment AS source
ON target.order_id = source.order_id

WHEN MATCHED THEN
  UPDATE SET
    customer_id = source.customer_id,
    amount = source.amount,
    status = source.status,
    updated_at = source.updated_at

WHEN NOT MATCHED THEN
  INSERT (
    order_id,
    customer_id,
    amount,
    status,
    updated_at
  )
  VALUES (
    source.order_id,
    source.customer_id,
    source.amount,
    source.status,
    source.updated_at
  );

The logic is straightforward:

Record exists?
       │
   ┌───┴───┐
   │       │
  YES      NO
   │       │
 UPDATE   INSERT

This lets the same pipeline handle both new records and changed records.

Step 5: Orchestrate the Incremental Data Pipeline With Airflow

Now we can turn the process into an Airflow DAG.

A practical workflow could be:

check_source_file
        ↓
load_to_staging
        ↓
validate_increment
        ↓
merge_to_bigquery
        ↓
run_quality_checks
        ↓
update_watermark
        ↓
cleanup_staging

Airflow’s job here is not to process millions of rows itself.

Its job is to make sure the right operations happen in the right order.

A simplified DAG might look like:

from airflow import DAG
from airflow.providers.google.cloud.operators.bigquery import (
    BigQueryInsertJobOperator
)
from datetime import datetime

with DAG(
    dag_id="incremental_orders_pipeline",
    start_date=datetime(2026, 1, 1),
    schedule="@daily",
    catchup=False,
) as dag:

    load_staging = BigQueryInsertJobOperator(
        task_id="load_staging",
        configuration={
            "query": {
                "query": "CALL load_orders_to_staging()",
                "useLegacySql": False,
            }
        },
    )

    merge_data = BigQueryInsertJobOperator(
        task_id="merge_orders",
        configuration={
            "query": {
                "query": "CALL merge_orders_to_target()",
                "useLegacySql": False,
            }
        },
    )

    load_staging >> merge_data

In a real project, the SQL would usually be maintained separately from the DAG rather than placing large SQL statements directly inside Python.

Make Your Incremental Data Pipeline Idempotent

Incremental pipelines introduce an important question:

What happens if the pipeline runs twice?

Imagine 100,000 records were successfully merged.

Then Airflow retries the task because of a temporary infrastructure failure.

If the pipeline simply performs an INSERT, the same records could appear twice.

That’s why incremental pipelines should be idempotent.

A correctly designed operation should be safe to retry.

Using a stable business key such as:

order_id

and a MERGE operation gives us a much safer pattern.

The idea is:

First run
    ↓
Insert/update records

Retry
    ↓
Same records
    ↓
Update existing records instead of duplicating them

Retries are normal in distributed systems. Your pipeline should be designed around that reality.

Add Data Quality Checks to Your Incremental Pipeline

One of the easiest mistakes in data engineering is assuming:

Airflow task succeeded = data is correct.

Not necessarily.

A file could contain 500,000 rows when you expected 1 million.

Or every customer_id could suddenly be null.

Add validation checks after the merge.

For example:

SELECT COUNT(*)
FROM analytics.orders
WHERE order_id IS NULL;

Expected result:

0

You can also check duplicates:

SELECT order_id, COUNT(*) AS record_count
FROM analytics.orders
GROUP BY order_id
HAVING COUNT(*) > 1;

These checks can become separate Airflow tasks.

If validation fails, downstream publishing or notifications can be stopped.

Partition BigQuery for Scale

Incremental loading reduces the amount of data processed, but BigQuery table design still matters.

For a large orders table, partitioning by date can significantly improve query efficiency.

For example:

orders
├── 2026-08-20
├── 2026-08-21
├── 2026-08-22
└── ...

Queries filtering on the partitioning column can avoid scanning unrelated data.

Clustering can also help when queries frequently filter or join on columns such as:

customer_id
order_id
country

The exact strategy depends on the workload, but the principle is simple:

Don’t only optimize the pipeline. Optimize the data model it produces.

What Happens When a Pipeline Fails?

A production pipeline needs a recovery strategy.

Consider this scenario:

GCS Load          ✓
Staging           ✓
Validation        ✓
MERGE             ✗

The raw file is still available in GCS.

That means we can retry the failed operation without losing the source data.

A good incremental pipeline should have:

  • Retries for transient failures
  • Idempotent transformations
  • Clear logging
  • Data quality checks
  • Failure notifications
  • Historical backfill support
  • Raw data retention

This changes the pipeline from a simple scheduled script into a system that can actually survive operational problems.

The Complete Flow

Putting everything together:

                       SOURCE
                          │
                          ▼
                   ┌─────────────┐
                   │     GCS     │
                   │ Raw Files   │
                   └──────┬──────┘
                          │
                          ▼
                   ┌─────────────┐
                   │   Airflow   │
                   │ Orchestrate │
                   └──────┬──────┘
                          │
                          ▼
                ┌───────────────────┐
                │ BigQuery Staging  │
                │ Incremental Batch │
                └─────────┬─────────┘
                          │
                     Validation
                          │
                          ▼
                   ┌─────────────┐
                   │   MERGE     │
                   └──────┬──────┘
                          │
                          ▼
                ┌───────────────────┐
                │ BigQuery Curated  │
                │    Orders         │
                └─────────┬─────────┘
                          │
                          ▼
                    BI / Analytics

Airflow controls the workflow.

GCS preserves the source.

BigQuery performs the data processing.

And the incremental strategy prevents us from repeatedly processing data that hasn’t changed.

Final Thoughts

Incremental loading is one of those concepts that looks simple until you try to make it reliable.

The basic idea is easy:

Don’t reload everything. Process what changed.

The engineering challenge is everything around that idea – identifying changes correctly, handling updates, preventing duplicates, recovering from failures, validating data, and making the process safe to retry.

Using Apache Airflow + GCS + BigQuery gives us a strong foundation for solving that problem.

The bigger lesson is that a production data pipeline isn’t just about moving data.

It’s about building a system that can answer three questions every day:

What changed?

Did we process it correctly?

Can we safely run it again if something goes wrong?

Once those questions are built into the architecture, incremental pipelines become much easier to scale, monitor, and maintain.

Frequently Asked Questions

1. What if a record is updated after it was already processed?

The pipeline can use an updated_at column to detect changes. On the next run, the changed record is picked up and updated in BigQuery instead of creating a duplicate.

2. . How do you handle records that are deleted from the source?

Deletes can be easy to miss because incremental files usually contain new or updated records, not deleted ones.

If BigQuery needs to reflect source deletions, the pipeline should capture them using a delete flag, CDC, or a separate deletion feed. Otherwise, deleted records may remain in the warehouse even though they no longer exist in the source.

3. Is incremental loading always better than a full load?

Not necessarily. Incremental loading is usually more efficient for large datasets, but full loads can still make sense for small tables or simple workloads where reliability and simplicity matter more than processing cost.

We use cookies to enhance your experience, analyze site traffic and deliver personalized content. Learn more about who we are, how you can contact us, and how we process personal data in our Privacy Policy.