Real-time Sync (CDC)
How Astervis keeps in step with your PBX, powered by litcdc.
Astervis never reads from your PBX database directly. Instead, a tiny service called litcdc tails the MySQL/MariaDB binary log, copies every Call Detail Record (CDR) into Astervis's own TimescaleDB store, and keeps copying as new calls land — in real time. A call shows up on your dashboard within seconds of hanging up, and your PBX database is never touched by a query from Astervis.
This page explains what that pipeline does, how to check it's healthy, and what to tune if it ever falls behind. For most installs there's nothing to configure: the installer sets it all up and it just runs.
What CDC actually does
CDC stands for Change Data Capture. Rather than polling the cdr table for new rows,
litcdc reads the same binary log that MySQL replication uses — the authoritative,
ordered record of every change the database makes. That gives Astervis an exact, gapless
copy of your call history without putting any read load on your PBX.
The flow looks like this:
Initial snapshot (historical backfill)
On its very first run, litcdc takes a single consistent snapshot of the existing
cdr table. This is your historical backfill — every call already in your PBX is
copied across, so your dashboards aren't empty on day one. The snapshot uses a brief
read lock to pin a consistent point in the binlog, which is why the CDC database user
is granted LOCK TABLES.
Realtime streaming
Once the snapshot finishes, litcdc switches to streaming: it reads new binlog events as they're written and forwards them onward. From here on, every new or updated CDR is captured the instant the PBX writes it.
Durable buffer
Captured events are written to a durable, bounded log on disk (an embedded redb
store) before anything consumes them. This buffer is what makes the whole pipeline
crash-safe.
HTTP long-poll delivery
The replication service connects to litcdc over a local HTTP long-poll feed,
drains events from the buffer, and writes them into TimescaleDB. Long-poll means the
consumer holds a request open until there's something to deliver — low latency, no busy
waiting, and no message broker to operate.
litcdc replaced the old Debezium + Redpanda pipeline that earlier versions used. It's a single ~10–25 MiB Rust binary that does the job of a Java connector plus a Kafka-style broker — which is why the whole Astervis stack now fits comfortably in 2 GB of RAM.
Snapshot vs. streaming — and why it rarely re-snapshots
The snapshot is a one-time event. litcdc runs in initial snapshot mode: snapshot once,
then stream forever. The key to that "forever" is the durable buffer, which lives on a
host-mounted volume at /opt/astervis/litcdc_data. Because the buffer survives container
restarts and full server reboots, litcdc remembers exactly where it was in the binlog and
resumes streaming from that point — it does not re-snapshot.
This matters in practice: restarting the stack, upgrading the installer, or rebooting the host is cheap. litcdc picks up where it left off instead of re-copying your entire call history every time.
A re-snapshot only happens if the durable buffer is deleted or the binlog position it
remembers is no longer available on the PBX. The installer sets MySQL
expire_logs_days = 3, so as long as the stack is never down for more than a few days,
litcdc always finds its position and resumes cleanly. Don't delete
/opt/astervis/litcdc_data unless you intend to force a full re-backfill.
Checking sync status
The fastest health check is the installer's status command, which lists every container and its health at a glance:
sudo astervis-installer statusThe two services that make up the sync pipeline are litcdc (container astervis-litcdc)
and replication (container astervis-replication). Both should be running. If
litcdc is healthy but call history is missing, the problem is almost always upstream — the
MySQL binlog config or the CDC user's grants — rather than the consumer.
To look closer, read the logs directly. litcdc logs its snapshot progress and then a steady stream of captured events:
docker logs -f astervis-litcdcDuring the initial run you'll see snapshot progress over the cdr table, then it flips
to streaming binlog events. A row count climbing toward your total CDR count means the
historical backfill is working.
litcdc's HTTP feed is published on the host at 127.0.0.1:18088 (it binds 8088 inside the
Docker network, but host port 8088 belongs to Asterisk's own HTTP server on FreePBX, so
it's mapped to 18088 outside). It's loopback-only and not meant for direct use, but it's
handy as a quick liveness check:
curl -s http://127.0.0.1:18088/healthzThe simplest confirmation of all: make a test call on your PBX, hang up, and watch it appear in the call history view within a few seconds. That's the entire pipeline — binlog → litcdc → buffer → replication → TimescaleDB → dashboard — working end to end.
Buffer, backpressure, and retention
litcdc's durable buffer is bounded — it won't grow without limit and fill your disk. Two
settings govern its behaviour, both set by the installer in /opt/astervis/docker-compose.yml:
| Setting | Value | What it does |
|---|---|---|
LITCDC_BUFFER_MAX_BYTES | 67108864 (64 MiB) | The hard cap on the buffer. litcdc truncates events the consumer has already read once the buffer passes this size. If the consumer has fallen behind and nothing new has been read, litcdc pauses the binlog reader (backpressure) rather than overflowing. |
REPLICATION_THROTTLE_MS | 0 | The delay the replication consumer waits between batches. Zero means drain at full speed — it keeps the buffer shallow and pulls events through as fast as TimescaleDB accepts them. |
The combination is deliberate. The consumer drains with no throttle, so under normal load the buffer stays nearly empty and the 64 MiB cap is never approached. The cap exists purely as a safety valve: if the consumer stalls (database busy, container restarting), litcdc applies backpressure to the binlog reader instead of consuming unbounded memory or disk. Once the consumer recovers, the backlog drains and streaming resumes — and because the buffer is durable, no events are lost in the meantime.
REPLICATION_THROTTLE_MS=0 is the right value and you shouldn't need to change it. An
earlier non-zero throttle was the reason backfills crawled; setting it to 0 took a
~64k-row table from roughly an hour down to about eight minutes. Raise it only if you have a
specific reason to rate-limit writes into TimescaleDB.
Tuning notes
These values live in /opt/astervis/docker-compose.yml, in the litcdc and replication
services. After editing, recreate just those two containers:
cd /opt/astervis
docker compose up -d --force-recreate litcdc replicationRunning astervis-installer upgrade self-updates the installer binary — it does not
regenerate docker-compose.yml. So if you need the latest CDC defaults on a server that was
installed with an older version, you have to edit the compose file by hand (or do a fresh
install). The defaults shipped from installer v0.0.84 onward are mem_limit: 256m,
LITCDC_BUFFER_MAX_BYTES=67108864, and REPLICATION_THROTTLE_MS=0.
Troubleshooting
Where the pieces live
Last updated on