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.
| Scenario | Rows | Time | Rows / sec |
|---|---|---|---|
PostgreSQL → PostgreSQL · batched
| 1,000,000 | 15.9 s | 62,743 |
PostgreSQL → MySQL · batched
| 1,000,000 | 11.1 s | 89,847 |
MySQL → PostgreSQL · batched
| 1,000,000 | 11.4 s | 87,789 |
PostgreSQL → MongoDB · batched
| 1,000,000 | 46.9 s | 21,325 |
PostgreSQL → PostgreSQL · batched + durable spool
| 1,000,000 | 21.1 s | 47,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.
| Scenario | Rows | Time | Rows / sec |
|---|---|---|---|
PostgreSQL · upsert
| 1,000,000 | 8.50 s | 117,606 |
MySQL · upsert
| 1,000,000 | 3.62 s | 276,091 |
Reproducing this
docker compose -f docker-compose.test.yml up -d
pnpm benchmarkThe 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.
