Syncle

Benchmarks

Every number on this page comes from a recorded run of the benchmark suite against real databases. The results are committed as benchmarks/results.json and this page only renders that file — so anything shown here can be reproduced, and nothing here was typed by hand.

Read this before quoting a number

Measured on a single local machine: Syncle, both databases and Redis all run on the same host, in containers, with no network between them. Real deployments put a network in that path, and against a managed or remote database latency — not the database — is usually what sets the pace, so expect lower absolute numbers there. These runs are useful for comparing code paths against each other, not for predicting your production ceiling. Every figure is measured; none is estimated, extrapolated or rounded up.

The configuration

The shipped defaults — these are not tuned for the benchmark. Read from the running configuration, so this cannot claim settings the run did not use.

Rows per delivery
100,000 (SYNCLE_CDC_BATCH_SIZE)
Batch byte ceiling
64 MB (SYNCLE_CDC_BATCH_BYTES)
Partial-batch linger
50 ms (SYNCLE_CDC_LINGER_MS)
Durable spool
off — the default; one scenario below enables it
Write mode
idempotent upsert, keyed on the primary key

The machine

Syncle, the databases and Redis all ran here, together.

Platform
darwin 25.5.0 (arm64)
CPU
Apple M4
Cores
10
Memory
16 GB
Node
v22.23.1
PostgreSQL
16.15
MySQL
8.0.46
Redis
7.4.11
Databases
containerised, same machine (docker-compose.test.yml)
Sharing the machine
none — the test stack had the machine to itself

Change data capture, end to end

A row is committed on the source, picked up from that database’s own change log (Postgres logical replication, MySQL binlog) and written to the destination. Timing starts when the writes begin and stops when the last row has landed, so it includes reading the log, delivery and the destination write. Every run is verified complete and duplicate-free before its time is recorded.

ScenarioRowsTimeRows / sec
PostgreSQL → PostgreSQL · batched
  • verified: 1000000 rows, exactly once
  • syncle cpu (peak / avg): 221% / 26%
  • syncle memory (peak): 249 MB
  • pg cpu / memory (peak): 176% / 1335 MB
1,000,00015.9 s62,743
PostgreSQL → MySQL · batched
  • verified: 1000000 rows, exactly once
  • syncle cpu (peak / avg): 205% / 46%
  • syncle memory (peak): 287 MB
  • pg cpu / memory (peak): 88% / 1420 MB
  • mysql cpu / memory (peak): 53% / 1760 MB
1,000,00011.1 s89,847
MySQL → PostgreSQL · batched
  • verified: 1000000 rows, exactly once
  • syncle cpu (peak / avg): 224% / 29%
  • syncle memory (peak): 369 MB
  • pg cpu / memory (peak): 127% / 1609 MB
  • mysql cpu / memory (peak): 82% / 1838 MB
1,000,00011.4 s87,789
PostgreSQL → MongoDB · batched
  • verified: 1000000 rows, exactly once
  • syncle cpu (peak / avg): 258% / 10%
  • syncle memory (peak): 288 MB
  • pg cpu / memory (peak): 105% / 1820 MB
1,000,00046.9 s21,325
PostgreSQL → PostgreSQL · batched + durable spool
  • verified: 1000000 rows, exactly once
  • syncle cpu (peak / avg): 266% / 51%
  • syncle memory (peak): 527 MB
  • pg cpu / memory (peak): 138% / 1616 MB
  • peak spool depth: 83789
1,000,00021.1 s47,375

Destination write ceiling

Rows written straight into a destination table, with no change stream involved. This is the fastest the destination can accept them, and the bar the sync figures above are measured against.

ScenarioRowsTimeRows / sec
PostgreSQL · upsert
  • syncle cpu (peak / avg): 86% / 8%
  • syncle memory (peak): 130 MB
  • pg cpu / memory (peak): 98% / 1227 MB
1,000,0008.50 s117,606
MySQL · upsert
  • syncle cpu (peak / avg): 82% / 22%
  • syncle memory (peak): 176 MB
  • mysql cpu / memory (peak): 110% / 1811 MB
1,000,0003.62 s276,091

Reproducing this

docker compose -f docker-compose.test.yml up -d
pnpm benchmark

The run resets replication slots, fixtures and the metadata store first, because leftovers from a previous run distort everything after them. Each figure is verified complete and duplicate-free before its time is recorded — a throughput number is worthless if the data is wrong.

Recorded 2026-09-07.