Configuration Reference
A consolidated reference for the server’s knobs.
Server (patinadb-raft)
| Flag | Default | Notes |
|---|---|---|
--config <path> | off | Load settings from a YAML config file (see below). |
--print-config-schema | off | Print a JSON Schema for the config file to stdout and exit. |
--check-config | off | Validate the resolved config, print what was understood, and exit without booting — an nginx -t-style dry run (see below). |
--id <u64> | required | Unique Raft node id (here or in the config file). |
--addr <host:port> | required | HTTP (REST + management + peer RPC). |
--db <dir> | required | Database root (one subdir per database). |
--bootstrap | off | Self-init a single-voter cluster. |
--join <member> | off | Auto-join an existing member as a learner on startup. Excludes --bootstrap. |
--bolt-addr <addr> | 127.0.0.1:7687 | Bolt listener; "" disables Bolt. |
--advertised-addr <a> | = --bolt-addr | Public Bolt address for routing behind a proxy. |
--auth-user <name> | neo4j | Auth username. |
--auth-password <p> | "" | Shared password. Empty = no auth, fail-closed: the node won’t start without --insecure-disable-auth. |
--insecure-disable-auth | off | Explicitly allow running open (empty password). Trusted networks only. |
--tls-cert <path> | off | PEM cert chain. With --tls-key, serves the HTTP plane (REST + peer RPCs) over HTTPS. |
--tls-key <path> | off | PEM private key (required with --tls-cert). |
--tls-ca <path> | system roots | PEM CA peers verify each other with (self-signed / private-CA clusters). |
--require-metrics-auth | on (default) | Gate GET /metrics behind Basic auth. Default-on since series carry db=<name> labels. |
--insecure-open-metrics | off | Opt out of metrics auth — serve /metrics open on a private monitoring network. |
--query-timeout-secs <n> | 300 | Per-request budget for a REST /cypher read; overrun → 503 (deadline, not a hard cancel). 0 = off. |
--max-concurrent-requests <n> | 512 | Cap on in-flight HTTP requests; excess shed with 503. 0 = unlimited. |
--readiness-max-lag <n> | 50 | /ready apply-lag tolerance: report not ready (503 lagging) when last_log_index − last_applied exceeds this. See Day-2 operations. |
--otel-endpoint <url> (PATINADB_OTEL_ENDPOINT) | unset | Enable request/trace correlation (X-Request-Id + inbound W3C traceparent) and force structured JSON logs for OpenTelemetry-collector ingestion. See Day-2 operations. |
--allow-csv-dir <dir> | deny-all | Directory LOAD CSV FROM 'file://…' may read from. Repeatable. Unset ⇒ every LOAD CSV file read is refused. See Cypher file I/O. |
--allow-export-dir <dir> | deny-all | Directory the CSV export procs (patinadb.export.*) may write to. Repeatable. Unset ⇒ every export write is refused. |
--rbac-closed (PATINADB_RBAC_CLOSED) | off | Closed-mode RBAC: deny a non-admin any database it holds no explicit grant on. See Authentication & TLS. |
--telemetry-degrade-override-until <value> (PATINADB_TELEMETRY_DEGRADE_OVERRIDE) | unset | Time-boxed break-glass: keep writes enabled past a missed telemetry grace window until an absolute deadline. See Day-2 operations. |
--bookmark-wait-timeout-secs <n> (PATINADB_BOOKMARK_WAIT_TIMEOUT_SECS) | 10 | How long a Bolt BEGIN/RUN will block for this node to catch up to an incoming driver-session bookmark before failing with a retryable BookmarkTimeout. 0 disables the wait (bookmarks are parsed but ignored). See Bolt → Causal consistency. |
Environment: PATINADB_AUTH_PASSWORD sets the auth password (preferred over a
shell flag).
More server flags (RBAC, Bolt/transaction limits, telemetry, ops)
The table above covers the flags you’ll set on day one. These round out the
full surface — most have safe defaults and only need touching for a specific
requirement (multi-tenant RBAC, transaction-buffer bounds, dead-voter
auto-repair, telemetry/licensing). Every one is also settable via
PATINADB_* env var and/or the YAML config file, same precedence rules.
| Flag | Default | Notes |
|---|---|---|
--cluster-secret <s> (PATINADB_CLUSTER_SECRET) | = --auth-password | Dedicated peer /raft/* secret, separate from the root-admin credential so the two rotate independently. |
--rbac-closed (PATINADB_RBAC_CLOSED) | off | Deny a non-admin any database it holds no explicit grant on (tenant isolation). See Authentication & TLS. |
--rbac-rel-grants (PATINADB_RBAC_REL_GRANTS) | off | Strict relationship-type RBAC: a label-scoped user must also hold a per-rel-type grant for every edge type traversed. See Authentication & TLS. |
--rbac-rel-property-grants (PATINADB_RBAC_REL_PROPERTY_GRANTS) | off | Strict relationship-property RBAC: a label-scoped user must also hold a per-(relType, prop) grant. See Authentication & TLS. |
--auth-max-attempts-per-min <n> (PATINADB_AUTH_MAX_ATTEMPTS_PER_MIN) | 0 (disabled) | Brute-force auth throttle, per source IP and per username. Blocks a failed-login flood before it reaches the argon2 verify. |
--max-reads-per-user <n> (PATINADB_MAX_READS_PER_USER) | 0 (unlimited) | Cap concurrent reads per authenticated principal — one user’s burst of expensive read procs can’t exhaust the blocking pool. |
--blocking-threads <n> (PATINADB_BLOCKING_THREADS) | tokio default (512) | Absolute ceiling on the tokio blocking-thread pool. |
--max-bolt-connections <n> | 1024 | Cap on concurrent authenticated Bolt connections; 0 = unlimited. See the note below (issue #440) — an unauthenticated connection never spends this budget. |
--bolt-handshake-timeout-secs <n> (PATINADB_BOLT_HANDSHAKE_TIMEOUT_SECS) | 10 | Close a Bolt socket that sends nothing (or stalls mid-handshake) before completing the version-negotiation handshake. 0 disables. |
--bolt-idle-timeout-secs <n> (PATINADB_BOLT_IDLE_TIMEOUT_SECS) | 300 | Close a Bolt connection that sits with no open transaction and no started result stream and sends no message for this long (covers a connection that finishes HELLO/LOGON then goes silent). 0 disables. |
--idle-tx-timeout-secs <n> (PATINADB_IDLE_TX_TIMEOUT_SECS) | 300 | Roll back + close an explicit Bolt transaction left idle this long (“idle in transaction” guard). 0 disables. |
--max-tx-ops <n> (PATINADB_MAX_TX_OPS) | 5,000,000 | Max buffered DeltaOps an explicit Bolt transaction may accumulate before it’s aborted. 0 = unlimited. |
--stream-pull-timeout-secs <n> (PATINADB_STREAM_PULL_TIMEOUT_SECS) | 300 | Cancel a streaming Bolt RUN result the client never PULLs within this window. 0 disables. |
--audit-max-entries <n> (PATINADB_AUDIT_MAX_ENTRIES) | 100,000 | Retention for the durable node-local security-audit log. 0 = unlimited. |
--auto-evict-after-secs <n> (PATINADB_AUTO_EVICT_AFTER_SECS) | 0 (disabled) | Auto-evict a voter that stays dead (unreachable + not replicating) past this many seconds, using the same quorum-preserving guard as POST /mgmt/evict-voter. See Day-2 operations. |
--license <path|token> (PATINADB_LICENSE) | none (community) | Signed license file or inline token. See Licensing & Telemetry. |
--install-name <text> (PATINADB_INSTALL_NAME) | unset (anonymous) | Opt-in, operator-chosen telemetry label. |
--telemetry-interval-secs <n> | 21600 (6h) | Heartbeat interval. |
--telemetry-grace-secs <n> | 259200 (72h) | Community-mode grace window before write-degrade. |
--disable-telemetry | off | Turn off telemetry entirely — requires a valid license (fail-closed otherwise). |
Also note PATINADB_MAX_CONCURRENT_RAFT_RPCS — a separate, env-var-only
backpressure cap (default 1024, 0 = unlimited) on inbound peer /raft/*
RPCs, distinct from --max-concurrent-requests (which governs client
traffic) so a peer-RPC flood and a client-request flood can’t shed each
other’s capacity.
YAML config file (--config)
Instead of (or alongside) flags, point the server at a YAML file mirroring the settings above:
# node.yaml
id: 1
addr: "127.0.0.1:21001"
db: "/var/lib/patinadb"
bootstrap: true
bolt_addr: "0.0.0.0:7687"
auth_user: "neo4j"
# auth_password: prefer the PATINADB_AUTH_PASSWORD env var over the file
query_timeout_secs: 30
max_concurrent_requests: 256
patinadb-raft --config node.yaml
Every field is optional; an absent key falls back to the CLI flag, then to the
built-in default. The YAML key names match the long flag names with -
replaced by _ (e.g. --bolt-addr → bolt_addr). Unknown keys are rejected so
a typo fails loudly.
Precedence (highest wins): an explicitly-passed CLI flag (or its bound env
var, e.g. PATINADB_AUTH_PASSWORD) > the config file > the built-in
default. A flag left at its clap default does not override a value set in the
file — only flags the operator actually typed do. The required settings (id,
addr, db) may come from either the file or flags; if neither supplies one,
startup fails with a clear error.
Config JSON Schema (--print-config-schema)
patinadb-raft --print-config-schema prints a JSON Schema (Draft 7) for the
config file — every property carries its description (lifted from the Rust
doc-comments) so editors can offer autocompletion and validation. It works
without any other arguments:
patinadb-raft --print-config-schema > patinadb-config.schema.json
Config dry run (--check-config)
patinadb-raft --check-config is an nginx -t for patinaDB: it resolves the
effective configuration exactly the way a real boot does — CLI/env >
<VAR>_FILE/_COMMAND > --config file > default — prints what it
understood, validates it, and exits without starting the node. Exit code
0 means valid, 1 means a setting is invalid or a runtime env knob is set to a
value the engine would silently ignore. Drop it into a deploy script or CI gate:
patinadb-raft --id 1 --addr 127.0.0.1:21001 --db /var/lib/patinadb \
--bootstrap --auth-password "$PW" --cache-limit 40% --check-config || exit 1
It prints the resolved node identity, cluster mode, security posture (flagging auth-on-without-TLS), every limit with its effective value, the resolved cache budget in bytes (so you can see the caches are actually enabled and with how much), the telemetry/licensing state, and the filesystem sandbox.
Its most valuable job is the runtime env-knob scan. Several engine knobs
(PATINADB_MAX_AGG_ROWS, PATINADB_CACHE_LIMIT, …) are read lazily deep in the
engine and fall back to their default if the value doesn’t parse — silently.
So PATINADB_MAX_AGG_ROWS=5M looks like five million but is ignored (the runtime
wants a plain integer, 5000000), and — the case that motivated this feature — a
PATINADB_CACHE_LIMIT typo could silently leave the cache off. --check-config
resolves each set knob with the same parser the runtime uses and fails the
check on any set-but-unparseable value, naming it:
Runtime env knobs (curated — set values only)
PATINADB_CACHE_LIMIT 512MiB → 536870912 bytes
PATINADB_MAX_AGG_ROWS 5M ✗ expected a plain integer, got '5M'
configuration INVALID — 1 problem(s):
✗ PATINADB_MAX_AGG_ROWS is set to '5M' but expected a plain integer … SILENTLY IGNORED
Secrets management
The three sensitive server settings — PATINADB_AUTH_PASSWORD (root-admin
credential), PATINADB_CLUSTER_SECRET (peer /raft/* shared secret), and
PATINADB_LICENSE — can be sourced without ever putting the plaintext value in a
-e env var or a config file. patinaDB embeds no secrets-manager SDK; two
conventions integrate one while keeping its client out of the binary (the server
only ever sees the resolved value at startup):
-
<VAR>_FILE— names a file whose contents are the real value (mirrorsPOSTGRES_PASSWORD_FILE). Point it at a Docker Composesecrets:file, a KubernetesSecretvolume, or a tmpfs file a Vault Agent / AWS or GCP Secrets Store CSI-driver sidecar keeps refreshed — the DB reads the file, the sidecar owns the fetch + rotation. This is the pattern to prefer when you need auto-rotation. A trailing newline is trimmed. -
<VAR>_COMMAND— names a shell command whose stdout (trailing newline trimmed) is the value, for a direct dynamic fetch with no sidecar:PATINADB_AUTH_PASSWORD_COMMAND="vault kv get -field=pw secret/patinadb" # AWS: "aws secretsmanager get-secret-value --secret-id patinadb/pw \ # --query SecretString --output text" # GCP: "gcloud secrets versions access latest --secret=patinadb-pw"The command runs via
sh -c "<cmd>"once at startup (the fetch CLI must be present + authenticated). Fail-closed: a command that can’t spawn, exits non-zero, or produces empty output aborts startup with a loud error — it never silently falls back to “no secret” (which would look like--insecure-disable-auth). The fetched value is never logged.
Precedence (most specific wins):
direct flag/env > <VAR>_FILE > <VAR>_COMMAND > config file >
built-in default. Both _FILE and _COMMAND are only consulted when the direct
flag/env is unset. See deploy/README.md for the sidecar + command examples.
Resource limits & quotas
Guards that bound the cost of a single query so one statement can’t exhaust
memory or run unbounded. The engine budgets are environment variables; the
two server request limits are flags (see the patinadb-raft table above).
| Limit | Where | Default | Purpose |
|---|---|---|---|
PATINADB_MAX_HOPS | env | 1000 | Depth cap for an unbounded variable-length hop ([*], or [*..n] with n unset). An explicit [*a..b] in the query always wins. Prevents runaway traversal on a cyclic graph. |
PATINADB_CARTESIAN_CAP | env | 10000 | Max rows a disjoint (cross-product) multi-MATCH may produce before the query errors. Stops an accidental N×M blow-up. |
PATINADB_MAX_AGG_ROWS | env | 5000000 | Cap on the O(input)/O(result) row buffers behind GROUP BY/aggregate, a full (non-top-K) ORDER BY, UNION dedup, and a hash-join build side. A clear error over the cap instead of a silent OOM. See Query Planning. |
PATINADB_MAX_CAPTURE_OPS | env | 5000000 | Cap on the number of resolved ops a single write statement (the Raft leader’s resolve step) may buffer before recording one engram/Raft entry. A whole-graph SET/bulk CREATE over the cap fails with a clear error pointing at CALL {…} IN TRANSACTIONS instead of risking an OOM or a giant single Raft entry. |
PATINADB_MAX_ALGO_WORK | env | 1e9 | Static work-budget backstop for O(V·E) read procedures (betweenness/closeness): refuses a call whose estimated n·(n+e) exceeds this, so a Reader can’t trigger unbounded compute even without --query-timeout-secs. Admits realistic analytic graphs (10k nodes / 50k edges ≈ 6e8); raise it for a genuinely large centrality run. See Day-2 operations. |
PATINADB_MAX_SNAPSHOTS | env | unlimited (0/unset) | Prunes on-disk periodic time-travel snapshot files down to the N most recent (+ every pinned/tagged one) after each snapshot-taking commit. A long-lived, write-heavy database otherwise grows snapshot files unbounded; pruning only slows reconstruction of an old, out-of-window engram — every AS OF result stays byte-identical. |
Write pipelining
| Knob | Default | Purpose |
|---|---|---|
PATINADB_PIPELINED_WRITES | on | Lets concurrent autocommit writes to one database resolve in parallel instead of serializing behind each other’s whole Raft round-trip, admitting each under a short optimistic conflict check. =0 is the rollback valve — writes then serialize per database, as before. |
Pipelining is what makes the optimistic concurrency model in
Production Deployment the one
you actually get: with it on, a write–write conflict between concurrent writers
is detected and reported as the driver-retryable
Neo.TransientError.Transaction.LockClientStopped rather than being avoided by
serializing everyone. There are no lost updates either way — a client (or,
better, a driver’s managed transaction) simply retries the transient.
The trade-off is isolation: pipelined autocommit writes run at snapshot isolation, the same level Bolt’s explicit transactions already use, so write skew is possible — two writers each reading a value and writing disjoint rows based on it leave no write–write overlap to detect. That matters only for a multi-row invariant that genuinely needs serializability; the sentinel-node pattern in Concurrency & retries turns such a case back into a detectable conflict. Turning the knob off restores serialized execution per database at a real throughput cost (measured ~4–5× on a disjoint-write workload).
Time-travel performance knobs
These change only how an AS OF read is served, never what it returns — a
historical result is byte-identical whichever path runs. Both are read-path
accelerations with a fallback that is always correct, so they are safe to flip
either way; see Time Travel for the full
picture.
| Knob | Default | Purpose |
|---|---|---|
PATINADB_PARTIAL_TIMETRAVEL | on | Reconstructs only the subgraph an AS OF read actually touches, when the query’s labels/relationship types are statically bounded (an unbounded shape falls back to a full reconstruct). Read-only, so a write-only workload pays nothing. =0 is the rollback valve — set it if you ever suspect the partial path; the full reconstruct then serves every read, slower but identical. |
PATINADB_INDEXED_SNAPSHOTS | off | Builds a point-queryable .snapidx sidecar per snapshot so a cold single-vertex AS OF lookup is a point read instead of a full-graph rebuild. Opt-in because it costs disk. The build is lazy (triggered by a read that would benefit, never by a commit) and asynchronous (it never blocks the read that triggers it — that read is served by the scan path), so enabling it can’t stall a writer or the server’s apply loop. |
PATINADB_MAX_SNAPSHOT_INDEXES | 16 | Caps how many .snapidx sidecars may exist at once (0 = unlimited). Bounds sidecar count, not bytes — worst-case disk is roughly N × the size of a snapshot file. Only relevant when PATINADB_INDEXED_SNAPSHOTS is on. |
Why
PATINADB_INDEXED_SNAPSHOTSis opt-in. It earns its keep on repeated historical point reads: the read that triggers a build is served by the scan path, so the sidecar only helps later reads. If your backend time-travels rarely, or reads a different point each time, leave it off — you’d pay the disk without the win. Turn it on when a known set of historical points is read repeatedly. |--query-timeout-secs| server flag |300| Per-request wall-clock budget for a REST read; overrun →503.0= off. | |--max-concurrent-requests| server flag |512| Max in-flight HTTP requests; excess shed with503.0= unlimited. |
Set an env budget to 0 (or unset) to fall back to the built-in default. Tighten
them as DoS guards on a shared node, or raise PATINADB_MAX_HOPS for a
genuinely deep graph. A RETURN … LIMIT k is the normal way to bound result
size — there is no implicit result cap (an unlimited query streams every row).
For observability, set PATINADB_SLOW_QUERY_MS (server env, off by default)
to log a WARN for any REST query slower than that many milliseconds — carrying
the query’s normalized shape (literals + $params folded to ?), not raw
values. Per-shape latency stats are also served at GET /mgmt/queries.
Commercial entitlements
The server resolves a set of commercial caps (cluster HA size, combined
node+edge scale, database count, history-retention window, and two feature
gates) either from the hard-coded Community ceiling or from a signed license
token’s entitlement claims. This is configured entirely by which license you
install (--license / PATINADB_LICENSE / <db-root>/license.key — see
Licensing & Telemetry), not by a server flag. See
Editions & Limits for the full Community/Pro/Enterprise table,
what each limit does when you hit it, and how to read a running node’s
resolved tier + live usage via GET /version (also scraped onto
patinadb_entitlement_usage_ratio{axis} / patinadb_entitlement_limit{axis}
Prometheus gauges).
Cache memory budget
patinaDB can keep a governed, RAM-budgeted cache of decoded objects / adjacency
/ query results above the OS page cache. The server defaults it ON, at
PATINADB_CACHE_LIMIT=40% of the resolved memory limit, when you leave the
flag/env var/config key unset — so a fresh node ships with caching already
engaged. Set PATINADB_CACHE_LIMIT=0 (or the equivalent flag/config key)
explicitly and the cache is entirely off — only the OS page cache and the
existing plan/stats caches are used, at zero residual cost.
The budget derives from a cgroup-aware total (the real ceiling the kernel
OOM-kills at in a container, not the host’s RAM): cgroup v2 memory.max →
v1 memory.limit_in_bytes → host MemTotal, taking min(cgroup, MemTotal).
From that total, PATINADB_MEMORY_LIMIT is patinaDB’s own-heap ceiling (not
including the OS page cache), and four regions are carved from it. Each knob is
an absolute size (8GiB), a fraction of its parent (40%), or auto;
precedence is explicit-absolute > fraction > default, and fractions compose
against the resolved parent.
| Env var | Region | Default | Parent |
|---|---|---|---|
PATINADB_MEMORY_LIMIT | own-heap ceiling (excl. page cache) | auto = TOTAL − min_free | discovered TOTAL |
PATINADB_CACHE_LIMIT | cache pool (L1/L2/L3) — unset/0 disables caching | 40% | MEMORY_LIMIT |
PATINADB_WORK_MEM_LIMIT | action reserve (concurrent query working memory) | 45% | MEMORY_LIMIT |
PATINADB_MEM_HEADROOM | transient-spike / allocator-slop / OOM safety | 15% | MEMORY_LIMIT |
PATINADB_CACHE_MIN_FREE | page-cache floor (system free RAM kept resident for the storage engine’s page cache) | max(1GiB, 10%) | discovered TOTAL |
Every knob is also a patinadb-raft flag (--memory-limit, --cache-limit,
--work-mem-limit, --mem-headroom, --cache-min-free) and a YAML config key
(memory_limit, cache_limit, work_mem_limit, mem_headroom,
cache_min_free), with the same explicit-flag/env > file > default
precedence as every other setting.
The budget is validated at startup and fails loud (the node refuses to boot)
when it over-commits — CACHE_LIMIT + WORK_MEM_LIMIT + HEADROOM ≤ MEMORY_LIMIT
and MEMORY_LIMIT + CACHE_MIN_FREE ≤ TOTAL — with an error naming the offending
knobs and the resolved bytes. When enabled, the fully-resolved budget (bytes per
region) is logged at startup so it is never a mystery.
Example — a container with memory.max=16GiB, PATINADB_CACHE_LIMIT=40% and
otherwise defaults: TOTAL=16GiB, min_free≈1.6GiB, MEMORY_LIMIT≈14.4GiB
(TOTAL − min_free), then Cache ≈5.8GiB · Actions ≈6.5GiB · Headroom ≈2.2GiB.
Set PATINADB_MEMORY_LIMIT=10GiB to leave more RAM to the page cache on a
read-heavy deployment.
Internal defaults (informational)
These are not user-configurable flags today, but are useful to know:
| Setting | Value |
|---|---|
| Snapshot interval | every 50 commits |
| Raft election timeout | 750–1500 ms |
| Raft heartbeat | 250 ms |
| BM25 parameters | k1 = 1.2, b = 0.75 |
| Full-text prefix/fuzzy expansion cap | 256 terms |
| Bolt streaming channel | 256 records (bounded) |
| Default database name | default |
On-disk layout
The --db directory contains the B-tree tables for the graph, the
property/compound indexes, the engram log and snapshots, the full-text catalog
and index data, and the persistent Raft log and state-machine metadata. Back
up the whole directory as a unit. Each database is a subdirectory of --db.