CHRONICLE QUEUE IN PRODUCTION: ARCHITECTURE, TUNING, AND REAL-WORLD PATTERNS
You’re running a trading system that processes millions of events per day. Kafka adds milliseconds you can’t afford. Aeron adds network complexity you don’t need on a single machine. Chronicle Queue is the tool that sits between — sub-microsecond latency, disk-persisted, zero-GC on the hot path. But getting it right in production requires understanding how it actually works under the hood.
Who Is This Guide For?
Engineers evaluating Chronicle Queue for production trading systems, market data platforms, or any low-latency Java application. You already know the basics — this guide skips “Hello World” and goes straight to architecture, tuning, failure modes, and production patterns.
By the End of This, You’ll Know
- How Chronicle Queue achieves sub-microsecond latency through memory-mapped files and off-heap storage
- Which configuration parameters actually matter in production and why
- How to tune the JVM to minimize tail latency on the Chronicle hot path
- The failure modes that bite teams in production and how to handle them
- When to use Enterprise features like replication and Async Mode
- How to integrate Chronicle Queue with Kafka for a fast-path + distribution architecture
Architecture: How Chronicle Queue Actually Works
The performance characteristics of Chronicle Queue aren’t magic — they’re the direct result of a specific set of design decisions. Understanding these tells you why it behaves the way it does under load.
Memory-Mapped Files
Chronicle Queue persists every message to a memory-mapped file (mmap). When you call writeDocument(), the data goes straight into the OS page cache mapped to a .cq4 file. No serialization to heap. No kernel context switch for write. The file is the queue.
// Actual Chronicle Queue write - data goes directly to mmap'd file
ChronicleQueue queue = ChronicleQueue.single("/data/queue");
try (ExcerptAppender appender = queue.createAppender()) {
appender.writeText("message"); // Direct to page cache, zero-copy
}
This is why Chronicle Queue can write millions of messages per second on a single machine — it bypasses the traditional kernel networking stack entirely. The write path is: application → page cache → (eventually) disk. For local IPC between processes on the same machine, two processes sharing the same memory-mapped file can communicate at memory speed. According to Chronicle Software, the queue achieves sub-microsecond latencies for in-process communication, with Chronicle Queue Enterprise targeting under 40 μs at the 99.99th percentile for typical workloads.
Append-Only Design
Chronicle Queue writes sequentially. No index updates, no B-tree rebalancing, no random seeks. The appender keeps a single write pointer that advances linearly through the file. This is critical because sequential writes are 10-100x faster than random writes on spinning disks and still 3-5x faster on NVMe SSDs.
Off-Heap Storage
Data lives outside the Java heap entirely. The MappedByteBuffer references direct memory — the OS virtual memory system manages it. This means Chronicle messages never trigger a GC pause. A 1GB JVM can stream 1TB of data through Chronicle without a single young-gen collection caused by queue data.
The zero-GC property is the single most important reason Chronicle Queue exists. In HFT, a 10ms stop-the-world GC pause can mean missing a market event entirely.
Understanding Latency: Where Time Actually Goes
Before tuning, you need to know what you’re optimizing against. The typical sources of latency in a Chronicle Queue application (approximate ranges, environment-dependent):
| Source | Impact | Mitigation |
|---|---|---|
| Page faults | 10–100 μs | Pre-touch pages, use mlockall |
| TLB misses | 0.5–2 μs | Use 2MB huge pages |
| GC pauses | 1–100 ms | Keep Chronicle data off-heap |
| Context switches | 1–10 μs | Thread affinity, busy-spin vs blocking |
| Disk I/O | 10–100 μs | NVMe, proper roll cycle sizing |
The Foojay benchmark using JLBH (Java Latency Benchmarking Harness) demonstrates that Chronicle Queue Enterprise with Async Mode achieves sub-4μs latencies at the 99.99th percentile for 140-byte messages at 100K/sec. Open-source Chronicle Queue reaches approximately 50 μs at the 99.99th percentile under the same test conditions. The Foojay article covers the full benchmark methodology and results.
Production Configuration
Wire Type
Chronicle Queue supports multiple wire formats. The choice has real performance impact:
- WireType.BINARY_LIGHT — Default. Compact binary format optimized for Chronicle Queue. The queue defaults to BINARY_LIGHT unless explicitly configured.
- WireType.BINARY — Explicit binary wire type. More verbose than BINARY_LIGHT but provides full field metadata for cross-language compatibility.
- WireType.FIELDLESS_BINARY — Fastest but skips field metadata. The reader must know the field order. Useful for high-throughput scenarios where field names are predetermined.
- WireType.TEXT — Not supported in Chronicle Queue. The README explicitly states TEXT (including JSON and CSV), RAW, and READ_ANY are unsupported.
// Simple approach - ChronicleQueue.single() uses BINARY_LIGHT wire by default
ChronicleQueue queue = ChronicleQueue.single("/data/prod-queue");
// For custom configuration, use SingleChronicleQueueBuilder
ChronicleQueue queue2 = SingleChronicleQueueBuilder.builder()
.path(Path.of("/data/prod-queue"))
.wireType(WireType.BINARY)
.build();
Roll Cycles
Chronicle Queue rolls files based on time or size. The default is RollCycles.DAILY — one file per day. For production workloads at high throughput, this can create multi-GB files that increase recovery time.
ChronicleQueue queue = ChronicleQueue.single("/data/trades");
try (ExcerptAppender appender = queue.createAppender()) {
// Files roll based on the configured roll cycle
// For shorter rolls:
// SingleChronicleQueueBuilder.builder().rollCycle(RollCycles.HOURLY)
}
Rule of thumb: Target file rolls every 15-60 minutes for production. Files that take hours to roll mean longer recovery from crashes and harder debugging when something goes wrong.
Block Size
The blockSize() parameter controls the size of memory-mapped file blocks. The default is 64MB, which is sufficient for most production workloads. Increase it to reduce jitter from chunk creation in high-throughput scenarios:
ChronicleQueue queue = ChronicleQueue.single("/data/high-throughput");
// Default blockSize is 64MB
// For higher throughput, increase block size via builder:
ChronicleQueue queue2 = SingleChronicleQueueBuilder.builder()
.path(Path.of("/data/high-throughput"))
.blockSize(1024 * 1024 * 1024) // 1GB blocks
.build();
Larger blocks reduce the frequency of memory-mapped region creation but increase memory footprint. Profile against your workload.
File System Considerations
- Use XFS or ext4 with
noatimemount option. These filesystems have predictable sequential-write performance characteristics that align with Chronicle Queue’s append-only design. Avoid filesystems with copy-on-write semantics for latency-sensitive workloads. - Mount with
largepagesif using 2MB huge pages. - Separate the Chronicle data directory from the OS and application logs. A full root disk will corrupt your queue.
- NVMe is strongly recommended for production. SATA SSDs have higher p99 latency under sustained write loads.
JVM Tuning for Chronicle Queue
The JVM must be configured to leave Chronicle’s off-heap data alone.
GC Flags
-XX:+UseZGC # Or G1GC for JDK 17+
-XX:MaxGCPauseMillis=1
-XX:+DisableExplicitGC # Never call System.gc()
-Xms4g -Xmx4g # Fixed heap, no resizing
ZGC (experimental in JDK 11, production-ready since JDK 15) keeps pause times under 1ms even for multi-hundred-GB heaps. G1GC with MaxGCPauseMillis=1 is a reasonable fallback but will miss occasionally.
Huge Pages
# Enable transparent huge pages (Linux)
echo always > /sys/kernel/mm/transparent_hugepage/enabled
# Or use explicit 2MB pages
-XX:+UseLargePages
Huge pages reduce TLB misses, which directly translates to more consistent latency on the Chronicle write path. The improvement is measurable — typically 10-20% reduction in p99 latency.
Thread Configuration
# Pin Chronicle threads to specific cores via taskset
taskset -c 2-3 java -jar application.jar
# Or use thread affinity in code (from OpenHFT Java Thread Affinity)
# Affinity.bind(thread, cpuId) binds a thread to a specific CPU core
For trading systems, pinning Chronicle producer and consumer threads to dedicated cores eliminates context-switch jitter from other processes.
Tail Latency Optimization
The 99.99th percentile latency is where Chronicle Queue separates from alternatives. Here’s how to push it lower.
Chronicle Queue Enterprise Async Mode
Async Mode provides a multi-writer, multi-reader buffered queue implementation. It decouples the producer from the disk write, allowing the producer to return immediately while writes happen on a background thread. This flattens tail latency by removing disk-wait from the hot path.
According to Chronicle Software’s async mode documentation, async mode is enabled by setting buffer modes on the builder:
ChronicleQueue queue = ChronicleQueue.single("/data/async-queue");
try (ExcerptAppender appender = queue.createAppender()) {
// Async mode is enabled via writeBufferMode/readBufferMode (Enterprise)
// When writeBufferMode is set to Asynchronous, excerpts are written
// to the Chronicle Queue using a background thread
}
The trade-off: Async Mode can lose messages in a crash if the background write hasn’t flushed. Use it only when tail latency matters more than absolute durability.
JLBH Profiling
Use JLBH (Java Latency Benchmarking Harness) to profile your Chronicle Queue configuration before production:
# Clone and build JLBH
git clone https://github.com/OpenHFT/JLBH.git
cd JLBH
mvn package
# Run with Chronicle Queue
java -jar target/jlbh-examples.jar
JLBH accounts for coordination omission
— the common benchmarking mistake of ignoring pauses that occur between requests. The default java-chronicle-queue sample benchmark is a good starting point.
OS Tuning
# Lock Chronicle files in memory (prevents swapping)
sudo mlockall
# Increase max file descriptors
ulimit -n 1000000
# Reduce swappiness
echo 1 > /proc/sys/vm/swappiness
# Enable tuned-adm for latency profile
sudo tuned-adm profile latency-performance
# Set disk scheduler to none (NVMe) or deadline (SATA SSD)
echo none > /sys/block/nvme0n1/queue/scheduler
Enterprise Features Worth Knowing
The open-source version of Chronicle Queue covers the core queue functionality. Chronicle Queue Enterprise adds production-critical features:
Replication. Active-active queue replication across data centers for zero-downtime failover. Uses a multicast or point-to-point protocol to synchronize queue state. The latency cost per message varies depending on network topology and configuration — consult the Chronicle Software documentation for benchmark data relevant to your network.
Python Queue Enterprise. Quants typically build models in Python while production runs in Java or C++. Python Queue Enterprise lets Python read from the same shared-memory Chronicle Queue that Java writes to. No serialization, no copy, no “two-language problem.” The official documentation covers this use case at Chronicle Software.
Cross-Language Support. The .cq4 file format is portable across Java, C++, Rust, and Python. A C++ trading engine can write market data that a Java risk system reads from the same file, at memory-mapped speed.
Integration Patterns
Chronicle + Kafka Hybrid
This is the most common production pattern I see in fintech systems. The fast path goes through Chronicle Queue for sub-microsecond decision making. The same data gets asynchronously forwarded to Kafka for distribution, replay, and audit.
The bridge from Chronicle to Kafka reads from a Chronicle ExcerptTailer and publishes each message to a Kafka topic. This decouples the latency-sensitive path (sub-microsecond) from the distribution path (milliseconds).
Multi-Process IPC
Multiple processes on the same machine share a Chronicle Queue via the memory-mapped file. This is common when different services need to communicate without the overhead of TCP:
// Process A (Producer)
ChronicleQueue queue = ChronicleQueue.single("/dev/shm/shared-queue");
try (ExcerptAppender appender = queue.acquireAppender()) {
appender.writeText("event data");
}
// Process B (Consumer)
ChronicleQueue queue = ChronicleQueue.single("/dev/shm/shared-queue");
try (ExcerptTailer tailer = queue.createTailer()) {
String event = tailer.readText();
}
Using /dev/shm (tmpfs) for the queue path reduces latency further by keeping data in RAM only. Restarting the machine loses the queue — use only for ephemeral IPC, not durable storage.
Request-Response with Chronicle
For a full request-response implementation with timeouts, see my Chronicle Queue tutorial. The key insight is that Chronicle Queue excels at multiplexing multiple requesters onto a single response queue — each tailer maintains its own index position.
Failure Modes and Recovery
Disk Full
When the disk containing the Chronicle Queue fills up, writes will fail with an IOException. The queue stops accepting new messages. Recovery involves freeing space (deleting old .cq4 files) and restarting the queue.
Prevention: Set up disk monitoring at 80% capacity. Chronicle Queue Enterprise supports automatic file retention policies that delete files older than a configurable window.
File Corruption
Memory-mapped file corruption is rare but happens — from kernel bugs, failing hardware, or unclean shutdowns. Chronicle Queue performs data integrity validation on read — corrupted files manifest as checksum errors. The specific integrity mechanism is implementation-dependent and may vary by wire type.
Recovery: Chronicle Queue Enterprise provides repair capabilities. For open-source, replay from the original data source or a Kafka backup. Consult the Chronicle Software documentation for details on recovery procedures.
Process Crash
An unclean shutdown leaves the last partially-written excerpt in an indeterminate state. On restart, Chronicle Queue scans the last roll file and truncates to the last complete message. This is normally transparent, but can lose the in-flight message.
Mitigation: Use Chronicle Queue Enterprise’s write-ahead log for deterministic recovery. Or accept the risk — for many trading systems, losing the last microseconds of data is acceptable compared to the latency cost of a full transactional log.
Silent Data Loss
The most dangerous failure mode is also the most subtle. If a process writes to Chronicle Queue but the OS hasn’t flushed the page cache to disk when the power fails, the data is gone. The application saw the write succeed — the OS said it did — but it never hit the platter.
Mitigation: Call MappedFile.sync() after critical writes for guaranteed durability, or use Enterprise Edition’s configurable durability levels to balance performance against persistence guarantees.
Monitoring Chronicle Queue
These metrics matter for production health:
| Metric | What It Tells You | Alert Threshold |
|---|---|---|
| Write latency (p50/p99) | Write path health | p99 > 10 μs |
| File roll count | Whether roll cycle is appropriate | > 100 files/hour |
| Disk usage per queue | Storage consumption | > 80% of mount |
| Read-ahead cache hits | Whether tailer keeps up | Hits < 90% |
| Open file descriptors | Resource leak detection | Growing steadily |
Chronicle Queue provides monitoring via DumpMain (command-line) and programmatic access to queue state. The GitHub documentation covers how to obtain latency, throughput, and activity metrics. Wire these into your existing monitoring stack.
What You Can Actually Use Today
- Open-source Chronicle Queue (v5.x) — Covers core queue functionality, persistent messaging, zero-GC writes. Production-ready for single-machine use cases. Available on GitHub.
- Chronicle Queue Enterprise — Adds replication, Async Mode, cross-language support, and priority support from the Chronicle team. Licensed product from Chronicle Software.
- JLBH — Open-source latency benchmarking harness from OpenHFT. Use it to validate your configuration before production. GitHub.
- Chronicle Threads — Thread affinity and busylock utilities for pinning threads to cores. Open-source, included with the Chronicle ecosystem.
For a hands-on walkthrough with runnable code, see the getting started tutorial. For comparison with Aeron and Kafka, see the Chronicle vs Aeron and Chronicle vs Aeron vs Kafka benchmarks.