Using
an RDS PostgreSQL Read Replica as the CDC source to minimize production impact
Reference architecture used in the tested
migration approach.
|
Item |
Details |
|
Source |
RDS
PostgreSQL 18.4 |
|
Migration
source |
RDS
PostgreSQL Read Replica |
|
Migration
engine |
AWS
Database Migration Service (AWS DMS) |
|
Target |
Aurora
PostgreSQL |
|
Migration
pattern |
Multi-Tenant
→ Single-Tenant |
|
DMS
mode |
Full
Load + CDC |
|
Large-table
approach |
Controlled
concurrency; two large tables at a time during the test approach |
Abstract.
Large PostgreSQL migrations become difficult when
the source is a production multi-tenant system, the destination is a
single-tenant Aurora PostgreSQL environment, and a relatively small number of
tables account for terabytes of data. This case study presents a practical
architecture that uses an RDS PostgreSQL Read Replica as the AWS DMS source for
CDC. It walks through the PostgreSQL 18.4 replication configuration, logical
replication slot setup, DMS endpoints, Full Load + CDC validation, large-table
concurrency, and the troubleshooting lessons discovered during testing.
1. The Problem: Moving Large PostgreSQL Data Without
Turning Migration Into a Production Incident
The migration objective was to synchronize
data from a multi-tenant RDS PostgreSQL environment into an Aurora PostgreSQL
single-tenant environment. The challenge was not only the volume of data. The
source contains a number of very large tables, with several individual tables
reaching hundreds of gigabytes and with indexes representing a substantial part
of the total footprint. A migration that simply maximizes parallelism can
therefore become a source, DMS, or target capacity problem.
At the same time, the production database
must continue serving application traffic. Directly adding a heavy migration
workload to the writer is undesirable, particularly when Full Load is reading
large relations and CDC must remain active for an extended period.
The design question was therefore: how can
the migration copy the initial data and continuously capture changes while
moving the DMS source-capture connection away from the production writer? The
answer tested here is to introduce an RDS PostgreSQL Read Replica between the
production Primary and AWS DMS.
2. Why the Read Replica Architecture?
AWS RDS/Aurora database topology from the test
environment, showing the Primary, Read Replica, and Aurora target.
The Read Replica provides a separate
database endpoint for the DMS source connection. The production Primary remains
the application-facing source of truth, while physical replication keeps the
Read Replica synchronized. DMS then uses the Read Replica as its source
endpoint and performs Full Load + CDC toward Aurora PostgreSQL.
|
Component |
Primary responsibility |
|
RDS
PostgreSQL Primary |
Application
workload and source of truth |
|
RDS
PostgreSQL Read Replica |
Migration
source / CDC read path |
|
AWS
DMS |
Initial
Full Load and ongoing CDC processing |
|
Aurora
PostgreSQL |
Single-tenant
target database |
This architecture is designed to minimize
direct DMS CDC workload on the production writer. It does not mean that the
migration is completely impact-free: physical replication, WAL generation,
replica processing, network traffic, and target writes still consume resources.
The important difference is that the DMS source connection is separated from
the production writer, giving the migration a dedicated source endpoint and an
operational control point.
3. Architecture at a Glance
The flow is intentionally simple: the
Primary sends changes through physical replication to the Read Replica; the
Read Replica is used as the DMS CDC source; DMS performs Full Load + CDC; and
Aurora PostgreSQL receives the data in the single-tenant target.
The architecture also separates tuning
decisions:
·
Source protection is primarily
controlled by monitoring the Primary and Read Replica relationship.
·
CDC source pressure is
controlled through Read Replica capacity and DMS source configuration.
·
Migration throughput is
controlled through DMS instance size and table concurrency.
·
Target pressure is controlled
through Aurora capacity, indexing strategy, and load concurrency.
4. Preparing the PostgreSQL 18.4 Source
The test environment used PostgreSQL 18.4
on both the Primary and the Read Replica. Before changing replication
parameters, establish the exact database name and confirm the replica role.
SELECT current_database();
SELECT pg_is_in_recovery();
SELECT version();
The Read Replica should return true for
pg_is_in_recovery(). The database name returned by current_database() becomes
important later because rds.logical_slot_sync_dbname must correspond to the
database used by the logical-slot workflow.
5. Configure the Primary: Logical Replication and the
Physical Replica Slot
On the Primary, enable logical replication
and configure synchronized_standby_slots with the actual physical slot name
associated with the Read Replica. The physical slot name should be discovered
from the environment rather than copied from another server.
|
Parameter |
Tested configuration |
|
rds.logical_replication |
on |
|
synchronized_standby_slots |
Actual
physical Read Replica slot name |
SELECT
slot_name,
slot_type,
active,
restart_lsn
FROM pg_replication_slots
WHERE slot_type = 'physical';
In the supplied test environment, the
physical slot was:
rds_ap_south_1_db_4jxstomc3p4fynocelip3vc4dy
Primary parameter configuration showing logical
replication and synchronized_standby_slots.
The important point is that
synchronized_standby_slots is not simply a Boolean '1' setting in this
configuration. It identifies the physical standby slot that participates in the
synchronization design.
6. Configure the Read Replica: Logical Slot
Synchronization
The Read Replica requires its own parameter
configuration. The tested configuration enabled logical replication, enabled
replication-slot synchronization, set the logical-slot synchronization
database, and enabled hot-standby feedback.
|
Parameter |
Read Replica value |
|
rds.logical_replication |
on |
|
sync_replication_slots |
on |
|
rds.logical_slot_sync_dbname |
Exact
source database name |
|
hot_standby_feedback |
on |
Verification of rds.logical_slot_sync_dbname on the
Read Replica.
In the test environment, rds.logical_slot_sync_dbname
returned datablogs. The key lesson is not the literal name 'datablogs'; it is
that the value must match the actual database being used. A different
environment must use its own exact database name.
hot_standby_feedback is enabled in the
tested configuration because the Read Replica is serving the logical CDC
workload. This setting should be monitored together with WAL retention and
replica health; it should not be enabled without operational monitoring.
Read Replica configuration after the replication
settings were changed.
7. Verify Effective Parameter Values Before Proceeding
Use the same verification query on both
instances:
SELECT
current_database() AS database_name,
inet_server_addr() AS server_ip,
version() AS postgres_version,
name AS parameter,
setting AS value,
pending_restart
FROM pg_settings
WHERE name IN (
'rds.logical_replication',
'synchronized_standby_slots',
'sync_replication_slots',
'rds.logical_slot_sync_dbname',
'hot_standby_feedback'
)
ORDER BY name;
The effective PostgreSQL setting is more
useful than simply checking what was entered into a parameter group. The
pending_restart column also shows whether a restart is still required before
the intended value becomes active.
·
Primary: verify logical
replication is on.
·
Primary: verify
synchronized_standby_slots contains the actual physical replica slot.
·
Read Replica: verify logical
replication is on.
·
Read Replica: verify
sync_replication_slots is on.
·
Read Replica: verify
rds.logical_slot_sync_dbname is the exact source database.
·
Read Replica: verify
hot_standby_feedback is on.
8. Create and Monitor the Logical Replication Slot on Replica
The DMS source endpoint uses a logical
replication slot named dms_read_replica_slot in the tested design. The slot
must be created in the intended database and then verified before the DMS task
is started.
SELECT *
FROM pg_create_logical_replication_slot(
'dms_read_replica_slot',
'test_decoding'
);
Creation of the dms_read_replica_slot logical
replication slot on the Read Replica.
Monitor the slot with:
SELECT
slot_name,
slot_type,
plugin,
database,
active,
active_pid,
restart_lsn,
confirmed_flush_lsn,
wal_status,
failover,
synced
FROM pg_replication_slots
WHERE slot_name = 'dms_read_replica_slot';
This query is particularly valuable during
DMS troubleshooting. If the DMS task reports that a slot is already active,
unavailable, or cannot be used for logical decoding, the slot state and active
process should be checked before repeatedly restarting the task.
9. Create the AWS DMS Replication Instance
The migration used a provisioned AWS DMS
replication instance. The test environment demonstrated the end-to-end
workflow, while the production migration should size the instance based on
measured Full Load throughput, CDC rate, table concurrency, and target
capacity.
AWS DMS replication instance used for the migration
test.
For the large-table workload, the practical
approach is controlled concurrency. Two large tables at a time was the working
approach discussed and tested. This avoids assuming that maximum parallelism
automatically produces maximum migration speed.
|
If this metric is high |
Likely action |
|
DMS
CPU / memory |
Consider
scaling the DMS replication instance or reducing concurrency. |
|
Read
Replica CPU / I/O |
Reduce
migration pressure and verify replica health. |
|
Replica
lag |
Investigate
source/replica pressure before increasing DMS load. |
|
Aurora
CPU / I/O |
Increase
target capacity or reduce concurrent load. |
|
CDC
latency |
Identify
whether DMS, source replica, network, or target is the bottleneck. |
10. Configure the DMS Source Endpoint on the Read Replica
The DMS PostgreSQL source endpoint is
configured against the Read Replica. This is the central architectural decision
that separates the DMS CDC source connection from the production writer.
|
Setting |
Test configuration |
|
Endpoint
role |
Source |
|
Database
engine |
PostgreSQL |
|
Database
name |
datablogs |
|
SSL
mode |
require |
|
SlotName |
dms_read_replica_slot |
AWS DMS PostgreSQL source endpoint configured against
the Read Replica with SlotName=dms_read_replica_slot.
SlotName=dms_read_replica_slot
SlotName belongs on the DMS PostgreSQL
source endpoint. It tells DMS which logical replication slot to use for CDC. It
is not a target-endpoint setting.
11. Configure the Aurora PostgreSQL Target Endpoint
The target endpoint connects AWS DMS to the
Aurora PostgreSQL writer. The supplied test configuration used
MaxFileSize=1048576 and loadUsingCSV=true.
|
Setting |
Supplied test value |
|
Endpoint
role |
Target |
|
Database
name |
datablogs |
|
SSL
mode |
require |
|
MaxFileSize |
1048576 |
|
Extra
connection attributes |
loadUsingCSV=true; |
AWS DMS Aurora PostgreSQL target endpoint
configuration.
These settings should be retained for
production only after validation against the exact DMS version and target
workload. Target-side write capacity can become the limiting factor even when
the DMS instance has spare CPU.
12. Create Full Load + CDC
With the source and target endpoints
validated, create the DMS task using Full Load + CDC. This provides the initial
copy and then continues with change data capture.
1.
Validate the Read Replica
source endpoint.
2.
Validate the Aurora target
endpoint.
3.
Select Full Load + CDC.
4.
Configure table mappings for
the approved migration scope.
5.
Start the task.
6.
Monitor Full Load progress and
CDC latency continuously.
For a multi-tenant to single-tenant
migration, table mappings should be reviewed carefully. The migration should
explicitly define which schemas and tables are moving and whether any tenant
filtering, consolidation, or transformation is required.
13. Proving CDC With Continuous Source Changes
A controlled test table was used to
generate continuous inserts while the DMS task was running. This validates the
CDC path rather than only the initial Full Load.
DO $$
BEGIN
LOOP
INSERT INTO public.dms_cdc_test
(message)
VALUES ('CDC test - ' ||
clock_timestamp());
COMMIT;
PERFORM pg_sleep(1);
END LOOP;
END $$;
Controlled source-side continuous insert workload used
to validate CDC.
This type of continuous test is appropriate
for a non-production validation environment. In production, use a controlled
test procedure with a defined stop condition and business approval.
14. End-to-End Result: Full Load Completed, CDC Ongoing
The supplied DMS screenshot shows the
desired migration state: Full Load progress at 100%, with the task status
indicating that loading completed and replication is ongoing.
|
Validation |
Observed/tested result |
|
DMS
task mode |
Full
Load + CDC |
|
Full
Load progress |
100% |
|
Task
status |
Load
completed, replication ongoing |
|
CDC
validation |
Continuous
source inserts used |
AWS DMS final task state showing Full Load completed
and replication continuing.
This is an important milestone, but it is
not by itself the final cutover criterion. The team should continue monitoring
CDC latency and validate the target data before switching application traffic
or declaring the migration complete.
15. The Large-Table Problem: Why Two Tables at a Time?
The source contains roughly fifteen large
tables, with the largest tables measured in hundreds of gigabytes and with
indexes adding substantial storage. When tables of this size are loaded in
parallel, the bottleneck can move quickly between DMS compute, source I/O,
network throughput, Aurora write capacity, and index maintenance.
For that reason, two large tables at a time
is a sensible starting point for this workload. It is not a universal DMS limit
or a magic number. It is a controlled concurrency value that can be increased
only after observing the system under load.
·
Start with two large tables.
·
Measure rows/second or GB/hour
rather than judging speed by CPU alone.
·
Check DMS CPU and memory.
·
Check Read Replica CPU, I/O,
and replication lag.
·
Check Aurora CPU, I/O, write
latency, and storage behavior.
·
Increase concurrency only when
all critical components have headroom.
·
Reduce concurrency if replica
lag or target pressure increases.
16. Indexes: Fast Load Versus Long Index Creation
A common migration optimization is to load
table data before building secondary indexes. This can improve bulk-load
throughput because the migration is not maintaining every index entry during
the initial data load. However, that approach is not automatically better for
every migration.
For very large tables, rebuilding indexes
afterward can take a long time and can consume significant target CPU, I/O, and
storage bandwidth. If the target must become production-ready immediately after
cutover, deferred index creation can simply move the time cost from the load
phase to the post-load phase.
The right approach should therefore be
decided table by table. Compare index size, index build duration, target
maintenance window, application readiness requirements, and whether the DMS
task can safely operate with the selected target object strategy.
17. How the Design Minimizes Production Impact
The main control is to use the Read Replica
as the DMS source rather than connecting DMS directly to the production writer.
This reduces the need for DMS CDC reads to compete directly with application
transactions on the Primary.
|
Production concern |
Control in this design |
|
DMS
source read workload |
Move
DMS source connection to the Read Replica. |
|
Replica
lag |
Monitor
continuously and reduce migration pressure if it rises. |
|
DMS
bottleneck |
Scale
the replication instance when DMS compute is the limiting factor. |
|
Target
bottleneck |
Tune
Aurora capacity and load concurrency. |
|
Large
table pressure |
Use
controlled concurrency, starting with two large tables. |
|
Long
CDC window |
Monitor
slot state, WAL retention, and CDC latency. |
|
Cutover
risk |
Reconcile
data and wait for CDC to catch up before cutover. |
The phrase 'no impact on production' should
be avoided in technical documentation because no migration can guarantee that.
A more accurate statement is that the architecture is designed to minimize
direct DMS CDC workload on the production writer while providing monitoring and
throttling points.
18. Troubleshooting Lessons From the Test Environment
18.1 DMS reported that the slot was already active
Slot 'dms_read_replica_slot'
state found as 'already active' while expected as 'inactive'.
This indicates that another process or DMS
session was consuming the logical slot. Before resuming the task, identify the
active session/process, stop the conflicting consumer where appropriate,
confirm the slot is inactive, and then resume the DMS task.
18.2 DMS attempted CREATE TABLE on a read-only source
ERROR: cannot execute CREATE
TABLE in a read-only transaction
The test showed DMS attempting to create an
internal DDL artifact on the read-only source. This is an important
compatibility/configuration consideration when using a Read Replica as the DMS
source. The issue should be reproduced and validated in the exact DMS version
and task configuration intended for production rather than assuming that every
task mode behaves identically.
18.3 A synchronized slot could not be used for logical
decoding
ERROR: cannot use replication
slot "dms_read_replica_slot" for logical decoding
DETAIL: This replication slot is being synchronized from the primary server.
This was observed during an earlier
configuration experiment. The lesson is to avoid mixing different
slot-synchronization approaches without testing the exact intended design. The
working test configuration documented here should be treated as the baseline,
and any failover/slot-synchronization variation should be validated separately.
18.4 The logical slot did not appear where expected
When a logical slot is missing from the Read
Replica, verify the instance role, effective parameter values, exact database
name, slot synchronization settings, and the physical replica relationship
before recreating slots repeatedly. The test cycle demonstrated why checking
all layers in order is faster than troubleshooting DMS alone.
18.5 DMS Full Load – Recovery Conflict
During the Full Load phase, DMS reported
SOURCE_UNLOAD errors because long-running queries on the PostgreSQL Read
Replica conflicted with WAL recovery. The observed errors were:
FATAL:
terminating connection due to conflict with recovery – User was holding shared
buffer pin for too long.
ERROR: cannot
assign TransactionIds during recovery.
Initial action: max_standby_streaming_delay
was increased from 30 seconds to 300 seconds (300000 ms).
Final tuning applied for the migration
test:
max_standby_archive_delay
= 1800000 -- 30 minutes
max_standby_streaming_delay
= 3600000 -- 1 hour
TransactionConsistencyTimeout
= 900 -- 15 minutes
These settings provide additional time for
long-running DMS Full Load operations before recovery conflicts or DMS
transaction-consistency handling terminate the connection. Replica lag, WAL
retention, query duration, and application read performance should be monitored
closely during the migration.
Lesson learned: when DMS Full Load runs
against a PostgreSQL Read Replica, recovery-conflict settings must be tuned
together with replica health and workload characteristics. Increasing the delay
values is a mitigation, not a substitute for monitoring replica lag, WAL
retention, long-running queries, and the impact on application read
performance.
19. Monitoring Queries
Effective parameter values:
SELECT
name,
setting,
unit,
context,
pending_restart
FROM pg_settings
WHERE name IN (
'rds.logical_replication',
'synchronized_standby_slots',
'sync_replication_slots',
'rds.logical_slot_sync_dbname',
'hot_standby_feedback'
)
ORDER BY name;
Logical and physical slot state:
SELECT
slot_name,
slot_type,
plugin,
database,
active,
active_pid,
restart_lsn,
confirmed_flush_lsn,
wal_status,
failover,
synced
FROM pg_replication_slots
ORDER BY slot_name;
Replica role:
SELECT pg_is_in_recovery();
20. Production Readiness Checklist
·
Read Replica is healthy and
synchronized.
·
PostgreSQL version and
parameter-group changes are validated.
·
Primary rds.logical_replication
is enabled.
·
synchronized_standby_slots
references the actual physical Read Replica slot.
·
Read Replica
sync_replication_slots is enabled.
·
Read Replica
rds.logical_slot_sync_dbname matches the exact source database.
·
hot_standby_feedback is enabled
as validated and its operational effects are monitored.
·
Logical slot
dms_read_replica_slot exists and its state is understood.
·
DMS source endpoint points to
the Read Replica.
·
Source endpoint SlotName is
dms_read_replica_slot.
·
Aurora target endpoint
connectivity is validated.
·
DMS Full Load + CDC task has
been tested.
·
Large-table concurrency begins
conservatively.
·
DMS, source replica, and Aurora
monitoring is active.
·
CDC latency and WAL retention
are within the approved limits.
·
Data reconciliation and
business validation criteria are defined.
·
Final cutover and rollback
procedures are approved.
21. Conclusion
A large PostgreSQL migration is as much a
capacity-management exercise as it is a replication exercise. In this case, the
goal was to move from a multi-tenant RDS PostgreSQL source to a single-tenant
Aurora PostgreSQL target without turning the migration into a production
performance problem.
The tested architecture uses an RDS
PostgreSQL Read Replica as the AWS DMS source, allowing the production Primary
to remain focused on application transactions while the migration pipeline
consumes changes from the replica. PostgreSQL 18.4 logical-replication
configuration, a dedicated logical slot, AWS DMS Full Load + CDC, and
controlled table concurrency form the core of the approach.
The most valuable part of the exercise was
the test environment. It exposed practical issues around slot state, read-only
DDL behavior, database-specific slot synchronization, and the interaction
between PostgreSQL replication and DMS. These findings can be incorporated into
the production runbook before the migration window.
For environments containing multiple very large
tables, starting with two large tables at a time provides a controlled
baseline. From there, concurrency should be increased only when measurements
show that DMS, the Read Replica, and Aurora all have sufficient capacity. The
objective is sustainable throughput, not maximum parallelism.
Key takeaway: separate the DMS CDC source from the production writer, validate the PostgreSQL replication chain in a test environment, and tune migration concurrency based on the actual bottleneck.











0 Comments