Build1 publisher3 min readPublished
Postgres logical replication copies a reporting replica's rows but leaves its schema and sequences behind
Postgres logical replication gave a dev.to author four gotchas on a reporting replica, starting with schema changes and sequences it never copies. The fixes happen at setup: migrations ordered across both databases, hand-synced sequences and off-peak initial copies.
The Engineer · Build desk

What happened
- An engineer building a Postgres reporting replica on logical replication wrote up four gotchas on dev.to that the getting-started guides skip.
- Logical replication decodes the WAL into row changes and replays them, so a replica can hold chosen tables, its own indexes and a newer major version.
- It replicates only inserts, updates and deletes, so a column added on the primary is unknown to the subscription.
- Sequences behind SERIAL and IDENTITY columns are not replicated; row ids arrive, but the subscriber's sequence stays where it started.
- Attaching a subscription begins with a COPY of each whole table; on a 400GB fact table that copy is a long snapshot-holding transaction on the source.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- constraint A team that needs a newer major version or reporting-only indexes on the replica cannot use a physical standby, so the logical-replication traps come with that requirement.
- decision Every migration against a published table now needs a subscriber step and a chosen order, and the post suggests building that step into migration tooling.
- exposure A reporting replica promoted in a failover, or written to for a quick fix, can hand out ids that already exist and fail on duplicate keys weeks later.
- cost The production primary pays for each large initial sync in I/O and bloat, so big tables have to be attached in low-traffic windows, a few at a time.
The tutorial version is accurate as far as it goes. According to the post, the standard walkthrough is CREATE PUBLICATION on the primary, CREATE SUBSCRIPTION on the replica, and data flowing a few seconds later [14]. None of the four gotchas appears in the getting-started guide [14].
The author wanted a replica that analysts query all day, on a different major version, with its own read-tuned indexes [15]. A physical standby can't be that replica. It copies the whole cluster at the WAL block level. It must run the same major version as the primary. Any extra reporting index has to exist on the primary as well [1]. Heavy analytical queries on a standby show up as replay lag [2].
Start with the DDL gap, because it fails quietly for the people using the replica. When a column is added on the primary, the post describes two outcomes. Depending on replica identity and column defaults, the subscriber either gets silent gaps in reporting data or its replication worker stops on a missing-column error, and nobody notices until a dashboard is empty [5]. For an added column, that error means the subscriber has to have the column before the primary starts sending rows that carry it [16]. The post's general rule is ordering. Each migration on a published table gets a subscriber step, applied before or in lockstep with the primary's migration depending on the direction of the change, and some teams script that step into their migration tooling [6]. "Treat DDL against replicated tables as a two-database transaction, even though Postgres will never enforce that for you," the author wrote [7].
Sequences cost nothing on a replica that only serves reads. They matter when someone writes to the replica for a one-off fix, or when the replica is promoted in a failover and nextval() hands back an id that already exists [9]. The author reports seeing duplicate key errors weeks after a nominally read-only replica started carrying other work [9]. The remedy is manual: sync sequence values as part of setup and again in every resync [10].
The initial sync is the gotcha most likely to get someone paged. Run against a live primary during business hours, it can cause visible slowdowns, according to the post [12]. The advice is to schedule initial syncs for the largest tables in low-traffic windows, publish tables incrementally rather than the whole schema at once, and monitor replication lag and source-side load while the copy runs [12]. The copy runs as a long transaction on the source that holds a snapshot, competes for I/O and can extend vacuum-related bloat [11].
The fourth gotcha is not described in the available text, which ends partway through the third. The three that are described are setup and runbook work, and a few-second demo exercises none of them [14]. For a replica that needs its own major version and its own indexes, I would still choose logical replication. The cost is a migration step, a sequence sync and a copy schedule, all written before go-live [6][10][12].
What to watch
- The fourth gotcha the post promises, which the available text does not reach and which could add another setup item.
- Failover runbooks that list the reporting replica as a promotion target, where unsynced sequences would first surface as duplicate keys.
- Replication lag and source-side load during the first large-table sync, the two signals the post says to monitor.