Helm Chart
The VeltrixDB Helm chart deploys a production-ready cluster on Kubernetes with per-pod local NVMe storage, Prometheus monitoring, and safe rolling updates — optimized for GKE but portable to any cloud with local NVMe instance types.
Prerequisites
| Requirement | Detail |
|---|---|
| Kubernetes | 1.27+ |
| Helm | 3.x |
| Prometheus Operator | 0.65+ (only required if serviceMonitor.enabled) |
| GKE node pools | Created with --local-ssd-interface=NVME |
--local-ssd-interface=NVME. Without it, GKE merges all local SSDs into a single RAID-0 device and you lose the parallel I/O benefit.gcloud container node-pools create veltrixdb-pool \
--cluster=my-cluster \
--zone=us-central1-a \
--machine-type=n2-highmem-64 \
--num-nodes=3 \
--local-ssd-count=8 \
--local-ssd-interface=NVME \
--disk-size=100GB
Installation
# Add the chart repo
helm repo add veltrixdb https://charts.veltrixdb.io
helm repo update
# Install with defaults
helm install veltrixdb veltrixdb/veltrixdb \
--namespace veltrixdb \
--create-namespace
# Install with custom values
helm install veltrixdb veltrixdb/veltrixdb \
--namespace veltrixdb \
--create-namespace \
--values my-values.yaml
Verify the deployment
# Wait for PVs (3 nodes x 8 disks = 24 volumes)
kubectl get pv | grep local-nvme
# Check pods are running
kubectl get pods -n veltrixdb -l app.kubernetes.io/name=veltrixdb
# Test connectivity
kubectl exec -n veltrixdb veltrixdb-0 -- \
sh -c 'echo PING | nc 127.0.0.1 9000'
# Expected: PONG
What the chart deploys
Confirmed against the templates in VeltrixDB-Helm-Chart/templates/: statefulset.yaml, service.yaml, service-headless.yaml, servicemonitor.yaml, pdb.yaml, prometheusrule.yaml, configmap.yaml, secret.yaml, rbac.yaml, and _helpers.tpl.
- StatefulSet — VeltrixDB pods with stable per-pod hostnames (
podManagementPolicy: Parallel, one volume-claim template per NVMe disk). - Headless + client Services — the headless service backs gossip/StatefulSet DNS (
publishNotReadyAddresses: trueso bootstrap peers are reachable pre-readiness); the client service fronts application traffic. - ServiceMonitor — Prometheus Operator scrape config, one target per pod.
- PodDisruptionBudget — bounds voluntary disruptions during node drains and upgrades.
- PrometheusRule — a bundled Prometheus alerting ruleset (see below).
- ConfigMap / Secret — engine configuration env vars and the cluster bootstrap token.
- RBAC — ServiceAccount plus any Role/RoleBinding the pods need.
daemonset.yaml in this chart's templates/ directory. Disk formatting (XFS) and the kubelet mount-namespace workaround for GKE/EKS/AKS are handled by the nvme-prep / mount-keeper DaemonSets that the Kubernetes Operator provisions dynamically per VeltrixCluster, or that you apply directly from VeltrixDB-Kubernetes-Operator/config/manager/nvme-mount-keeper.yaml if running the Helm chart without the operator. Deploy it before helm install — see NVMe Disk Provisioning.Pod Disruption Budget
The chart's default values.yaml sets podDisruptionBudget.maxUnavailable: 1 — at most one pod may be voluntarily disrupted at a time, which for a 3-node, RF-3 cluster keeps at least 2 nodes available during drains and rolling upgrades.
podDisruptionBudget:
enabled: true
maxUnavailable: 1
PrometheusRule alerts
The bundled PrometheusRule (rendered when serviceMonitor.enabled: true) ships 24 alert rules grouped into 10 rule groups, spanning write/read/delete latency, VLog GC health, cluster membership, replication, per-disk health, background scrubbing, and Bloom filter efficiency.
| Rule group | Alerts | Covers |
|---|---|---|
veltrixdb.write | 2 | VeltrixHighWriteLatency (P99 > 50ms), VeltrixWALFlushStalled |
veltrixdb.read | 2 | VeltrixHighReadLatency (P99 > 5ms), VeltrixLowCacheHitRate (< 50%) |
veltrixdb.delete | 2 | VeltrixHighDeleteLatency, VeltrixTombstoneBacklogGrowing |
veltrixdb.vlog | 5 | VeltrixVLogHighGarbageRatio, VeltrixVLogGCStalled, VeltrixVLogGCPermanentlyPaused, VeltrixVLogGCReadErrors, VeltrixVLogFileSizeGrowing |
veltrixdb.cluster | 4 | VeltrixNodeDown, VeltrixClusterSizeLow, VeltrixFrequentRebalances, VeltrixNodeFailed |
veltrixdb.replication | 2 | VeltrixHighReplicationLag (> 100ms), VeltrixReplicationFailures |
veltrixdb.disk | 2 | VeltrixSlowDisk (per-disk latency EWMA ≥ 5x cluster median), VeltrixDiskWriteLatencyHigh (> 20ms) |
veltrixdb.scrub | 2 | VeltrixCorruptionDetected (CRC32C/magic mismatch), VeltrixScrubReadErrors |
veltrixdb.bloom | 1 | VeltrixBloomFalsePositiveRateHigh (> 10%, design point ~1%) |
veltrixdb.gc | 2 | VeltrixGCEmergencyRunsSustained (garbage ≥ 65% for 30 min), VeltrixWriteAdmissionThrottling |
- alert: entries directly in templates/prometheusrule.yaml gives 24 across the 10 groups above — use the table as the current source of truth.The most operationally important two: VeltrixCorruptionDetected fires the moment the background scrubber finds a CRC32C or magic mismatch (severity critical, for: 0m) — treat as an immediate SMART-check-and-resync trigger. VeltrixGCEmergencyRunsSustained fires when VLog garbage has sat above the emergency threshold (65%) for 30 minutes and GC is bypassing admission control just to keep up — it means sustained write rate is exceeding GC throughput and you need to reduce load or add disks.
Configuration reference
Key settings from values.yaml. Full defaults are tuned for GKE n2-highmem-96 nodes (480 GB RAM, 96 vCPU, 8 x 375 GiB local NVMe SSDs).
| Parameter | Default | Description |
|---|---|---|
replicaCount | 3 | Number of VeltrixDB pods; must match node pool size for correct anti-affinity placement. |
image.tag | v1.0 | VeltrixDB container image tag. |
storage.disksPerNode | 8 | NVMe disks per pod; must match --local-ssd-count on GKE. |
storage.diskSizeGi | 375 | Capacity per NVMe disk (GKE local SSD = 375 GiB). |
storage.storageClassName | local-nvme | StorageClass created by the infra provisioner. |
storage.numShards | 1024 | Index shard count; must be a power of 2 matching the compiled C++ constant. |
storage.keyValueSeparation | true | WiscKey-style KV separation — keys in SSTables, values in per-disk VLog. |
storage.vlogGCThreshold | 0.30 | VLog garbage fraction that triggers defragmentation. |
storage.walFlushWindowMs | 10 | WAL group-commit window (ms). Must equal vlogFlushWindowMs. |
storage.vlogFlushWindowMs | 10 | VLog fdatasync group-commit window (ms). |
storage.walMaxBatchEntries | 4096 | Max WAL entries per group-commit flush before early timer fire. |
storage.rawVlog.enabled | false | Write VLog directly to /dev/nvmeXnY, bypassing XFS. Saves ~25 µs P99 read tail / ~70 µs P99 write tail. Adds SYS_RAWIO capability and a /dev hostPath mount. |
storage.rawVlog.devices | [] | Explicit block-device paths; when empty, inferred as /dev/nvme0n1 ... /dev/nvme(N-1)n1 from disksPerNode. |
cache.maxSizeMB | 262144 | LIRS cache size in MiB — 256 GiB, sized for 480 GiB nodes (80% rule). |
cache.lirRatio | 0.90 | Fraction of cache reserved for the high-locality (LIR) resident set. |
server.port | 9000 | Plain TCP protocol port. |
server.tlsPort | 9443 | TLS port, active only when tls.enabled. |
server.metricsPort | 2112 | Prometheus metrics + /healthz + /readyz. |
cluster.gossipPort | 7946 | UDP gossip failure-detector port. |
cluster.replicationFactor | 3 | Desired replica count per shard. |
resources.requests.memory | 480Gi | Memory request per pod. |
resources.requests.cpu | 64 | CPU request per pod. |
resources.requests.hugepages-2Mi | 2Gi | Hugepages for the C++ allocator and io_uring SQPOLL fixed buffers. Requires vm.nr_hugepages >= 1024 on the host. |
autoTune.enabled | false | Auto-detect hardware and self-size cache/shards/compaction threads. |
autoTune.sysTune | true | Apply sysctl/hugepages/scheduler tuning; requires CAP_SYS_ADMIN or root. |
auth.enabled | false | Enable RBAC authentication. |
auth.configSecret | "" | Secret name with key auth-config.json. |
tls.enabled | false | Enable TLS 1.3 on the server port. |
tls.existingSecret | "" | Secret with tls.crt/tls.key. |
tls.caSecret | "" | Secret with ca.crt; when set, enables mTLS. |
serviceMonitor.enabled | true | Create the ServiceMonitor + PrometheusRule. |
serviceMonitor.interval | 15s | Prometheus scrape interval. |
podDisruptionBudget.enabled | true | Create the PDB. |
podDisruptionBudget.maxUnavailable | 1 | Max pods voluntarily disrupted at once. |
livenessProbe / readinessProbe | /healthz @ 30s+10s x3 / /readyz @ 15s+5s x6 | HTTP probes on metricsPort. |
terminationGracePeriodSeconds | 60 | Grace period before SIGKILL, giving the WAL time to flush. |
affinity.podAntiAffinity | required, kubernetes.io/hostname | One VeltrixDB pod per physical host (NVMe disks are node-local). |
topologySpreadConstraints | maxSkew: 1, zone | Spreads pods evenly across zones. |
Performance tuning
For 100K+ writes/s, lower the flush windows (both must stay equal):
storage:
walFlushWindowMs: 5
vlogFlushWindowMs: 5
For maximum cache hit rate, size the cache to roughly 85% of node RAM:
cache:
maxSizeMB: 409600 # 400 GB on a 512 GB node
Security, audit, and data-integrity features
TLS and RBAC auth
auth:
enabled: true
configSecret: veltrixdb-auth
tls:
enabled: true
existingSecret: veltrixdb-tls
At-rest encryption (AES-256-GCM)
kubectl create secret generic veltrixdb-encryption \
-n veltrixdb \
--from-literal=key=$(openssl rand 32 | base64)
security:
encryptionAtRest: true
encryptionSecretName: veltrixdb-encryption
Each pod loads the key from the VELTRIXDB_ENCRYPTION_KEY env var at startup; without the secret, startup fails when security.encryptionAtRest is true. Key rotation is offline-only.
Background scrubber
scrubber:
enabled: true
mbPerSec: 50
CRC32C re-validation of every VLog record at a controlled rate; default 50 MB/s/disk gives roughly a 2-hour full pass on a 375 GB SSD. Mismatches surface via the VeltrixCorruptionDetected alert.
Raw NVMe block-device VLog
For latency-critical clusters, enable storage.rawVlog.enabled to write the VLog directly to block devices instead of XFS files. WAL and segment files continue to live on the XFS PVCs.
storage:
rawVlog:
enabled: true
devices:
- /dev/nvme0n1
- /dev/nvme1n1
- /dev/nvme2n1
- /dev/nvme3n1
- /dev/nvme4n1
- /dev/nvme5n1
- /dev/nvme6n1
- /dev/nvme7n1
| Metric | File-based VLog (default) | Raw block VLog |
|---|---|---|
| P99 random read | ~80 µs | ~55 µs |
| P99 4 KB write + sync | ~250 µs | ~180 µs |
| P99.99 spike under sustained writes | XFS journal blip every ~30 s | Flat |
mkfs.xfs on devices destined for raw VLog use. WAL/segment XFS partitions must live on a separate slice of each disk or on the boot disk.Production values.yaml example
replicaCount: 3
image:
tag: "1.0.0"
storage:
disksPerNode: 8
diskSizeGi: 375
cache:
maxSizeMB: 262144 # 256 GiB - recommended for 480 GiB nodes
storage:
walFlushWindowMs: 10
vlogFlushWindowMs: 10
resources:
requests:
memory: 480Gi
cpu: "60"
limits:
hugepages-2Mi: 2Gi
autoTune:
enabled: false
cluster:
gossipPort: 7946
replicationFactor: 3
auth:
enabled: true
configSecret: veltrixdb-auth
tls:
existingSecret: veltrixdb-tls
serviceMonitor:
enabled: true
interval: 15s
podDisruptionBudget:
maxUnavailable: 1
Upgrading
helm upgrade veltrixdb veltrixdb/veltrixdb \
--namespace veltrixdb \
--values my-values.yaml
The StatefulSet uses RollingUpdate with maxUnavailable: 1, so pods are replaced one at a time. The PodDisruptionBudget bounds voluntary disruptions from node drains that happen concurrently with the rollout.
Validating a deployment
After install or upgrade, validate the cluster end-to-end with the bench.sh harness shipped in the main VeltrixDB repository. It checks two hard gates: packing density (must stay at or below roughly 152 B/record at 128 B values) and GC emergency runs (must stay at 0 under sustained write stress).
kubectl port-forward -n veltrixdb veltrixdb-0 9000:9000 2112:2112 &
git clone https://github.com/VeltrixDB/veltrixdb.git
cd veltrixdb
ADDR=127.0.0.1:9000 METRICS_ADDR=127.0.0.1:2112 \
NUM_KEYS=10000000 \
BULK_DUR=120 STRESS_DUR=300 \
./scripts/bench.sh
Exit 0 means both gates passed; exit 1 means investigate /tmp/veltrixbench-out/run.log. Re-run after every upgrade that bumps image.tag.
Uninstalling
helm uninstall veltrixdb --namespace veltrixdb
PVCs are not deleted automatically, to protect your data:
kubectl delete pvc -n veltrixdb -l app.kubernetes.io/name=veltrixdb
Troubleshooting
| Symptom | Cause / fix |
|---|---|
Pods stuck in Pending | Check PVs are Available; verify the nvme-prep DaemonSet pods are Running; confirm --local-ssd-interface=NVME was set on the node pool. |
| Write throughput lower than expected on macOS / dev environments | F_FULLFSYNC costs ~7-10 ms on macOS — a hardware limitation, not a bug. Use Linux NVMe in production. |
| WAL batch size is 1 (no amortisation) | Compare veltrixdb_wal_flushes_total to veltrixdb_writes_total; if roughly equal, raise storage.walFlushWindowMs and storage.vlogFlushWindowMs together. |
io_uring buffer registration fails at startup | Needs RLIMIT_MEMLOCK or CAP_IPC_LOCK; add IPC_LOCK to the pod's securityContext.capabilities.add. |
| VLog not being compacted | Check vlog_gc metrics via curl localhost:2112/metrics inside a pod. |