Licensing & Telemetry
patinaDB’s server (patinadb-raft) runs in one of two modes. The distributed
Docker image ships without a license, so by default a node runs in
community mode and sends an anonymous usage heartbeat to a telemetry server.
Installing a license file switches the node to licensed mode for
on-prem / offline / air-gapped operation, where telemetry is best-effort and can
be turned off entirely.
This chapter explains both modes, documents exactly what the telemetry heartbeat contains (and, just as importantly, what it never contains), and shows how to obtain and install a license.
The two modes
Community mode (default — no valid license)
A background heartbeat is mandatory. On startup the node sends an initial heartbeat, then repeats it on an interval. It tolerates transient network outages (retries with backoff, plus a long grace window), so a brief blip is harmless. But it must not run indefinitely offline:
- If no heartbeat succeeds within the grace window (default 72 hours),
the node degrades. It refuses client writes with a clear
503error and logs the reason loudly. - Reads keep working during and after the grace window — degradation only blocks writes.
- Control / admin commands (creating databases, users, indexes) are not blocked, so an operator can still recover the node.
- As soon as a heartbeat succeeds again, the block is lifted automatically and writes resume.
The refusal is surfaced as an HTTP 503 (REST) or a Bolt failure, with a
message explaining that the node could not reach the telemetry server and is
running unlicensed.
Licensed mode (valid license file)
A valid license unlocks on-prem operation:
- Telemetry is best-effort: it is still sent by default (so the maintainer can see version adoption), but a failure to send never blocks anything and the node never degrades.
- Telemetry can be turned off completely with
--disable-telemetry. --disable-telemetryis honored only with a valid license. An unlicensed node started with--disable-telemetryrefuses to start (fail-closed) with a clear message — a community node must send telemetry.
What the telemetry heartbeat sends
The heartbeat is a coarse, anonymous JSON POST. The payload is a strict
allowlist of counters and environment facts. Nothing outside the table
below is ever sent. This is not a promise on paper alone: an automated guard
test asserts the serialized payload’s key set equals this allowlist, and a
second doc↔code drift test parses this very table and asserts it lists exactly
the fields the code sends — so if a field is ever added to the wire without
being documented here (or vice versa), the build fails. The table cannot
silently drift from reality.
Every field, exhaustively:
| Field | Type | Meaning |
|---|---|---|
install_id | UUID | A random id generated once and persisted at <db-root>/.patinadb_install_id. Stable and anonymous — not derived from anything identifying. |
nonce | UUID | A fresh random value generated per heartbeat. It exists only so the server’s signed response can be bound to this exact request (anti-tamper — see Response signing); it carries no information about you. |
install_name | string | null | Opt-in, operator-chosen label (--install-name / PATINADB_INSTALL_NAME). Unset ⇒ null (anonymous), the default. This is the only free-text field and it is consent-based: it is whatever you type, and is never auto-derived from the environment (no hostname, no username). |
version | string | The node build version. |
protocol_version | integer | The Raft protocol version. |
os | string | Target OS (e.g. linux). |
arch | string | Target CPU architecture (e.g. x86_64). |
cores | integer | Logical CPU core count (≥ 1). A coarse hardware-sizing signal. |
memory_bytes | integer | Total host/cgroup RAM in bytes. A coarse hardware-sizing signal — not a live memory-usage figure. |
uptime_secs | integer | Seconds since this process started. |
node_count | integer | Cluster membership size (voters + learners). |
is_leader | boolean | Whether this node is currently the Raft leader. |
database_count | integer | Number of databases in the registry. |
total_vertices | integer | Aggregate vertex count across all databases (a coarse volume signal only). |
total_edges | integer | Aggregate edge count across all databases (a coarse volume signal only). |
requests_per_min | number | Coarse recent request rate: total recorded query executions ÷ uptime minutes. An aggregate count only — carries no query text. |
avg_query_ms | number | Mean query latency in milliseconds, aggregated (count-weighted) across all query shapes. An aggregate timing only — carries no query text. |
license_status | string | "community" or "licensed". |
license_customer | string | Licensed mode only. The licensee’s own customer id from the license (the buyer of the license — this is not an end-user). Omitted in community mode. |
What we NEVER send
The telemetry payload will never contain any of the following. When in doubt, it is left out:
- No database content — no rows, no graph data of any kind.
- No names — no property keys or values, no label or relationship-type names, and no database names.
- No queries — no query text or any fragment of one (the
requests_per_minandavg_query_msfigures are pure aggregate counters/timings). - No node or edge UUIDs.
- No hostname, IP address, username, or location. (
install_nameis the one label you may choose to send — it is never read from the host.) - No RBAC user names, engram messages, or authors.
- No personally-identifiable information of any kind.
Only the coarse counts and environment metadata in the table above leave the node.
How and when it sends; the endpoint
On startup the node sends one heartbeat, then repeats it on the interval. It is a
single coarse anonymous POST — see what it sends
above. The stable, anonymous install_id lets the maintainer count distinct
installs across restarts without knowing anything about who you are; the optional
install_name is a label you may choose to attach so your own installs are
recognizable in your reports — it defaults to anonymous and is never taken from
the host.
The endpoint is a compile-time constant baked into the binary — for the
distributed community build, the maintainer’s server. In a release build it is
NOT overridable: the --telemetry-endpoint flag and the
PATINADB_TELEMETRY_ENDPOINT environment variable are compiled out, so a
community node always phones the real host (redirecting it to /dev/null would
defeat the point of community telemetry). Only debug/test builds accept an
override, so the test suite can point at a mock server.
Honest note on the endpoint lock. Locking the endpoint is a soft deterrent, not a hard control. Telemetry is best-effort, so a determined operator can still firewall-block the host or patch the binary — we don’t pretend otherwise. The lock also means a licensed organization cannot point the heartbeat at its own telemetry server; the supported path for a licensed org that doesn’t want to phone home is
--disable-telemetry(turn it off), not redirection.
| Setting | Flag | Env var | Config key | Default |
|---|---|---|---|---|
| Heartbeat interval | --telemetry-interval-secs <n> | — | telemetry_interval_secs | 21600 (6h) |
| Grace window | --telemetry-grace-secs <n> | — | telemetry_grace_secs | 259200 (72h) |
| Opt-in install label | --install-name <text> | PATINADB_INSTALL_NAME | install_name | unset (anonymous) |
| Disable telemetry (licensed only) | --disable-telemetry | — | disable_telemetry | false |
The endpoint itself has no run-time flag in release (see above). See the Configuration Reference for how flags, environment variables, and the config file combine.
Response signing (anti-tamper server authentication)
Locking the endpoint stops a community node from redirecting its phone-home,
but a determined operator could still point telemetry.patinadb.org at a
fake server (via /etc/hosts or DNS) that just returns 200 OK — dodging
the degrade-after-grace without ever reaching the real host. To close that,
the real telemetry server cryptographically signs its ping response and the
node verifies the signature.
How it works:
- The node sends a fresh random
noncewith each heartbeat (see the field table above), alongside itsinstall_id. - The server signs its response. The genuine server holds an Ed25519
private key and returns
{ server_time, signature }, where the signature coversnonce ‖ install_id ‖ server_time. Only a server holding that private key can produce a signature the node accepts. - The node verifies the signature against a second Ed25519 public key
embedded in the binary (
TELEMETRY_RESPONSE_PUBLIC_KEY, separate from the license key), checks thenoncematches the one it sent, and checksserver_timeis fresh (within ±5 minutes, to bound replay). - The verdict feeds the grace clock. A valid signature is a successful heartbeat (it resets the grace clock). A missing, malformed, forged, or stale signature is a failed heartbeat — it does not reset the clock, so a community node still degrades after the grace window.
A fake server has no private key, so it cannot produce a valid signature and
therefore cannot stop the node from degrading. Because the signing key lives
entirely outside the TLS / system-trust chain, this defeats even an attacker who
has managed to insert a rogue CA into the host’s trust store (a plain
/etc/hosts redirect is already stopped by strict TLS certificate validation —
the node’s HTTPS client never disables cert/hostname verification).
Honest ceiling. This is an anti-forgery control, not an anti-patch control. A determined operator can still edit the embedded public key (or delete the verification) out of their own rebuilt binary, or simply firewall-block the host. Signing raises the bar from “edit one line in
/etc/hosts” to “patch and recompile the binary” — a soft deterrent consistent with the community-telemetry model. The supported opt-out for a licensed org remains--disable-telemetry, not evasion.
Opting out
There is exactly one supported way to stop telemetry, and it requires a license:
--disable-telemetry, honored only with a valid license. It turns the heartbeat off completely — nothing is sent. An unlicensed node started with this flag refuses to start (fail-closed): a community node must send telemetry.
If you run community mode and simply stop reaching the endpoint (network
block, air-gap), the node does not silently continue forever — after the
grace window (default 72h) it degrades: client writes are refused with a
503, while reads keep working, until a heartbeat succeeds again. This
“degrade after grace” behavior is the honest community-mode contract; the way to
run offline without degrading is to install a license.
Installing a license (on-prem / offline / air-gapped)
A license is a compact, Ed25519-signed token. Because the node verifies it against a public key embedded in the binary, a license can be validated with zero network access — ideal for air-gapped deployments.
A node looks for a license in this order:
- The
--license <value>flag or thePATINADB_LICENSEenvironment variable. The value may be either a path to a license file or an inline token. - A
license.keyfile in the database root (<db-root>/license.key).
If no valid license is found, the node logs why and falls back to community mode. An invalid signature or an expired license is likewise logged and treated as community mode — the node still boots, just unlicensed.
To operate a node fully offline:
- Obtain a license token (a
license.keyfile) from patinaDB, your vendor, or your account representative — see Editions & Limits for what each tier includes. - Save it as
license.keyin the node’s database root (or point--licenseat it, or setPATINADB_LICENSE). - Start the node. It logs
licensed mode (on-prem)on success. - Optionally add
--disable-telemetryto stop all outbound heartbeats.
Privacy stance
patinaDB’s telemetry is designed to be anonymous by construction: the payload is a fixed allowlist of coarse counters and environment facts, guarded by an automated test that fails the build if any field outside the allowlist is added. No database content, no names, and nothing personally identifiable ever leaves the node. Licensed / air-gapped deployments can disable telemetry entirely.
Telemetry and licensing are node-local — each node phones home independently and they are never routed through the Raft log, so they add no cross-node coordination. For authentication and transport security of the data plane, see Authentication & TLS.