Code examples

This section provides examples of a simple application using the CDC procedures. The examples are provided separately for each of the supported language libraries.

Cursor management and server side filtering

The change identifiers returned from the CDC procedures are valid until the server rotates the transaction log.

Usually the change cursor stays valid, since each call to db.cdc.query updates the cursor. However, very strict selectors may cause db.cdc.query to not return any changes. This can in extreme cases lead to the cursor not being updated in time before the transaction log is rotated. To avoid this, the code examples update the cursor to db.cdc.current if db.cdc.query returns empty. Note that db.cdc.current needs to be called and stored before calling db.cdc.query for this to work.

Another way to avoid this issue is to use broader selectors and perform client side filtering instead.

Monitoring and addressing CDC client performance

For optimal performance when the change rate is high, first monitor whether your CDC client is keeping up with Neo4j. Track lag by comparing the latest processed txId with the latest txId in Neo4j, and/or by monitoring txCommitTime. If lag keeps increasing, apply the recommendations below.

For high change rates, a single dedicated reader thread is usually sufficient for a CDC stream. The critical requirement is that this reader does not pause for downstream processing.

Use a hand-off design:

  • One dedicated reader continuously consumes the CDC stream and immediately hands off each change to an in-memory queue.

  • A pool of worker threads consumes from the queue and performs processing in parallel.

Avoid short polling patterns such as "fetch X events, stop, then reconnect". db.cdc.query streams continuously until the client catches up, and keeping a single stream open is typically the fastest approach. Repeatedly stopping and restarting the stream adds overhead and reduces throughput.

The Neo4j driver buffers incoming changes in memory. If the reader does not hand off fast enough, that buffer can fill up and the server will stop sending additional changes until space is available. Similarly, if the application queue fills up, worker backpressure will block the reader and propagate the same slowdown. Therefore, plan capacity across the full pipeline:

  • Size worker concurrency so the queue is drained at least as fast as changes arrive.

  • Size queue depth to absorb short traffic spikes without blocking the reader.

  • Monitor queue depth, processing latency, and end-to-end lag.

Runtime and language choice also affects throughput. For data-intensive workloads, higher-throughput runtimes (for example, Java) can often sustain higher processing rates than lower-throughput options (for example, Python), depending on workload characteristics and implementation details.