← All blogs

Zero records. Six hours. Why your Airbyte CDC sync says it's running when it isn't.

Author
Abhisek Ojha
Published
Apr 4, 2026
Reading time
5 min

The sync had been running for 5 hours and 56 minutes. Zero bytes. Zero records. And the best part: everything looked completely normal.

I refreshed the Airbyte UI about twenty times that night. The record count never moved. The logs printed the same heartbeat line every ten seconds. The status said running. Not failed, not stuck. Just calmly, confidently running into nothing.

If you have set up Airbyte CDC on Postgres, you may have met this already. It is not a bug in Airbyte and it is not a misconfiguration. It is a gap in how the Debezium engine underneath actually behaves, and once you see it, the fix is a couple of SQL statements.

The setup

We were pulling transaction and alerting data from a live Postgres RDS instance into an analytics layer every 15 minutes. First sync: 5h 56m, 0 records. Second sync: same. Every new trigger then hit a 409 Conflict, because the previous sync was still "running" and blocking the connection. The schedule was dead before it moved a single row.

Things that did not work

In the spirit of shared suffering, here is what we tried first:

  • More memory for the connector pod. Nope.
  • Restarting the Airbyte server. The sync resumed exactly where it left off, at nothing.
  • Checking RDS parameter groups for timeouts. All fine.
  • Fewer tables in the connection. Helped a little.
  • Setting Initial Waiting Time to 300 seconds. The countdown never started.
  • Adding a Debezium heartbeat query on its own. Still nothing.

Every one of these felt right. We were treating the symptom.

What is actually going on

Postgres writes every committed change to the write-ahead log (WAL). Airbyte's CDC creates a logical replication slot and streams that log through Debezium.

Here is the catch: by default, the stream is not limited to your tables. Autovacuum, maintenance jobs, and every other application on the same instance all write to the WAL, and all of it reaches Debezium.

So "exit after 300 seconds of no changes" never fires, because from Debezium's point of view the log is never quiet. Your tables can be silent while the timer resets forever. The UI shows running, which is technically true.

Fix 1: scope a publication to your tables

A publication is a named list of tables that replication is allowed to see. Point Airbyte at one and the other noise is filtered out at the source. This is the single biggest fix.

CREATE PUBLICATION airbyte_pub
  FOR TABLE public.transactions, public.alerts;

Fix 2: add a heartbeat table

The idle timer relies on real WAL writes, not just reads, to confirm Debezium has caught up. Give it something to write:

CREATE TABLE IF NOT EXISTS public.airbyte_heartbeat (
  id   int PRIMARY KEY,
  ts   timestamptz NOT NULL DEFAULT now()
  );

Then point the source's publication and heartbeat settings at these (exact field names vary a bit by Airbyte version). With both in place, a quiet set of tables looks quiet, and the countdown finally counts down. Our syncs went from hours to under seven minutes.

Fix 3: two orchestrator surprises

Once syncs finished, our own code was the next problem.

  • 409 Conflict is not an error. It means the previous run is still going. Find the running job and wait for it instead of crashing the pipeline.
  • incomplete is not a failure. It means Airbyte synced what it could and advanced the checkpoint. Treat it like success so the downstream steps still run.

Recap

Scope the publication. Add the heartbeat table. Latch onto running jobs on 409. Let incomplete through.

Or skip the saga

We built Fabric so this class of problem does not need a write-up. Sign up, get a workspace, add a source, pick your tables, and hit Start Replication: each source runs as a continuous, supervised process, so there is no idle-timeout to defeat, no job scheduler to collide with, and a 30-second heartbeat that is on by default. It also creates the heartbeat table for you.

Replication for a source can begin in under 15 minutes. Honest caveat: the 15 minutes is the technical part. The long pole is usually your own organization, waiting on the firewall ticket, the database read replica, the security review, and whoever owns the credentials. Fabric cannot speed those up, but it does make sure they are the only thing left to wait on.

Prefer to run it in your own VPC? The deployment docs cover self-hosting.

Have you hit this before? I would love to know how long your longest zero-record sync ran.