Performance Tuning
VeltrixDB's throughput and latency are governed by a small number of knobs: the WAL/VLog flush window, cache size, batching, and admission control. This page summarizes the tuning guidance from the project's performance guide.
The most important knob: flush windows
Both the WAL and VLog use group-commit: writes arriving within a configurable window share a single fdatasync. Keep -wal-flush-window-ms and -vlog-flush-window-ms equal at all times.
writes/s ~ goroutines x (1000 / window_ms)
| Goal | Window | P99 write |
|---|---|---|
| Lowest latency (low concurrency) | 0 ms | ~0.2 ms |
| Low latency | 2 ms | ~2.2 ms |
| 100K+ writes/s | 5 ms | ~5.2 ms |
| Default (balance) | 10 ms | ~10.2 ms |
| Maximum throughput | 20 ms | ~20.2 ms |
./veltrixdb --wal-flush-window-ms 5 --vlog-flush-window-ms 5
Widening the window trades write latency (one window's worth, at most) for higher sustained write throughput by amortizing fdatasync across more writers per batch.
Batch operations
Always prefer MultiPut/MultiGet over individual PUT/GET calls when operating on multiple keys — they share a single network round trip.
| Method | Throughput | When to use |
|---|---|---|
| Individual PUT | ~10K/s per goroutine at a 10 ms window | Single key |
| MultiPut, 1024 keys | ~108K/s (macOS) / ~426K/s (Linux) | Any multi-key write |
The TCP server automatically coalesces back-to-back PUTs from the same connection into a MultiPut, so batching benefits apply even without client-side changes.
Disk density and packing
Batched writes pack multiple records per 4 KB VLog block, which materially changes on-disk density at small value sizes:
| Avg value | Disk per record (packed) | Gain vs. legacy 4 KB |
|---|---|---|
| 64 B | 88 B | 47x |
| 128 B | 152 B | 27x |
| 256 B | 280 B | 15x |
| 512 B | 536 B | 7.6x |
| 4 KB+ | 4096 B | 1x |
Packing is automatic for MultiPut and pipelined PUTs. A single unbatched Put uses the unpacked lock-free path instead. You can verify actual packing efficiency with veltrixdb_vlog_file_bytes / veltrixdb_storage_writes_total ~ value_size + 24.
Cache tuning
-cache 65536 # 64 GB
--auto-tune # VeltrixDB picks 85% of available RAM
--read-heavy # 400 GB preset + scan-resistant tuning
-read-heavy is a preset documented in the server flags reference as a 400 GB cache with an extended GC interval — use it for workloads dominated by reads where you can afford more RAM and want to bias GC toward less frequent, larger passes.
Check cache effectiveness with veltrixdb_cache_hits_total / (hits + misses). Small values (≤256 B) resist eviction more aggressively under the LIRS policy, so workloads with many small values benefit the most from a larger cache.
Admission control
VeltrixDB throttles background GC based on an exponentially-weighted moving average (EWMA) of read latency, to keep GC from competing with foreground reads for NVMe bandwidth:
| Read EWMA | What happens |
|---|---|
| < 2 ms | Full speed |
| < 3 ms | Normal |
| ≥ 3 ms | GC capped at 60 MB/s |
| ≥ 4 ms | GC paused, and each PUT sleeps 2 ms |
| No reads for 4+ minutes | EWMA considered stale — GC auto-resumes |
If veltrixdb_storage_write_admission_throttles_total is rising, read latency has crossed the 4 ms threshold and writes are being intentionally slowed to protect read latency. Diagnose the underlying cause with veltrixdb_storage_read_latency_seconds before changing any other knob — raising the cache size or flush window will not help if the disk itself is the bottleneck.
Common problems
| Symptom | Check | Fix |
|---|---|---|
| Write P99 high | veltrixdb_storage_write_admission_throttles_total rising | Read latency too high — see admission control above |
| Cache hit rate low | vlog_gc_skipped_empty_total rising | Increase -cache or enable KV separation |
| GC not running | vlog_gc_skipped_paused_total rising | Wait up to 4 minutes for the stale EWMA to clear |
reads_total = 0 with live traffic | Binary version | Update — a fixed release restores the missing metrics call |
| False failure-detector alerts (single node) | fd.SetLocalNode not called | Call it before fd.Start() in your cluster setup |