Vibe Engines
YouTube

Vibe Engines is free, interactive AI engineering & system design — runs in your browser. Pick a learning path

System Design

Design a Data Integration Pipeline

Step 1 / 9

Learn system design by building a data integration pipeline that ingests a customer’s data step by step.

The numbers to beatAPI·SFTP·DB·CDCconnectorsrawstagingre-runnableon failure

The whole design, in writing

Every step of the build above, written out: the problem each piece solves, the option that was taken and the ones that were not, the numbers, and how it fails in production.

The big idea

What is a data integration pipeline?

A customer’s data lives across several systems — an API here, a nightly SFTP file there, a production database — and it’s messy, inconsistent, and huge. Your solution needs it in one clean, typed place. Copying it once by hand works for a demo and breaks the moment the data changes, grows, or arrives malformed.

Build a pipeline: connect to each source, stage the raw data, validate and map it onto a canonical schema (quarantining what doesn’t fit), transform and load it idempotently, and run it incrementally with reconciliation. It turns a customer’s mess into a warehouse you can trust.

Step 1 · The skeleton

Copy source to target

The naive version: Ingestion reads the source and writes straight to the Warehouse. It "works" once — until the source is paginated, the data is dirty, a run fails halfway, or tomorrow’s data changes. A one-shot copy is where every fragile integration starts.

Source SystemsAPI · SFTP · DBIngestionorchestrate the run
New in this step: Source Systems, Ingestion.

Stand up Ingestion as the orchestrator between the Source Systems and the Warehouse. For now it just moves data across. Everything that follows makes that move reliable, validated, and repeatable.

Why this piece earns its place

The skeleton hides an assumption worth naming out loud: that exactly one run is ever in flight. The first time an extract runs longer than the gap between runs — a slow source, a heavy day — the next run starts while the first is still working, and now two runs share one checkpoint. Each reads roughly the same window, each moves the mark, and whichever finishes last decides where the following run begins, so a window can be skipped entirely. Keyed loads fix a sequential retry; they do nothing about two writers racing on the same bookmark. So I make a run a first-class object before I make it reliable: a lock so a second run waits or is skipped rather than overlapping, a run_id stamped on every staged file and loaded row, and a final status the orchestrator writes down instead of anyone assuming. Without that id, every later question — which rows did this run touch, what did it hold back, what should reconciliation compare — has no way to even be asked.

What the new pieces do

Source Systemsclient
The customer’s systems that hold the data — a SaaS API, a nightly SFTP drop, a production database. Messy, inconsistent, and the only source of truth you get.
Ingestionbackend
The orchestrator that runs the pipeline: pull from the source, stage it, validate, transform, load. Coordinates the connectors and tracks each run’s checkpoint.

Step 2 · Get the data out

Connectors + staging

The data doesn’t come one clean way. One source is a REST API you must page through; another is an SFTP file dropped nightly; another is a database you read directly. And writing straight into the warehouse means a mid-run failure leaves it half-loaded.

ConnectorsAPI · SFTP · DB · CDCStagingraw landing zone
New in this step: Connectors, Staging.

Sources arrive as API pages, SFTP files, and DB reads — and a run can fail halfway. Where should extracted data land first?

  1. Writing directly means a failure partway through leaves the warehouse half-loaded and inconsistent, and you can’t validate before committing. You need a place to land raw data first.

  2. Connectors pull from each source (API pagination, SFTP, DB, CDC) into a raw staging zone. You validate and transform from staging, so a failed run never half-loads the target — you just re-run.

  3. In-memory staging can’t hold large extracts and loses everything on a crash mid-run. Staging must be durable so a failed run is safely re-runnable.

Add Connectors that pull from each source in its own way (API pagination, SFTP fetch, DB read, or CDC stream) into a raw Staging area. Validate and transform from staging, so the warehouse is only ever touched by clean, complete data.

Why this piece earns its place

Staging looks like a buffer and behaves like a record, so the decision this step really introduces is how long raw extracts live there. Keep one run’s worth and you have decoupled extract from load and nothing more: the day you fix a mapping bug, the only way to reprocess last month is to go back and pull it from the customer again — slow, and sometimes impossible, because the source overwrote the rows you wanted. Keep everything forever and you are now the custodian of a full second copy of the customer’s data, in your account, including whatever they later ask you to delete, and paying to hold it. I would size the window to cover the gap between a bad load and someone noticing it, partition staged files by run_id so a re-run replaces that run’s files instead of piling up beside them, and make deletion something the customer can ask for rather than something you discover you cannot do.

  • API·SFTP·DB·CDCconnectors
  • rawstaging
  • re-runnableon failure

What the new pieces do

Connectorsservice
Pulls data from wherever it lives — polling an API with pagination, fetching an SFTP file, reading a database, or streaming change-data-capture. Whatever access the customer grants.
Stagingcache
A landing area for raw, unmodified source data before anything touches the warehouse. Lets you validate and transform without half-loading the target on a failure.

Back of the envelope

one connector per source type
pagination, files, DB, change streams
land raw in staging first
never write the warehouse mid-run
durable staging
a failed run is safe to retry

Step 3 · Clean or quarantine

Validate + map

Customer data is a museum: inconsistent field names, mixed date formats, nulls that mean five things, values that don’t fit your types. Load it as-is and one bad row silently corrupts everything downstream that trusts the warehouse.

ConnectorsAPI · SFTP · DB · CDCStagingraw landing zoneQuarantinebad rows heldValidate + Mapto canonical schema
New in this step: Quarantine, Validate + Map.

Some source rows are malformed — a non-numeric age, a missing key. What do you do with them?

  1. Forcing garbage through with defaults corrupts the dataset — the analyst can’t tell a real zero from a defaulted one. Bad rows must be caught, not silently coerced into plausible-looking wrong data.

  2. Dropping rows loses data invisibly, so no one notices until a report is wrong weeks later. Failed rows have to be captured and surfaced, not discarded.

  3. Coerce each row to your typed canonical schema. Good rows continue; rows that fail go to a quarantine you can count and hand back to the customer — bad data becomes visible and contained, never silent corruption.

Add a Validate + Map step: coerce each staged row onto your canonical schema. Rows that pass continue to transform; rows that fail are routed to Quarantine — held for inspection and hand-back, never dropped or forced through.

Why this piece earns its place

Quarantine contains bad data, and then it quietly becomes its own problem: a table that only ever fills. Two things keep it a defense instead of a slower kind of drop. Every held row has to carry the raw row exactly as it arrived, the rule that rejected it, and the run that saw it — the point is to fix the mapping and replay, and a row held without its reason is just a deletion you can count. And the validator has to tell a bad row from a bad schema. One customer typo is a row-level event; a renamed column fails every row in the extract, and patiently grinding a whole day through quarantine turns a one-line mapping fix into a full replay. So past some failure share I stop the run, leave the checkpoint exactly where it is, and page someone, rather than quarantining diligently all night. Containment only counts if somebody drains it.

  • canonicalschema
  • quarantinenot drop
  • driftbecomes visible

What the new pieces do

Quarantinecache
Where rows that fail validation go — set aside for inspection and hand-back to the customer, never silently dropped. A rising quarantine rate is your schema-drift alarm.
Validate + Mapservice
Coerces each row onto your canonical schema — types, required fields, allowed values. Good rows pass; bad rows are routed to quarantine, not into the warehouse.

Back of the envelope

coerce to a canonical schema
types, required fields, allowed values
bad rows → quarantine
inspect + hand back, never silent
quarantine rate = drift alarm
a rename shows up instantly

Step 4 · Don’t re-copy everything

Incremental sync

Re-extracting the entire source every run is slow, expensive, and hammers the customer’s systems — and gets worse as the data grows. But you also can’t miss changes. You need only what’s new or changed since last time.

Ingestionorchestrate the runConnectorsAPI · SFTP · DB · CDCStagingraw landing zoneQuarantinebad rows heldSchedulerincremental + backfill
New in this step: Scheduler.

Re-copying the whole source every run is too slow and heavy. How do you pull only what changed?

  1. Record where the last run stopped — a max updated-at timestamp or a change-data-capture offset — and each run pulls only rows past it. Sync stays cheap and fast as the dataset grows, and nothing is missed.

  2. Running less often makes the data staler without fixing the core problem — each run still copies everything. You need incremental extraction, not a lower frequency.

  3. Full extraction still reads and transfers the entire source every run — the expensive part — even if you discard most of it. The savings have to come at extraction, via a checkpoint.

Add a Scheduler that tracks a checkpoint — a high-water mark (max updated-at) or a CDC offset — so each run pulls only what changed since the last one. Keep a separate backfill path to reload history when you need it.

Why this piece earns its place

A high-water mark quietly assumes two things about the customer’s source, and both are false somewhere. It assumes every write touches the updated-at column — a bulk script or a hand-run SQL fix usually does not, so those rows sit behind your mark and are never seen again. And it assumes rows become visible in timestamp order, which they do not: a long transaction stamps a row before your mark but commits after you have already read past it, so that row is skipped permanently and nothing flags it. The defenses are cheap. Overlap the window — start each run slightly behind the last checkpoint rather than exactly on it, which costs a little re-extraction and is harmless precisely because the load is keyed. Advance the checkpoint only after a run has fully succeeded. Take a CDC offset over a column whenever the customer will grant it, because it comes from the log rather than from a field anyone can forget to set. And note what no high-water mark can ever see: a row deleted at the source leaves nothing behind to pull, which is why removals have to be reported rather than inferred.

  • checkpointper run
  • CDC / high-waterincremental
  • backfillon demand

What the new pieces do

Schedulerservice
Runs the pipeline on a schedule and tracks a checkpoint (a high-water mark or CDC offset), so each run pulls only what changed — with a backfill path to reload history.

Back of the envelope

high-water mark or CDC offset
pull only newer/changed rows
cost scales with change
not with total data size
keep a backfill mode
reload history when needed

Step 5 · Load without doubling

Idempotent load

Runs fail and get retried; backfills overlap incrementals. If loading just appends, a re-run doubles rows and a partial retry leaves gaps. The warehouse has to end up correct no matter how many times a run happens.

Transformnormalize + enrichWarehousethe target
New in this step: Transform, Warehouse.

Runs get retried and backfills overlap incrementals. How do you load so a re-run never doubles rows?

  1. Appending doubles rows on any re-run or overlapping backfill, and there’s no way to correct it after the fact. Loads must be keyed so a repeat is a no-op or an update, not a duplicate.

  2. Load by merging on a stable business key: an existing key updates in place, a new key inserts. Re-running a batch or overlapping a backfill just re-writes the same rows — idempotent, so the warehouse is correct regardless of run history.

  3. Full delete-and-reload is safe but re-extracts and re-writes everything — throwing away the incremental sync you just built and hammering the source. Keyed upserts get idempotency without the full reload.

The Transform step loads into the Warehouse by upserting on a business key — an existing key updates, a new key inserts. A re-run or overlapping backfill re-writes the same rows instead of duplicating them: the load is idempotent.

Why this piece earns its place

All of this rests on the business key being stable, and in customer data it frequently is not. If the only thing identifying a record is an email address or a name-and-date pair, then the day the customer edits it the merge does exactly what you asked: it inserts a new row and leaves the old one sitting there, plausible and orphaned. Row counts still look reasonable, so nothing complains. So I spend real time choosing the key before I write the merge. Take the source’s own immutable identifier if it has one, however ugly, over anything a human types. If I have to synthesize a key, I hash only the fields the customer confirms never change, and I keep the mapping so a key can be traced back. And I treat a changed key as what it actually is — a delete plus an insert — which means something has to detect it, because an upsert by itself never will. A key chosen casually is a duplicate you meet months later.

  • upserton business key
  • re-run= no-op / update
  • nodoubles or gaps

What the new pieces do

Transformservice
Cleans and reshapes the validated data into the model the warehouse expects — deduping, joining, deriving fields — then loads it idempotently on a key.
Warehousedata
The destination — a data warehouse or database the analyst queries. Loads are keyed and idempotent, so a re-run never doubles rows or leaves gaps.

Back of the envelope

merge on a business key
existing updates, new inserts
idempotent by construction
safe to retry any run
backfill + incremental converge
same correct result

Step 6 · Prove it landed

Reconciliation + monitoring

The customer swears the data isn’t syncing. Without evidence, "it works" is your word against theirs. And schema drift — a renamed column, a new format — can silently break a run that looks green. You need proof and early warning.

Analyst / AppQuarantineWarehouseScheduler
New in this step: Analyst / App. · swipe to pan the diagram

Add reconciliation: compare source counts and checksums against the warehouse per run, and report any added / removed / changed records — turning "is it syncing?" into a fact. Monitor quarantine rate, row counts, and run duration, with alerts, so drift and failures surface in minutes. The Analyst now queries data you can vouch for.

Why this piece earns its place

Reconciliation fails in practice for a dull reason: the two sides are never compared as of the same moment. Count the source now and it has moved on since the extract, so the numbers differ on every run, and a report that is permanently red teaches everyone to stop opening it. Reconcile as of the window the run actually pulled — the checkpoint boundary it started and finished on — not as of whenever the report happens to run. Checksums carry the matching trap. Validation and transform change values on purpose: a date is reformatted, a type is coerced, a field is derived. Checksum a raw source column against its transformed warehouse column and every row mismatches by design. So compare key sets and counts first, since those survive transformation, and reserve checksums for fields carried through untouched or for the staged copy taken before transform. Then give the report an owner and somewhere the customer sees it, because this only earns trust if it is read.

  • counts + checksumsreconcile
  • quarantine ratedrift alarm
  • per-runreport

What the new pieces do

Analyst / Appclient
Whoever consumes the loaded data — a dashboard, an analyst, a model. They trust the warehouse to be correct and complete, which the pipeline has to earn.

Back of the envelope

reconcile source vs warehouse
counts, checksums, added/removed/changed
alert on quarantine spikes
schema drift shows up first here
a report earns trust
proof the migration landed

You did it

You just designed a customer data integration pipeline.

Source SystemsAnalyst / AppIngestionConnectorsStagingQuarantineValidate + MapTransformWarehouseScheduler
The finished design, end to end. · swipe to pan the diagram

Everything you assembled, in order

  • Ingestion orchestrates the run between messy sources and a clean warehouse.
  • Connectors pull from API / SFTP / DB / CDC into a raw staging area first.
  • Validation coerces to a canonical schema; bad rows are quarantined, never dropped.
  • A checkpoint (high-water mark or CDC offset) makes each run incremental, with a backfill path.
  • Loads upsert on a business key — idempotent, so retries and overlaps never double or gap.
  • Reconciliation proves the data landed; quarantine-rate alerts catch schema drift early.

Where an interviewer pokes next

Getting the boxes right is the easy half. These are the questions that separate a candidate who drew the diagram from one who has run the thing. Answer each one out loud before you open it.

  1. Why stage raw data before loading it into the warehouse?

    Staging decouples extraction from loading so a failure has a safe boundary. If you write directly to the warehouse and a run dies halfway, the target is left half-loaded and inconsistent, and you had no chance to validate before committing. Landing raw data in staging first means you validate and transform from a stable snapshot, the warehouse is only ever touched by clean complete data, and a failed run is recovered by simply re-running — the warehouse was never corrupted.

  2. How do you handle schema drift from the customer’s source?

    You validate every row against a canonical schema at the boundary, so a renamed column or changed format shows up as a spike in the quarantine rate rather than as silently-wrong data in the warehouse. You alert on that quarantine rate, which is your earliest signal that the source shape changed. Then you update the mapping and reprocess the quarantined rows. The key is that drift becomes a visible, contained event — not an invisible corruption that surfaces weeks later in a wrong report.

  3. Why upsert on a business key instead of appending?

    Because runs get retried and backfills overlap incremental syncs, so the same rows will be loaded more than once. Appending doubles those rows with no way to fix it; upserting (merge on a stable business key) makes a repeat load a no-op or an update instead of a duplicate. That makes the load idempotent, which means the final warehouse state depends only on the data, not on how many times or in what order the runs happened — the property that makes a pipeline safe to retry.

  4. How do you prove to a skeptical customer that the migration worked?

    Reconciliation. For each run, compare counts and checksums between the source and the warehouse, and produce a diff of added, removed, and changed records. That turns "is it syncing?" from an argument into a fact — you can show exactly which records match and which don’t. A clean reconciliation report is frequently the single artifact that converts a doubtful customer, and a non-clean one immediately localizes the problem (a specific set of records) instead of leaving you to guess.

  5. How do you make incremental sync reliable?

    Track a checkpoint — a high-water mark like max(updated_at), or a change-data-capture offset — and persist it only after a run fully succeeds. Each run pulls rows past the checkpoint, loads them idempotently, then advances the checkpoint. If a run fails, the checkpoint isn’t advanced, so the next run safely re-pulls the same window (idempotent loads make that harmless). Keep a separate backfill mode for reloading history. The combination — checkpoint + idempotent load — is what lets sync be both cheap and exactly correct.

Check yourself — the answers, and why

Seven steps in, these are the calls you should be able to make cold. Pick one, then read why.

  1. Extracted data lands in a staging area first because…

    Validate/transform from staging; the warehouse is only touched by clean, complete data.

  2. A row that fails validation should be…

    Quarantine makes bad rows visible and recoverable; a spiking rate flags schema drift.

  3. Incremental sync avoids re-copying everything by…

    Each run pulls only rows past the checkpoint; cost scales with change, not total size.

  4. Loads upsert on a business key so that…

    Merge on a stable key; retries and overlapping backfills converge to the same correct state.

  5. To prove the data actually landed, you…

    A reconciliation report turns "is it syncing?" into a fact and localizes any mismatch.

How you’d open this design in an interview

Before any boxes: agree what it must do, pin the qualities that shape everything, then build — naming each trade-off as you make it. The walkthrough above is that exact order.

What it must do

Agree on these before drawing a single box.

  • Connect: pull from each source in its own way — API pagination, an SFTP file drop, a database read, or a CDC stream.
  • Validate & map: coerce each row onto a canonical schema; hold rows that don’t fit in quarantine.
  • Sync incrementally: track a checkpoint so each run pulls only what changed, with a backfill path for history.
  • Load idempotently: upsert on a business key so retries and overlapping backfills never double or gap rows.
  • Reconcile: compare source vs warehouse counts and checksums per run, reporting added / removed / changed records.

The qualities that shape everything

Each one names the mechanism that buys it.

A reliable, repeatable move, not a one-shot copy
Ingestion orchestrates the run — pull, stage, validate, transform, load — and tracks each run’s checkpoint instead of a fragile hand copy.
A failed run never half-loads the target
Connectors land raw source data in a durable staging area before the warehouse is touched, so a mid-run failure leaves the target untouched and the run re-runnable.
Bad data never silently corrupts downstream
Every row is coerced to a canonical schema at the boundary; failures route to quarantine, so drift is visible and contained instead of silent corruption.
Sync cost scales with change, not total size
A scheduler tracks a high-water mark or CDC offset so each run pulls only what changed since the last, with a separate backfill path.
Correct no matter how many times a run happens
Loads upsert on a business key, so retries and overlapping backfills re-write the same rows rather than doubling or gapping them.
Prove the data actually landed
Per-run reconciliation compares source vs warehouse counts and checksums and reports diffs, turning "is it syncing?" into a fact.

The trade-offs you say out loud

Senior signal isn’t the boxes — it’s naming what you gave up and why it was the right price.

Land raw in a durable staging area over write each source straight into the warehouse

A direct write leaves the warehouse half-loaded on a mid-run failure and gives no chance to validate first. Staging decouples extract from load, so a failed run is recovered by re-running — not a cleanup.

Validate to a canonical schema + quarantine over load malformed rows with defaults

Defaults corrupt the dataset — an analyst can’t tell a real zero from a defaulted one — and silently dropping loses data invisibly. Quarantine makes bad rows countable, recoverable, and a drift alarm.

Incremental extraction by checkpoint over re-copy the whole source each run

Full extraction reads and transfers everything — the expensive part — even if you discard most of it. A high-water mark or CDC offset makes each run cheap and misses nothing.

Upsert on a business key over append every extracted row

Appending doubles rows on any re-run or overlapping backfill with no way to fix it. A keyed merge makes a repeat a no-op or update — idempotent, so the warehouse is correct regardless of run history.

The answer, out loud

What a strong answer to “Design a Customer Data Integration Pipeline” sounds like, first question to last trade-off. It is about 7 minutes of talking; the whiteboard and the interviewer fill the rest of the 45. Read it aloud once, then close the page and give it yourself.

  1. 0–4 min

    What this actually is

    Before I draw anything I want to be clear about the job. A customer has data spread across a few systems — a SaaS API, a file that lands overnight, a production database — and our product needs it in one clean, typed place. The one-time copy is the easy part, and it is not the job. The job is making that move every day, on data that is messy and changes shape without anyone telling you, without corrupting the target. So I am designing for the hundredth run, and assuming the source is imperfect and the access I get is whatever the customer will grant me.

  2. 4–9 min

    The skeleton first

    The skeleton is an ingestion service sitting between the source systems and the warehouse. Right now it only moves rows across, and I would genuinely build that first, because it forces the unglamorous conversations early — which systems, whose credentials, who approves the access, what the customer considers sensitive. Everything after this makes the same move reliable rather than adding new capability, which is the honest shape of this kind of work. I would also make a run a real object from day one, with an identity and a recorded outcome, because nearly every question people ask later is a question about a particular run.

    Built in step 1: Copy source to target
  3. 9–16 min

    Pull it out, land it raw

    Then I split that move in two. Connectors pull from each source the way that source actually works — paging through a REST API, fetching the file that shows up overnight, reading the database, or consuming a change stream if we can get one — and they land the data raw in a staging area. Nothing writes the warehouse directly any more. That one boundary buys most of the reliability in this design: if a run dies halfway, staging is a mess and the warehouse is untouched, so recovery is a re-run instead of a cleanup with the customer watching. It also gives me something stable to validate against, rather than validating a source that keeps moving under me.

    Built in step 2: Connectors + staging
  4. 16–23 min

    Put a schema at the door

    Next I would put a schema at the boundary. Customer data arrives with inconsistent field names, mixed date formats, nulls that mean five different things, and values that will not fit the types we promised downstream. So every staged row gets coerced onto a canonical schema, and rows that do not fit go to quarantine — not into the warehouse, and not into the bin. I want to be explicit about why quarantine rather than defaults, because it is the decision people get wrong: a defaulted value is indistinguishable from a real one once it lands, and a silently dropped row becomes a wrong number in a report that nobody can trace back. A held row I can count, show the customer, and reload once the mapping is fixed.

    Built in step 3: Validate + map
  5. 23–30 min

    Only pull what changed

    At this point it works, but it re-copies everything, which is slow for us and rude to the customer’s production systems. So a scheduler tracks a checkpoint — a high-water mark on an updated-at column, or a change-data-capture offset where we can get one — and each run pulls only what is past it. The cost of a sync then scales with how much changed rather than with how much exists, which matters because the dataset only ever grows. I would keep backfill as a separate, deliberate mode rather than a flag on the normal path, because backfill is the thing you reach for during an incident and you want it to be boring when you do.

    Built in step 4: Incremental sync
  6. 30–36 min

    Load so a re-run is safe

    Incremental runs and retried runs both mean the same rows get loaded more than once, so the load has to not care. Instead of appending, the transform step merges into the warehouse on a business key: an existing key updates in place, a new key inserts. A retry, or a backfill that overlaps the nightly run, then rewrites the same rows instead of doubling them. That is what makes the pipeline operable rather than just correct on paper — I can re-run anything at any time without first working out how far the previous run got before it fell over, which is exactly the question you cannot answer at two in the morning.

    Built in step 5: Idempotent load
  7. 36–42 min

    Prove it landed

    Last, I would prove it. Every run reconciles the source against the warehouse — counts and checksums, and a list of what was added, removed and changed — because otherwise is it syncing is an argument rather than a fact, and it is an argument you tend to lose. Alongside that I would watch the quarantine rate, the row counts and the run duration. The quarantine rate is the most useful alarm on the board, because it is where a renamed column shows up first, minutes after the customer changed something they did not think to mention to us.

    Built in step 6: Reconciliation + monitoring
  8. 42–45 min

    Where the cost sits

    So the shape is connect, stage, validate, sync incrementally, load idempotently, reconcile. The trade-off running through all of it is that every guarantee costs something real: staging is a second copy of the customer’s data to hold and secure, validation means some rows stop at the door and a human has to deal with them, and reconciliation is work we pay on every run forever to earn trust we cannot otherwise ask for. On a deployment I think that is the right trade, because the alternative fails quietly. With more time I would work through what happens when the business key itself changes — an upsert can never see that on its own — and how quarantined rows get handed back to the customer in a form they can actually act on.

What this teaches

Learn system design by building a data integration pipeline that ingests a customer’s data step by step. An interactive guide covering source connectors (API, SFTP, database, change-data-capture), staging, validation and mapping to a canonical schema, quarantining bad rows, incremental sync, idempotent loads and backfill, and reconciliation — the pipeline a forward deployed engineer builds on almost every deployment.

Key takeaways

  • Ingestion orchestrates the run between messy sources and a clean warehouse.
  • Connectors pull from API / SFTP / DB / CDC into a raw staging area first.
  • Validation coerces to a canonical schema; bad rows are quarantined, never dropped.
  • A checkpoint (high-water mark or CDC offset) makes each run incremental, with a backfill path.
  • Loads upsert on a business key — idempotent, so retries and overlaps never double or gap.
  • Reconciliation proves the data landed; quarantine-rate alerts catch schema drift early.

Concepts covered

  • What is a data integration pipeline?
  • Copy source to target
  • Connectors + staging
  • Validate + map
  • Incremental sync
  • Idempotent load
  • Reconciliation + monitoring
built to ingest a customer’s mess, not memorized — connect, stage, validate, load, reconcile.
Finished this one? 0 / 65 System Designs done
NextDesign Webhook Ingestion at Scale Next in “Become a Forward Deployed Engineer” · step 39 of 63

Explore the topic

See this alongside everything else on the same subject — handbooks, system designs, challenges and tools, in one place.

More System Designs