About Expertise Work Projects
Hosted Monitoring & Dashboards Self-Hosted Observability Stack Bulk Document Data Extraction Email Deliverability Diagnosis & Repair SEO Migration Recovery AWS Security Review VPS Hardening & ModSecurity Cloud Architecture & Resilience Review SSL & Server Configuration Container Security Review DNS & Email Troubleshooting DevOps Deployment & Rollback Review WordPress Hardening Retainer Data Pipeline Rerun Review
Free Tools
Website Health Check Email Domain Health Check DNS Health Check SSL Certificate Checker Redirect Chain Checker Robots.txt Checker XML Sitemap Validator Docker Compose Checker WordPress Security Check AWS IAM / S3 Policy Checker Domain Registration Lookup Uptime Monitoring Trial Downtime Cost Calculator AWS Cost Estimator Cloud Architecture Self-Assessment DevOps Engagement Builder Self-Managed VPS vs Managed AWS
Blog Certifications Hire Me

Your orchestrator will run that load again. Nothing anywhere checks what the second run did.

Retries, backfills and manual re-triggers are automatic and they are meant to be. What no scheduler, no bookmark and no watermark does is look at the table afterwards and ask whether running twice changed anything it should not have. Every mechanism built for this tracks what was read. None of them tracks what was written.

Nothing on this page has measured your pipeline, and I am not going to pretend otherwise. There is no free checker here that finds this, because the fault lives inside a system nobody outside it can reach. What follows is the argument, the failure modes, and both of the things a real drill hands back — so you can decide whether it is worth running before you talk to anyone about it.

Most of this is free, and the free versions are good

Every page in this section starts by naming what you can get for nothing, because a service that hides its free competition is not worth buying. Here the free competition is unusually strong.

A language model will review your pipeline properly
Paste in a DAG, a dbt project or a Glue script and you will get a competent critique in seconds — the anti-patterns, an idempotency checklist, a corrected merge statement, and a clear explanation of every concept on this page. Pipeline code is text, and text is what models are best at. Do that first. If it finds something obvious, fix it and you may not need this at all.
dbt ships tests, and Great Expectations and Soda Core are open source
unique, not_null, relationships, accepted_values, plus anything you write yourself — free, in the tool your team already runs. The open-source frameworks go considerably further than anything a consultant would hand-write. Install them yourself; you do not need me for that.
Airflow gives you retries, backfill and catchup, and documents the right answer
Its best-practices page tells you plainly what to do about all of this. So does the Glue documentation. The advice is free, correct and public, and it is quoted below rather than paraphrased.
My own free tools give you nothing at all here, and that is worth saying plainly
There are seventeen checkers on this site and not one of them touches a data pipeline. No finding, no id, no report. Every other page in this section starts by pointing you at a free checker of mine; this one has none to point at, because the fault is not visible from outside the system it lives in.

Notice what none of that list contains: a way to find out whether your pipeline survives being run twice. Every entry either describes the problem or tests the data at rest. None of them runs your load again and counts what changed.

A pipeline is not code. It is code plus the state it has already written.

The first run writes into an empty table. Every run after that writes into a table already holding the result of the last one — and that is the case nobody rehearses, because the only place it exists is the place nobody wants to experiment in.

What every mechanism tracks Bookmarks, watermarks, high-water columns, _loaded_at timestamps, last modified times. All of them answer one question: what have I read?
What none of them tracks What did I already write, and what happens if I write it again? No vendor can ship that feature, because your target’s correctness is defined by your business logic and not by theirs.

It has to be caused, and the cause is routine. A retry after a transient failure. A backfill after a bad day. A late-arriving batch. Somebody re-triggering the job because the numbers looked wrong. The orchestrator does this automatically and by design — and the state that breaks a rerun does not exist until the pipeline has been running long enough to have written something.

And the damage is downstream, where nobody is looking. A second run does not only touch the target table. It can rebuild a materialised view something is reading, re-trigger a webhook, re-send an export, or re-push rows into a system that emails your customers. Almost nobody has written that list down, which is why it is one of the two things a drill hands back.

Three vendors document this limitation in their own products

Not a competitor comparison and not a criticism. These are good tools, and each of them is straightforward about the same boundary. Quoted rather than paraphrased, with the page that carries each sentence linked so you can check it in one click.

  • AWS — Glue job bookmarks, the feature built for rerun safety When you rewind or reset a bookmark, AWS Glue does not clean the target files because there could be multiple targets and targets are not tracked with job bookmarks. Only source files are tracked with job bookmarks. This is the sentence the page rests on. The mechanism sold as rerun safety is explicitly one-sided, and AWS says so in the documentation for that mechanism. The same page adds that in some cases, you might have enabled AWS Glue job bookmarks but your ETL job is reprocessing data that was already processed in an earlier run — so it sometimes falls short on the half it does cover. Tracking processed data using job bookmarks
  • Apache Airflow — best practices, “Creating a task” Airflow can retry a task if it fails. Thus, the tasks should produce the same outcome on every re-run. Read the modal verb. Should, not does. The retry is automatic; whether it is safe is entirely the author’s responsibility, and nothing in Airflow checks it. The scheduler will run a double-inserting task twice quite happily. The same section is specific about how: Do not use INSERT during a task re-run, an INSERT statement might lead to duplicate rows in your database. Replace it with UPSERT. It also says Read and write in a specific partition. Never read the latest available data in a task. and that the Python now() function should never be used inside a task, especially to do the critical computation, as it leads to different outcomes on each run. Airflow best practices
  • dbt — incremental models, two documented defaults If you add a column to your incremental model, and execute a dbt run, this column will not appear in your target table. That is the default behaviour and it is silent. dbt states on the same page that this is on_schema_change: ignore, which is set by default. Separately, on merge keys: Columns used in this way should not contain any nulls, or the incremental model may fail to match rows and generate duplicate rows. Carry that sentence with its condition or not at all. dbt says may, it is written about a unique_key supplied as a list of column names, and not every incremental strategy even uses a unique_keyinsert_overwrite works on partitions rather than rows and does not. dbt incremental models

Weight them honestly. The Glue sentence is the primary one, because it is a property of the system rather than a setting anyone can change. Airflow’s should and dbt’s two defaults support it. All three are quoted from the page that carries them, and all three were re-opened and checked against the source text on 23 August 2026.

Six ways a second run changes something it should not

None of these is exotic. Every one is a documented default or a habit that reads as perfectly normal in review, which is exactly why reading the code is not enough to find them.

An INSERT where the target needed a merge
The commonest one by a distance, and the one Airflow tells you about on its own best-practices page. The first run inserts. The retry inserts again. Nothing errors, because inserting rows is exactly what the statement was asked to do. The repair is an upsert keyed on something that actually identifies a row.
A merge key that can be null
The write is already a merge, so this one looks fixed. But a null in the key means the match silently fails and the row is written as new. dbt documents this against its own unique_key and the wording is careful — it says the model may generate duplicate rows. The repair is either a key that cannot be null or a single surrogate key, which is what dbt itself recommends.
A now() inside a transformation
The load is scoped to “today”, so a rerun tomorrow writes to a different bucket than the run it was meant to repeat, and a backfill writes every historical day into the present. Airflow’s own guidance is that this function should never be used inside a task for critical computation. The repair is a fixed interval passed in, not read from the clock.
Reading “the latest” instead of a fixed partition
The task reads whatever is newest at the moment it runs. Rerun it a week later and it reads a week’s more data, so the second run legitimately produces a different answer and neither is wrong. The repair is reading and writing one named partition, which is the same advice from the same page.
A new column that never arrives
Not a duplicate-row fault at all — the opposite. Add a column to an incremental model and the default behaviour writes the table without it, with no error. The rows look right and a field is quietly missing. The repair is choosing an on_schema_change value on purpose rather than inheriting the one that stays quiet.
A sink nobody counted
The target table is rarely the only thing a load writes. A materialised view rebuilds, a BI extract refreshes, a webhook fires, a partner export is dropped again, a reverse-ETL sync pushes rows into a system that emails people. The repair starts with the list, because you cannot fence what you have not written down.

Every one of those is fixable and most are a small change. The hard part is never the repair — it is knowing which one you have, on which stage, before it costs you a quarter of restated numbers.

Two artifacts, and here is what both of them look like

Rendered rather than described, because a service page that describes its own output is asking you to take the interesting part on trust.

The rerun ledger

One scheduled load, run a second time against a clone carrying the previous run’s data. Per stage, never as a total — the total is the number that hides everything, because a stage that duplicates and a stage that drops can sum to a delta that looks fine.

Stage Rows before Rows after Expected delta Actual delta What the job reported Why
Stage 1 — raw landing 4,812,004 4,812,004 No change No change Success, 0 files written Writes are scoped to a dated partition and the partition already existed. This stage is already safe to rerun, and saying so is half the value of the table.
Stage 2 — deduplicated orders 1,204,551 1,237,088 No change +32,537 Success, 32,537 rows inserted An INSERT where the target needed a MERGE. One day of orders written a second time, alongside the first copy. The job reported success both times.
Stage 3 — daily revenue rollup 742 743 No change +1 row, and 6 existing rows changed Success A now() inside the transformation put the rerun in a new day bucket, and the duplicated orders from stage 2 inflated six earlier days. The dashboard reading this table showed no error at all.

The expected column is written down by you, in your own words, before the drill runs. That is deliberate and it is half the deliverable: the gap between what you expect a second run to change and what it actually changes is the finding. Agreeing it afterwards would let either of us describe whatever happened as the intended outcome.

Look at the reported column. Every stage above reported success, including the two that were wrong. That is the whole problem in one column, and it is the same shape as a deployment rollback that the pipeline calls finished while the old version is still serving.

The blast list

Everything a second run touches, not just the table everyone thinks of — and which of them are safe to fire again, decided one at a time before anything is rerun.

Sink Whose system Safe to re-fire? Decided
The target table itself ours Safe once the write is a merge In scope for the drill, on the clone. This is the one everybody already knows about, and it is the only one on most people’s list.
Reverse-ETL sync into the CRM customer Not safe. Re-pushes rows that can trigger email Disabled for the drill and named here rather than in a footnote. Stubbed is not tested — whatever was switched off was not exercised, and that is recorded as a limit of the drill rather than glossed over.
Partner export dropped to SFTP external Unknown until asked Nobody on the call could say whether the partner deduplicates on their side. That question being open is itself the finding, and it was open before the drill rather than discovered by it.

Both tables above are an example of the shape rather than anybody’s real system — but the columns are the real columns. The numbers are illustrative and are not a claim about a client, a project or a result. What is real is the structure: rows before, rows after, expected against actual, per stage, against what the job reported; and every sink named, classified and decided.

What is free here, and what is not

Once, in one place, rather than a locked row beside every paragraph.

Free, always, with no account and no email address

  • Everything on this page, including both tables above and every column in them.
  • The full argument — why reruns break, why nothing built for reruns checks the target, and the three vendor pages that say so, linked so you can read them yourself.
  • All six failure modes and the repair for each one, named specifically enough to go and look for in your own code this afternoon.
  • The list of what to ask before you buy this from anyone, including me: which load, which sinks, is any of them customer-facing, can you clone the target.

What you would be paying for

  • The drill itself, against your pipeline — the load actually run a second time, against a clone carrying your real state.
  • The numbers that come out of it, per stage, against what you said you expected. Nobody can produce those by reading anything.
  • Your blast list — walked, classified and decided sink by sink, which is the part that needs somebody to have looked rather than to have thought.
  • The repair, and a second drill afterwards that proves it.

Nothing here is gated, blurred or held back behind an email address. There is no scanner on this page and therefore no result to withhold — the free column above is not a sample of the paid one, it is genuinely everything that can be given away without touching your system. What costs money is running your load, which nobody can do from a web page.

And if the drill cannot run, that is a check that did not happen — not a tier you failed to buy. If your warehouse cannot produce a clone carrying the previous run’s state, the honest answer is that the drill is not available on that target yet, said on the day and before anything is committed to. Dressing an incomplete measurement as a paid upgrade is the one thing that would make this page dishonest.

This asks to run a production load a second time, so the limits are in writing

It is a bigger request than anything else on this site — bigger than changing a DNS record, bigger than rolling an application back. A bad rerun corrupts data, and data does not roll back the way a container image does.

The drill runs against a clone of the target that already contains the previous run’s data. Never against production, at any tier. A zero-copy clone, a snapshot restore, a schema copy — whatever your warehouse offers.

That is not the same thing as a clean environment, and the difference is the whole design. A clean environment reruns perfectly every time because it has nothing in it, which is why testing a rerun on an empty staging table proves nothing at all. A clone of a live target carries exactly the state that breaks a rerun. The drill is valid precisely because the state came along with it.

  • Side-effecting sinks are disabled or stubbed for the drill, and which ones they were is a row in the blast list rather than a footnote. A drill that quietly re-sent customer email would be the single worst thing this page could produce.
  • Read-only on the source. The drill re-reads. It never rewrites anything upstream.
  • Nothing is deleted. Not a table, not a partition, not a file.
  • A backup or snapshot taken by you, verified by you, before anything starts. This page does not sell the backup and does not vouch for it — that is the resilience review, and it is a different piece of work.
  • Or you drive every step and I measure. Same ledger, same blast list, no credential moves. If you will not hand warehouse access to anybody, that is a reasonable position and it does not put this out of reach.

The drill does not prove your pipeline is correct. It proves what a second run does. Whether the first run produced the right numbers is a different question and this engagement does not answer it.

It looks like the deployment rollback review, and it is not one

Both do a dangerous thing on purpose, inside an agreed window, and hand back a measurement. If you have read that page the resemblance is real and worth putting side by side rather than glossing over.

Deployment & rollback review This page
What gets run again The application, going backwards The data load, going forwards a second time
What breaks it State the newer release wrote State the previous run wrote
The measurement Minutes, from the decision to the old version serving Rows, expected delta against actual, per stage
The repair Pinned images, expand-and-contract migrations MERGE instead of INSERT, partition-scoped writes, keys that cannot be null
How it fails The site is down and everybody knows The numbers are wrong and nobody notices
What feeds it Eleven finding ids from the free Compose checker Nothing. No checker here detects this

That last row is the honest one and it cuts against this page rather than for it. The boundary between the two is in the subject matter, not in any shared code — there is no finding on either side of it, because there is no finding at all. If what you actually need is your release path timed rather than your load counted, go and read that page instead; it is backed by evidence this one does not have.

Bulk document data extraction is the only other data-shaped page here and there is no overlap: it creates a dataset out of documents, while this one tests what happens when a load that already exists runs again.

Four ways in, and the second one is where most loads end up

Everyone starts with the drill, because until a rerun has been counted nothing after it can be aimed at anything — including by me.

Rerun Review

Everyone starts here. It also scopes everything else.

  • One scheduled load run a second time against a clone carrying the real state, inside an agreed window
  • The rerun ledger — rows before, rows after, expected delta against actual, per stage, and the stage where they diverge
  • The blast list — every sink the load touches, classified, and which are safe to fire again
  • What is already correct, not only what is not. A stage that reruns cleanly is written down as such
  • Nothing in production changes. Not one row
  • Or you drive every step and I measure — same ledger, no access needed
Recommended

Review + Repair

Not a starting point. Follows the review.

The tier where the load actually becomes safe to rerun.

  • Merges and upserts where an INSERT was, writes scoped to a fixed partition, now() taken out of transformations, merge keys that cannot be null
  • A second drill after the repair, so the fix is proved rather than asserted. Included, not quoted separately — a repair nobody re-measured is the same claim you started with
  • The blast list turned into isolation that holds: side-effecting sinks either made safe to re-fire or explicitly fenced
  • Applied on the clone and verified there first, every time

Review fee credited — you pay the difference, not both.

Backfill Baseline

Not a starting point. Follows the review.

The hard case, and a different one.

  • Everything in the repair
  • A real backfill range replayed and counted, because a one-day rerun and a ninety-day replay fail in different places
  • Late-arriving records and slowly-changing dimensions handled, where the expected delta is legitimately not zero
  • Schema change decided on purpose — including on_schema_change, whose default quietly drops a new column
  • A rerun runbook anyone on your team can follow, and handover so they drill without me

Review fee credited — you pay the difference, not both.

Rerun Drills

Monthly. Because a load that reruns safely today is a load nobody has edited yet.

  • The drill repeated against the load currently running in production — against a fresh clone each time, never against the production target
  • Diffed against last month, so a number getting worse is visible before it matters
  • New sinks caught as they appear: the blast list re-walked, not assumed
  • A one-page record each month you can hand to anyone who asks
  • Monthly in advance, cancel anytime, never auto-renewing

Five things about the packages, before you ask

  • The cap is one pipeline, one scheduled load, one agreed window. A pipeline means the set of tasks that run together on one schedule and write one coherent result. If two schedules write the same table, that is two pipelines, and I will say so before quoting rather than after.
  • Review + Repair is the marked tier, and it is where most loads end up. Not because it is the biggest — because the review on its own hands you a number and the repair is what turns the number into a load you can safely retry. It is also the only tier that measures itself twice.
  • What moves the size of the job is sinks, not rows. In order: whether any sink is customer-facing — a reverse-ETL sync into a CRM, a webhook, an email trigger — because that turns the drill into a measurement plus an isolation exercise; how many sinks there are at all, which is nearly always more than expected; whether your warehouse gives a cheap clone or the target is a self-managed database needing a restore; late-arriving data and slowly-changing dimensions; the backfill range if that tier is in scope; and whether you drive or I do.
  • The review fee is credited for 30 days. Come back inside a month and you pay the difference. After that it lapses, and not as a sales tactic: pipelines get edited, and a ledger from last quarter would have to be re-measured anyway.
  • The monthly tier never auto-renews and there is no minimum term. If a month’s drill produces nothing worth reporting, the record says so and that is a good outcome rather than a wasted one.

The monthly tier is not monitoring and it is not data observability. It produces no graph and nothing that wakes anybody up. It runs the same drill, counts the same rows, and compares this month’s answer with last month’s. If continuous watching is what you want, that is a different service.

There are no figures on this page, and that is deliberate rather than coy. A fixed number would be too high for a three-stage load writing one table and too low for a load feeding a CRM, a partner export and six downstream models. Tell me the shape of it in the form — which load, how many sinks, whether any of them reaches a customer — and you get a figure back before anything is committed to.

What this does not do

Stated before the tiers rather than after them, because a limit found afterwards is an excuse.

  • A rerun drill does not prove your pipeline is correct. It proves what a second run does. Whether the first run produced the right numbers is a separate question, and it needs your business logic rather than my measurement.
  • A clone is not production, and the one thing it cannot reproduce is concurrency. If something else writes to that table while the load runs, the drill did not see it. That is a real limit and it is written into the report rather than left for you to assume.
  • Stubbed sinks are not tested sinks. Whatever was disabled for safety was not exercised, and the blast list names every one of them.
  • Idempotent is not the same as cheap. A load made safe to rerun may read or write more than it did before. That trade gets stated up front rather than discovered in a bill.
  • This is not data quality testing and it is not observability. It is one question answered with a number.
  • A clean result says nothing about future runs. A pipeline that reruns cleanly today is a pipeline nobody has edited yet, which is the entire argument for the monthly tier and is not a claim that anything is permanently solved.

What I need before anything can be quoted

Most of this you can answer in the contact form in a couple of minutes, and the answers change the price more than anything I could guess at.

  • Which single scheduled load is in scope, and what it writes.
  • A clone, snapshot or restore of the target carrying the previous run’s data — or the access to create one. If that is not possible on your warehouse, say so early; it changes the shape of the work.
  • The list of every sink, including the ones that are not tables. This is the question most likely to come back incomplete, and that is itself worth knowing.
  • Whether any sink is customer-facing. Asked before anything is quoted, because it is the single biggest driver of the size of the job.
  • Read access to the pipeline definition — a DAG, a dbt project, a Glue job, a scheduled script, whatever it actually is.
  • A backup taken by you and verified by you. Not sold here, not vouched for here.
  • What you expect to change if the load runs twice, in your own words, in writing, before the drill. Half the deliverable is the gap between that and what happens.

Things this is not, and where they live instead

  • Building the pipeline. Reviewing and repairing rerun behaviour is in scope. Greenfield construction is a different conversation.
  • Data quality testing and observability — dbt tests, Great Expectations, Soda, freshness alerting. Free products, better than hand-written ones, and yours to install.
  • Deployment pipelines and rollback — that is the deployment and rollback review, and the difference is in the table above.
  • Backups and proving a restorethe resilience review.
  • Turning documents into a datasetbulk document data extraction.
  • Warehouse cost reduction, BI modelling, and migrating between warehouses. Real work, not this page.

What I can show you rather than tell you: travel-etl-pipeline is a public repository — a serverless ETL pipeline for NYC travel data on GCP, ending in a Tableau reporting layer. Ingest, transform, warehouse, visualise. Read the code rather than taking my word for anything.

Tell me which load you would not want to run twice by accident.

There is no free checker to run first on this one, and I would rather say that than invent one. So the useful first step is the list: which scheduled load, what it writes, and every sink it touches. If it turns out your writes are already merges scoped to a fixed partition and you can name every sink from memory, you have saved a fee and I would rather tell you that than take it.

Prefer to talk? Book a free call ↗  ·  Or hire me on Upwork ↗  ·  Typical reply within one business day.

Questions

Will you run this against our production tables?
No, and not at any tier. The drill runs against a clone of the target that already carries the previous run’s data — a zero-copy clone, a snapshot restore, or a schema copy, whatever your warehouse gives you. That is not a concession, it is the design: a clean empty environment reruns perfectly every time and proves nothing, while a clone of a live target carries exactly the state that breaks a rerun.
Our orchestrator already retries failed tasks. Is that not the same thing?
That is the problem rather than the answer. The retry is automatic and it is meant to be. What nothing checks is whether the second attempt was safe to make. Airflow’s own best-practices page puts that responsibility on the author, in the modal: tasks should produce the same outcome on every re-run. The scheduler will run a double-inserting task twice without complaint.
We use AWS Glue job bookmarks specifically so reruns are safe.
Bookmarks track what you have read, not what you have written, and AWS says so on the bookmarks page itself: only source files are tracked. Rewinding or resetting a bookmark deliberately does not clean the target, because Glue does not track targets at all. It is a good feature doing exactly half the job, and the half it does not do is the half that duplicates your rows.
Could a language model not just review our DAGs and tell us this?
It could review them, and it would do it well and in seconds — that is exactly why a code review is not what this sells. A model reads the code. It cannot read the state your last run already wrote, and the state is what breaks a rerun. The deliverable here is a count of what actually changed, not an opinion about what might.
Is this data quality testing?
No, and the tools for that are free and better than anything hand-written — dbt ships tests, Great Expectations and Soda Core are open source. Those catch a duplicate after a bad rerun has already written it. This finds out what a second run does before one happens by accident at three in the morning. If you want the safety net as well, install it yourself; you do not need me for that.
Does the drill prove our pipeline is correct?
No. It proves what a second run does. Whether the first run produced the right numbers is a completely different question and this page does not answer it. Anything claiming otherwise from one drill would be overselling a measurement.
What if the drill comes back clean?
Then you have a counted, dated answer instead of a hope, and the ledger says which stages are safe and why. That is a fine result and I would rather hand it to you than manufacture a problem. It also usually turns up the blast list, which almost nobody has written down, and that part is rarely clean.
What do you need from us before quoting?
Which single scheduled load is in scope and what it writes; every sink it touches, including the ones that are not tables; whether any of those sinks is customer-facing; whether your warehouse can give us a cheap clone; and what you expect to change if the load runs twice, in your own words and in writing before the drill. That last one is half the deliverable.