Raw data behind The Hidden CPU Cost of PostgreSQL Logical Replication. The harness which produced these numbers is run-benchmark.sh; the workload it runs is logical-replication-insert.sql.

Reproducing

The harness creates two disposable PostgreSQL clusters under /private/tmp, uses localhost ports 56432 and 56433, and removes the clusters when it exits. It defaults to the PostgreSQL 18 binaries from Postgres.app. Override PG_BIN, PUB_PORT or SUB_PORT if necessary.

bash run-benchmark.sh

Run date: 2026-08-03

Environment:

  • PostgreSQL 18.1 (Postgres.app)
  • Apple M1 Pro, 10 cores, 32 GB RAM
  • macOS 15.7.1
  • Two PostgreSQL clusters on the same host
  • 256 MB shared_buffers per cluster
  • fsync=off, synchronous_commit=off, full_page_writes=off, autovacuum=off
  • Publisher used wal_level=logical in every scenario

The main workload used eight clients, 20,000 transactions and 25 inserts per transaction (500,000 rows). CPU figures include PostgreSQL server processes but exclude pgbench and the pg_recvlogical client. Each subscription run waited for an explicit marker row to become queryable on the subscriber.

Raw main-workload runs

Run Scenario TPS Workload s Drain s Total s Publisher CPU s Subscriber CPU s
A Baseline 11843.675 1.712 0.024 1.736 4.750 0.000
A Decode only 7195.940 2.803 3.972 6.775 7.970 0.000
A Logical text 11506.997 1.760 2.491 4.251 7.470 3.010
A Logical binary 9314.725 2.179 2.025 4.204 7.890 2.640
A Text + four indexes 9148.729 2.220 3.053 5.274 6.650 4.660
B Baseline 8016.260 2.530 0.048 2.578 4.710 0.000
B Decode only 9083.127 2.225 4.484 6.710 7.920 0.000
B Logical text 5461.282 3.702 3.454 7.156 7.690 2.930
B Logical binary 6976.843 2.900 2.123 5.023 7.230 2.540
B Text + four indexes 11307.203 1.790 3.604 5.394 6.090 4.620
C Baseline 12645.438 1.609 0.028 1.637 4.350 0.000
C Decode only 5635.959 3.572 4.354 7.927 7.720 0.000
C Logical text 9405.749 2.155 2.477 4.632 7.710 2.940
C Logical binary 7892.504 2.563 3.004 5.566 7.740 2.760
C Text + four indexes 8917.373 2.265 3.624 5.889 5.470 4.840

Main-workload medians

Scenario Publisher CPU s Subscriber CPU s Total CPU s Relative CPU Drain s
Baseline 4.71 0.00 4.71 1.00x 0.03
Decode only 7.92 0.00 7.92 1.68x 4.35
Logical text 7.69 2.94 10.62 2.25x 2.49
Logical binary 7.74 2.64 10.50 2.23x 2.12
Text + four indexes 6.09 4.66 10.71 2.27x 3.60

Publisher CPU, subscriber CPU and drain are per-column medians. Total CPU is the median of the per-run publisher-plus-subscriber sums, so it is not always the exact sum of the independently calculated CPU medians.

TPS varied substantially because publisher and subscriber shared the same CPU. The post therefore uses CPU and applied-marker drain time as the primary metrics.

Subscriber WAL

One representative run, with pg_stat_wal reset after scenario setup and before the workload:

Scenario Subscriber WAL bytes MiB
Logical text 143,476,467 136.8
Logical binary 143,448,119 136.8
Text + four indexes 284,912,301 271.7

REPLICA IDENTITY FULL

The table contained 50,000 rows. The subscription was paused whilst the final 2,000 rows were updated, then resumed. Completion was measured with a separate replicated marker transaction.

Run Subscriber lookup path Apply s Publisher CPU s Subscriber CPU s
A No usable index 4.565 0.020 4.220
A B-tree on id 0.149 0.020 0.010
B No usable index 4.777 0.020 4.270
B B-tree on id 0.151 0.020 0.010

The midpoint ratio is approximately 31.1x. This is an intentionally adversarial row distribution designed to expose the sequential lookup path, not a universal replication multiplier.