Introduction
Logical replication is one of those PostgreSQL features that developers may not think about until a system starts using more than one database.
A typical application might have a primary PostgreSQL database and one or more replicated databases for reporting, migrations, regional workloads, or other operational purposes.
A simplified architecture looks like this:
Application
|
v
Primary PostgreSQL
|
| Logical Replication
v
Subscriber DatabaseThis sounds straightforward, but sequence values can introduce subtle problems.
PostgreSQL sequences are commonly used to generate values for identity or auto-incrementing columns. When data is replicated logically, the rows and the sequence state are related but are not necessarily synchronized in exactly the same way.
PostgreSQL 19 Beta 3 is particularly interesting for testing because the release includes fixes related to logical replication sequence synchronization and REFRESH SEQUENCES.
For developers and database engineers evaluating PostgreSQL 19, this makes sequence behavior an important area to test before relying on logical replication in production.
What Is Logical Replication?
Logical replication copies changes from a publisher database to a subscriber database.
The basic architecture is:
Publisher
|
| INSERT / UPDATE / DELETE
v
Logical Replication
|
v
SubscriberUnlike physical replication, logical replication works at the logical change level.
For example, when an application executes:
INSERT INTO customers (name)
VALUES ('John Smith');the logical replication system can replicate the resulting row change to the subscriber.
A simplified setup looks like:
PostgreSQL Publisher
|
v
Publication
|
v
Logical Replication
|
v
Subscription
|
v
PostgreSQL SubscriberThis provides flexibility for selective table replication and other logical replication scenarios.
What Is a PostgreSQL Sequence?
A sequence is a database object that generates numeric values.
For example:
CREATE SEQUENCE customer_id_seq
START WITH 1
INCREMENT BY 1;The application can request the next value:
SELECT nextval('customer_id_seq');which produces values such as:
1
2
3
4
5Sequences are commonly used with identity-style primary keys.
For example:
CREATE TABLE customers
(
id BIGINT GENERATED BY DEFAULT AS IDENTITY,
name TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL
);The database can generate the id value automatically when a row is inserted.
Why Sequences Matter During Replication
Consider a publisher:
Publisher
Customers
IDs:
1
2
3
4
5and a subscriber:
Subscriber
Customers
IDs:
1
2
3
4
5Now imagine the publisher generates:
6
7
8The rows may be replicated successfully.
But sequence state is a separate concern.
A useful mental model is:
Row Data
+
Sequence State
=
Consistent Replicated DatabaseIf those two pieces become inconsistent, future inserts can produce unexpected results.
A Simple Sequence Problem
Suppose the subscriber contains:
Customer IDs:
1
2
3
4
5but its local sequence still expects to generate:
5A new local insert could attempt to reuse an existing identifier.
That can result in a primary-key conflict.
For example:
ERROR:
duplicate key value violates unique constraintThe exact error depends on the schema and operation, but the underlying issue is the same: generated key state and existing data are out of alignment.
Why PostgreSQL 19 Beta 3 Is Worth Testing
PostgreSQL 19 Beta 3 includes fixes related to logical replication sequence synchronization and REFRESH SEQUENCES.
That makes this release a useful testing target for teams that depend on logical replication.
The important point is not simply:
"PostgreSQL 19 has a sequence feature."The more useful question is:
"Does our replication workflow keep sequence state
consistent under the operations our application performs?"That requires an actual test.
Understanding REFRESH SEQUENCES
PostgreSQL provides mechanisms for refreshing sequence state in logical replication scenarios.
A simplified conceptual workflow is:
Publisher
|
v
Sequence Changes
|
v
Replication
|
v
Subscriber
|
v
Refresh Sequence StateThe exact commands and behavior should be tested against the PostgreSQL version and logical replication configuration being used.
For PostgreSQL 19 Beta 3, sequence-related changes are particularly relevant because the release notes identify fixes around sequence synchronization and REFRESH SEQUENCES.
Build a Reproducible Test Environment
Before testing PostgreSQL 19 Beta 3, create two environments:
Publisher
PostgreSQL 19 Beta 3
|
v
Subscriber
PostgreSQL 19 Beta 3For application testing, you can use a simple customer table:
CREATE TABLE customers
(
id BIGINT GENERATED BY DEFAULT AS IDENTITY,
name TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
CONSTRAINT pk_customers
PRIMARY KEY (id)
);The important part is that the table has an automatically generated identifier.
Insert Initial Data
Start with several rows:
INSERT INTO customers (name)
VALUES
('Alice'),
('Bob'),
('Charlie'),
('David'),
('Emma');Verify the data:
SELECT *
FROM customers
ORDER BY id;You should see sequential identifiers.
The exact values depend on the starting sequence state.
Check Sequence State
For an identity column, PostgreSQL provides metadata that can be inspected to understand the associated sequence.
For a separately named sequence, you can use:
SELECT last_value
FROM customer_id_seq;This gives you a useful baseline before replication testing.
The important thing is to record both:
Current table data
+
Current sequence stateConfigure the Publication
A simplified logical replication publication can be created with:
CREATE PUBLICATION customer_publication
FOR TABLE customers;This tells PostgreSQL which table changes should be published.
The subscriber then creates a subscription pointing to the publisher.
The exact connection details depend on the environment.
For example:
CREATE SUBSCRIPTION customer_subscription
CONNECTION 'host=publisher-host dbname=appdb user=replicator password=...'
PUBLICATION customer_publication;In production, credentials should be handled securely rather than embedded in scripts or source control.
Test Normal Inserts
The first test should be simple.
Insert a new row on the publisher:
INSERT INTO customers (name)
VALUES ('Frank');Then verify the subscriber:
SELECT *
FROM customers
ORDER BY id;The row should appear after replication catches up.
This establishes that the basic logical replication workflow is functioning.
Test Multiple Inserts
Next, generate a larger sequence of values:
INSERT INTO customers (name)
SELECT 'Customer ' || generate_series(1, 100);Then compare the publisher and subscriber.
Check:
SELECT COUNT(*)
FROM customers;on both databases.
Also check:
SELECT MIN(id), MAX(id)
FROM customers;The goal is to verify that replicated rows and generated identifiers remain consistent.
Test Sequence Advancement
Now perform another insert on the publisher:
INSERT INTO customers (name)
VALUES ('New Customer')
RETURNING id;Record the generated identifier.
Then verify the subscriber data.
This test answers an important question:
Does the subscriber have sequence state that is consistent with the replicated data?
Do not stop after confirming that the row arrived.
Check the next generated value as well.
Test a Local Subscriber Insert
This is an important edge case.
Depending on the logical replication architecture, the subscriber may be read-only from an application perspective or may also accept local writes.
If local writes are allowed, test them explicitly.
For example:
INSERT INTO customers (name)
VALUES ('Subscriber Customer')
RETURNING id;Now verify:
Does the generated ID conflict?
Does it overlap with replicated values?
Does the sequence remain usable?The answer depends on the architecture and how sequences are managed.
This is exactly the type of scenario that should be tested rather than assumed.
Test Sequence Refresh
If your replication workflow uses sequence refresh operations, test them independently.
The test should record:
Sequence state before refresh
Sequence state after refresh
Table maximum ID
Next generated IDFor example:
SELECT MAX(id)
FROM customers;Then inspect the sequence state using the appropriate PostgreSQL sequence inspection method.
The goal is to establish whether:
Sequence Statematches:
Replicated Dataafter the refresh operation.
PostgreSQL 19 Beta 3 specifically includes sequence-related logical replication fixes, so this is one of the areas worth including in a regression suite.
Test Replication Lag
Replication is not necessarily instantaneous.
Therefore, avoid writing a test that assumes:
INSERT
|
v
Immediate SELECT on subscriberalways sees the new row.
Instead:
Publisher Insert
|
v
Replication
|
v
Wait / Poll
|
v
Subscriber ValidationYour test should have a reasonable timeout.
For example, an integration test could repeatedly check for the expected row instead of immediately declaring failure.
Test Large Sequence Values
Sequence bugs are not limited to small numbers.
Test values near realistic production ranges.
For example:
1,000
10,000
1,000,000and, where relevant, much larger values.
Also test the actual data type:
BIGINTrather than assuming INT is sufficient.
The important point is to test the range your application actually expects.
Test Rollback Scenarios
Consider:
BEGIN;
INSERT INTO customers (name)
VALUES ('Temporary');
ROLLBACK;Sequence behavior can be different from transaction behavior because sequence operations are not treated exactly like ordinary transactional row changes.
This is an important reason not to assume:
Rollback
=
Sequence Value ReturnedSequence allocation and transaction rollback should be tested separately.
Test Gaps in Sequence Values
Sequences can contain gaps.
For example:
1
2
3
5
6The missing value does not automatically indicate corruption.
Applications should generally not rely on generated identifiers being perfectly consecutive.
A benchmark should therefore distinguish:
Gapfrom:
Incorrect Sequence StateThese are different problems.
Test Replication After Restart
A useful production-style test is restarting the subscriber.
The sequence workflow becomes:
Publisher
|
v
Replication
|
v
Subscriber
|
v
Restart
|
v
Replication Resumes
|
v
Sequence Validation
Join the conversation! Your thoughts help the community grow.