GitHub
10/29/2026, 6:34 AMDELETE before the earlier
INSERT of the same row.
Both answers exist on the coordinator and nowhere else.
What it is for: disaster recovery between clusters that are not the same size
The reason I care about this is not "replication" in the abstract. It is that
today a Cloudberry cluster can only be stood behind by a copy of itself.
Mirrors protect a segment, not a site. For a second site, the choices are a
backup and restore, or a second cluster kept in step by physical means — and
physical means require the two clusters to have the same shape: the same
number of primary segments, segment for segment. That is a hard thing to promise
over the life of a system. The DR site is often smaller than production, on
purpose; production gets expanded and the DR site does not, or not at the same
time; a cluster is moved to different hardware with a different segment count.
Any of those ends the arrangement.
Logical replication does not have that constraint, because it ships rows and
commit order, not blocks and segment positions. The receiver re-routes every
row through the target's own distribution policy, so:
• source and target segment counts may differ — verified both ways: 3 → 2 and
2 → 3, as well as 3 → 3;
• the target is an ordinary cluster the whole time — queryable, not a replay-only
standby waiting to be promoted;
• a subset works: replicate the tables that matter to the DR site rather than the
whole instance;
• the two sides may be expanded independently, since neither side's segment count
is part of the contract between them.
That is the capability I am after.
And the target does not have to be a cluster
Nothing in the server changes knows what the other end is. They add two things —
a list on a WAL record, and an optional output-plugin callback — and an output
plugin is the standard way a third party gets at a logical stream. What it does
with the stream afterwards is none of the server's business.
That matters because "another Cloudberry cluster" is only one of the things
people want to feed:
• A message bus. A CDC pipeline into Kafka or Pulsar wants the same two
answers this proposal supplies, for the same reason. Reading the segment
streams independently — which is all a tool can do today — produces rows with
no transaction boundary across segments and no order between them, so a
downstream consumer sees a later DELETE before the earlier INSERT of the
same row and has no way to know the two belong to one transaction.
• Object storage / a lake table. Appending change files to S3-compatible
storage, Iceberg, Hudi and so on needs to know when a distributed transaction
is complete before it commits a snapshot. "Every segment the coordinator
named has delivered its part" is exactly that condition, and it is not
derivable from the segment streams alone.
• Another database entirely, or an in-house consumer — anything that already
speaks the logical replication protocol.
None of those need my receiver, and none of them need a change to the server
beyond what is here. They need an output plugin of their own, which is a
supported extension point, plus the two answers the plugin can now be given.
This is also why commit 6 exists in the shape it does: a cluster-wide slot is
the same slot on every node, made all-or-nothing, and every such tool needs
one. The server exports the piece that creates a slot and keeps the
cluster-wide policy out of itself, so a third party can build that policy rather
than wait for the server to grow one opinion about it.
So I would rather the discussion treat these as an interface than as a hook
for one receiver. If the shape is right, a CDC vendor can use it next week
without talking to me; if it is wrong, it is wrong for all of them at once —
which is the argument for settling it now rather than after the fact.
What I am proposing
The coordinator's distributed commit record carries the list of participating
segments, and logical decoding hands that list to an output plugin through one
new optional callback. Nine commits, ~900 lines, of which only two change an
interface:
• XACT_XINFO_HAS_DISTRIB_SEGS — the list on the record, written only where
wal_level already admits logical decoding.
• distributed_commit_cb — an optional output-plugin callback, called in WAL
order for XLOG_XACT_DISTRIBUTED_COMMIT and XLOG_XACT_DISTRIBUTED_ONEPHASE,
for the slot's own database only.
The other seven are consequences, and three of those are plain bug fixes that
stand on their own.
The nine commits, rebased on current `main`:
https://github.com/my-ship-it/cloudberry/tree/mpp-logicalrep-kernel
The nine commits
| # | Commit | What it does | Why it is needed | Touches |
| - | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
| 1 | [Fix the cmockery unit test harness for PG16](<https://github.com/my-ship-it/cloudberry…
apache/cloudberry