Data Processing

What happens to raw data before a model can use it: ingestion, validation, transformation, ETL vs ELT, batch vs streaming, and what silently breaks.

Published Updated

On this page

Definition

Data processing is everything that happens to data between the moment a system records it and the moment a model, a query or a dashboard can use it: pulling it out of the systems that produced it, checking it against an expected shape, quarantining or repairing what fails, joining it to other data, and writing the result somewhere durable enough to build on. It is where the time goes — Anaconda's 2020 State of Data Science survey put data loading and cleaning at 45% of a data scientist's working week, the largest single block in the study, larger than model selection, training or deployment.

The reason that share is so stubborn is not that cleaning is hard. It is that cleaning is never finished. Model training is a command you run again unchanged; a pipeline sits downstream of systems owned by other teams, and those systems change shape without telling you. A column is renamed, a field that was always an integer starts arriving as the string "N/A", a partner begins sending timestamps in local time instead of UTC. None of these raise an error. The job succeeds, the row count looks normal, and something quietly wrong flows into the next model.

So data processing is engineering, and it is judged by an engineering test: does the same input produce the same output every time the job runs? It has no opinion about what the numbers mean — only that they are the numbers the source actually reported. Asking what they mean is Data Analysis.

How It Works

A production pipeline is a sequence of stages, and the useful way to think about each one is: what does it guarantee, and what happens when it is run twice?

Ingestion moves data from a source you do not control into storage you do. The two shapes are pull and push. A pull extracts on a schedule, usually with a high-water query such as WHERE updated_at > :last_run, which is simple and has a well-known hole: it never sees rows that were hard-deleted, because a deleted row has no updated_at to exceed the mark. Change data capture closes that hole by reading the database's own replication log, so deletes arrive as events. A push inverts the relationship — producers write to a durable log such as Kafka and consumers read at their own pace, which decouples a slow downstream job from the system generating the traffic.

Landing writes the extract to immutable storage before anything interprets it, partitioned by ingestion date. This is the cheapest insurance in the whole system. If the transformation logic has a bug, you fix the logic and re-run it against data you still hold. If you transformed on the way in, the bug destroyed the evidence and the only remedy is re-extracting from a source that may have aged the history out.

Validation is where a pipeline stops being hopeful. The checks that earn their keep are boring: a column is not null, a key is unique, a categorical field takes one of a known set of values, a numeric field falls in a plausible range, a foreign key resolves. The design decision is what a failure does. Failing the whole batch keeps the warehouse consistent and wakes someone up; quarantining the bad rows into a side table keeps the pipeline running and defers the problem. Both are defensible; silently coercing the bad rows is not, and it is the default behaviour of almost every permissive parser.

Put a number on why this matters. At 50 million events a day, a 0.1% malformed rate is 50,000 broken records daily and roughly 1.5 million across a 30-day training window. If those errors are random, a model shrugs them off. If they all come from one mobile SDK version, one region or one partner feed, they are not noise — they are a correlated signal, and the model will happily learn to predict from the shape of your own pipeline.

Transformation does the deduplication, joins, aggregation, type coercion and encoding — including turning categories and text into the numeric form models require, which is vectorization. The order of this stage relative to loading is the ETL / ELT distinction, and the order flipped for an economic reason rather than a technical one. In a classic warehouse, storage and compute were the same appliance bought by capacity, so you transformed first and loaded only what you had decided to keep. Cloud warehouses — BigQuery in 2011, Snowflake generally available in 2015 — separated the two, and object storage now costs on the order of two cents per gigabyte-month (S3 Standard, at the time of publication). Keeping a terabyte of raw JSON for a year costs a couple of hundred dollars; discovering in month nine that you needed a field you discarded in month one costs a quarter.

Publishing and reprocessing is the stage most first drafts forget, and it dictates how the earlier ones must be written. Suppose your daily job handles 50 million rows in 20 minutes. Reprocessing three years of history is about 55 billion rows — roughly 1,100× the daily volume, which at the same throughput is 365 hours, or over 15 days of serial compute. You can fan it out across 100 workers and finish in under four hours, but only if each partition can be rebuilt independently and rebuilding it twice is harmless. That property is idempotency, and it is not a nicety. Delivery in real systems is at-least-once: a consumer that writes its output and crashes before committing its offset will replay the batch. At 500 batches a day and one retry per 10,000, you duplicate a batch roughly every 20 days. A duplicated 100,000-row batch is 0.2% of the daily total and invisible in any global count — but for every user in that batch, every per-user sum is exactly double.

Types

Batch, micro-batch and streaming are a genuine typology: they are three distinct execution models with different fault-tolerance stories, and practitioners use the words to mean specific things.

Batch processes a bounded input — yesterday, the last hour, one partition — on a schedule. It is the cheapest per record because the whole set is visible at once, so the engine can sort, shuffle and join without keeping long-lived state, and a failed run is simply run again. The cost is freshness, and the arithmetic is unforgiving: a job that starts at 02:00 and covers up to midnight means the number on a dashboard at 09:00 describes a world that is nine hours old.

Micro-batch runs the same programming model over windows of seconds to minutes. Spark Structured Streaming works this way by default, and the appeal is that it inherits batch's fault tolerance — each micro-batch either commits or is retried — while cutting latency by two or three orders of magnitude. It is the pragmatic middle, and most "real-time" pipelines in production are this.

Streaming processes each record as it arrives with continuously maintained state, as in Flink or Beam. Two costs are systematically underestimated. First, capacity: a batch job is sized for the day's average, while a streaming job must absorb the peak minute, so a workload whose peak is five times its mean pays for roughly five times the average capacity around the clock. Second, duplication of logic — the same business rule now exists in a batch implementation and a streaming one, and they drift. That is precisely the objection Jay Kreps raised to the Lambda architecture in 2014 when proposing what became known as Kappa: run one code path over a replayable log and reprocess by replaying it, rather than maintaining two.

Streaming also forces the hardest question in the whole discipline: what is "now"? Events carry an event time (when the thing happened) and a processing time (when your system saw it), and these diverge — a phone in airplane mode buffers events and delivers them three hours late. A windowed aggregation cannot wait forever, so it closes on a watermark: an assertion that nothing older than time T will still arrive. The trade is exact and has no free setting. A 10-minute watermark on hourly windows publishes quickly and drops that phone's events, or forces you to restate a figure you already published. A 6-hour watermark captures them and makes every published number six hours stale. This is the model formalised in the Dataflow paper (Akidau et al., VLDB 2015), which is why event time, watermarks and triggers are named the same way across Beam, Flink and Spark.

Real-World Applications

The modern warehouse stack is two named tools doing the two halves. Apache Airflow, built at Airbnb in 2014, schedules pipelines as directed graphs — and the detail worth noticing is that backfill is a first-class command, which tells you the designers assumed re-running history is routine rather than exceptional. dbt turned the T of ELT into version-controlled SQL with tests attached: not_null, unique, accepted_values and relationships assertions live in the repo next to the model that depends on them, so a data quality rule is reviewed, diffed and blamed like any other code.

On the ingestion side, Apache Kafka — built at LinkedIn and open-sourced in 2011 — made the durable, replayable log the standard substrate: a consumer that got it wrong rewinds its offset and reads the same bytes again instead of asking the source system to re-export. Google Cloud Dataflow and Apache Flink implement the event-time semantics described above, which is what makes a windowed aggregate over out-of-order data a configuration choice rather than a research project.

Storage picked up the same guarantees. Apache Iceberg, opened by Netflix in 2018, alongside Apache Hudi from Uber and Delta Lake from Databricks, put atomic commits and schema evolution on top of object storage: overwriting a partition becomes all-or-nothing, and a renamed column tracks a stable column ID rather than its name — the difference between a rename and an accidentally all-null column. Uber's Michelangelo platform (2017) popularised the feature store for the adjacent problem: when a feature is computed by a batch job for training and by a separate service at inference time, the two implementations disagree, and the model in production sees inputs it never trained on.

Key Concepts

Exactly-once is a property of your sink, not of your transport. No messaging system can promise a message is delivered exactly once — the acknowledgement itself can be the thing that is lost. What you actually get is at-least-once delivery plus a way to make repetition harmless: a merge on a natural key, an overwrite of a whole partition, or a commit that ties the write and the offset together in one transaction. You design your way to exactly-once semantics; you do not buy them.

Schema-on-write versus schema-on-read decides when you find out. A warehouse that enforces types at load time rejects the bad record at 02:00 with a stack trace naming the column. A lake that accepts anything and interprets at query time takes the same record silently, and you learn about it weeks later when an analyst notices a column is 40% null. Schema-on-read is genuinely more flexible for semi-structured data; the flexibility is paid for in detection latency, which is why table formats and data contracts have been pulling the lake back toward enforcement.

Challenges

A silent schema change is the characteristic failure of this discipline because nothing fails. An upstream team changes a field from a number to a string, or renames user_id to userId. A permissive JSON parse yields null; the loading job reports success and a normal row count; the feature pipeline imputes the median for the nulls, as it was told to; the model retrains on a column that is now a constant. Every dashboard is green. The only visible symptom is a slow drift in a quality metric days later, which is exactly why monitoring has to watch input distributions and null rates rather than only job exit codes.

Time zones destroy aggregates in ways that look like seasonality. Bucket a daily total by local wall-clock time and the spring daylight-saving day has 23 hours while the autumn one has 25 — so the feature dips about 4.2% (one hour in 24) on one day a year and rises about 4.2% on another. Worse, 01:30 local occurs twice in autumn, so any key of the form (user_id, local_hour) collides and one of the two rows wins silently. Store UTC plus the originating zone and convert at the edge where a human reads it; every conversion earlier than that is a chance to lose information you cannot reconstruct.

A null encoded as a valid value is worse than a missing one. A source that writes 0 for "age not provided" does not just shift the mean — with 8% of rows affected, a column whose true mean is 42 reports 38.6 — it creates a fake signal. A gradient-boosted tree will find a genuinely predictive split at age < 1, because "this field is absent" correlates with the partner feed that produced the record, and that feed correlates with the label. The model has learned to identify its own data sources. It scores well in cross-validation and collapses in production the first time the feed mix changes, which is leakage arriving through the ingestion layer rather than the modelling code.

Reprocessing is a correctness problem, not just a cost problem. Re-running last quarter with today's transformation logic produces a table that no longer matches the numbers already reported from it. Any pipeline whose output someone has quoted needs an answer to "which version of the logic produced this row", which is what lineage metadata and versioned table snapshots exist to record.

Iceberg is settling into the role of interchange layer between engines — Databricks acquired Tabular in 2024, and the major warehouse vendors now read and write the format — which decouples the choice of query engine from the physical layout of the data.

Data contracts move the schema check to the producer: rather than a consumer discovering a breaking change after it lands, the producing service declares a versioned schema and its own CI fails on an incompatible edit. It is an organisational fix wearing a technical hat, and the only approach that attacks the silent-schema-change failure at its cause rather than its symptom.

The heaviest new load is unstructured. Pipelines built for rows are being asked to ingest PDFs, HTML and scanned documents for retrieval-augmented generation, where the failure modes are unfamiliar: a table split across a page boundary, a repeated header absorbed into a chunk, a footnote glued to the wrong sentence. Language models are now often the parser as well as the consumer, which makes extraction probabilistic — and a probabilistic extractor needs the same validation discipline as any other unreliable source.

Code Example

The difference between a pipeline you can re-run and one you cannot fits in two SQL statements.

-- Not idempotent: a replayed batch appends the same rows a second time.
-- The result depends on how many times the job ran, which is not a property
-- of the data.
INSERT INTO orders_clean
SELECT order_id, user_id, amount_cents, event_time
FROM staging_orders
WHERE ingest_date = DATE '2026-07-21';

-- Idempotent: the natural key decides. Running this once, twice or ten times
-- leaves the table in exactly the same state.
MERGE INTO orders_clean AS t
USING (
    SELECT order_id, user_id, amount_cents, event_time
    FROM staging_orders
    WHERE ingest_date = DATE '2026-07-21'
    -- The source of a MERGE must be unique on the join key, or the statement
    -- errors (or, worse, picks a row arbitrarily). At-least-once delivery
    -- means duplicates are expected, so collapse them here, deterministically.
    QUALIFY ROW_NUMBER() OVER (
        PARTITION BY order_id ORDER BY event_time DESC, ingest_sequence DESC
    ) = 1
) AS s
ON t.order_id = s.order_id
WHEN MATCHED THEN UPDATE SET
    amount_cents = s.amount_cents,
    event_time   = s.event_time
WHEN NOT MATCHED THEN INSERT (order_id, user_id, amount_cents, event_time)
    VALUES (s.order_id, s.user_id, s.amount_cents, s.event_time);

QUALIFY is warehouse syntax (Snowflake, BigQuery, Databricks); elsewhere the same deduplication is a subquery filtering on ROW_NUMBER() = 1. The tie-breaker matters as much as the ranking: ordering by event_time alone leaves two records with identical timestamps in a coin-flip, so the job becomes non-deterministic in exactly the case — duplicates — it exists to handle.

Frequently Asked Questions

Because it is never done once. Every source system changes shape without warning — a renamed column, a widened type, a new null convention — so the pipeline has to be re-run, re-validated and often re-written, while the model training step is a command you run again unchanged.
Data processing is engineering: it is judged on whether the same input produces the same output every time it runs. Data Analysis is the act of asking a question of the result. Processing has no opinion about what the data means; it only guarantees that the numbers are the ones the source actually reported.
ETL transforms data before loading it into the warehouse; ELT loads the raw data first and transforms it with the warehouse's own compute. The order flipped once cloud object storage made keeping the raw copy cheap — under ELT a transformation bug is fixed by re-running SQL, because the evidence still exists.
A pipeline is idempotent when running the same batch twice leaves the same result as running it once — typically by merging on a natural key or overwriting a whole partition rather than appending rows. It matters because delivery is at-least-once in practice: a job that crashes after writing but before committing its offset will be replayed.
A watermark is the system's assertion that no more events older than a given event time will arrive, which is what lets a windowed aggregation close and emit. It is a bet, not a fact: set it short and late records are dropped, set it long and every published number is that much staler.
At 50 million events a day it is 50,000 broken records daily and about 1.5 million across a 30-day training window. Whether that matters depends entirely on whether the breakage is random noise or correlated with one source, one region or one app version — correlated errors teach the model a pattern that does not exist outside your pipeline.

Continue Learning

Explore our use-case guides and prompts to deepen your AI knowledge.