Skip to content

Configuration Guide ​

This guide covers all configuration options for NornicDB, with links to the canonical durability and memory operating guides.

Configuration File ​

NornicDB uses a YAML configuration file (typically nornicdb.yaml) that can be specified via:

bash
./nornicdb serve --config /path/to/nornicdb.yaml

Or via environment variables (see Environment Variables section).

Canonical inventory (all runtime-referenced names): Environment Variables Reference

Config file discovery (when --config is not provided) ​

NornicDB searches for a config file in this order:

  1. NORNICDB_CONFIG (explicit path)
  2. ~/.nornicdb/config.yaml
  3. next to the binary: config.yaml or nornicdb.yaml
  4. current working directory: config.yaml or nornicdb.yaml
  5. container mount path: /config/nornicdb.yaml or /config/config.yaml
  6. OS user config dirs:
    • macOS: ~/Library/Application Support/NornicDB/config.yaml
    • Linux: ~/.config/nornicdb/config.yaml

To avoid ambiguity in Docker/Kubernetes, prefer:

bash
export NORNICDB_CONFIG=/config/nornicdb.yaml

Core Configuration ​

Localization ​

NornicDB uses the operating system's ordered language preferences by default:

yaml
localization:
  language: auto

Set NORNICDB_LANGUAGE to override YAML and OS detection for the process default. Use canonical BCP 47 tags such as en-US, es-ES, pt-BR, or zh-Hant. Common POSIX forms such as en_US.UTF-8 are accepted and normalized.

Precedence is command-line override (when available), NORNICDB_LANGUAGE, YAML, OS preferences, then the embedded en-US source catalog. Request protocols may select a supported language for an individual response through Accept-Language or equivalent metadata. Missing language packs and individual keys fall back to en-US and emit bounded structured warnings.

Retention Policies Opt-In ​

Runtime retention enforcement is disabled by default. Enable it explicitly with compliance.retention_enabled: true or NORNICDB_RETENTION_ENABLED=true.

When retention is disabled:

  • no retention manager is created
  • no retention sweep background worker starts
  • no retention policy file is persisted on shutdown
  • retention admin endpoints return 503 Service Unavailable

Minimal opt-in example:

yaml
compliance:
  retention_enabled: true
  retention_policy_days: 30
  retention_auto_delete: false

retention:
  sweep_interval: 3600
  default_policies: false
  excluded_labels: ["AuditLog", "System"]

The retention: block is inert until retention is enabled.

retention.sweep_interval must be specified as an integer number of seconds. Shorthand duration strings are not supported.

Database Settings ​

yaml
# Database storage and basic settings
database:
  path: /data/nornicdb.db
  default_database: "nornic" # Default database name (like Neo4j's "neo4j")
  storage_serializer: msgpack # default: msgpack; MVCC metadata uses msgpack on the hot path
  mvcc_retention_max_versions: 1
  mvcc_retention_ttl: 168h

Multi-Database Support:

  • Default database name: "nornic" (configurable)
  • System database: "system" (for metadata, not user-accessible)
  • Multiple databases can be created via CREATE DATABASE command
  • Each database is completely isolated (multi-tenancy)
  • Database Aliases: Create alternate names for databases (CREATE ALIAS, DROP ALIAS, SHOW ALIASES)
  • Resource Limits: Set per-database resource limits (ALTER DATABASE SET LIMIT, SHOW LIMITS)
  • Automatic migration: Existing data is automatically migrated to the default database on first startup after upgrading
  • Configuration precedence: CLI args > Env vars > Config file > Defaults

Environment Variables:

  • NORNICDB_DEFAULT_DATABASE - Set default database name
  • NEO4J_dbms_default__database - Neo4j-compatible env var (backwards compat)
  • NORNICDB_STORAGE_SERIALIZER - Storage serializer (gob or msgpack)
  • NORNICDB_MVCC_RETENTION_MAX_VERSIONS - Default historical version cap per key
  • NORNICDB_MVCC_RETENTION_TTL - Protect recent MVCC history from pruning

MVCC Historical Retention ​

NornicDB keeps MVCC history for snapshot and temporal reads. The retention policy controls the default pruning behavior for that history.

yaml
database:
  storage_serializer: msgpack
  mvcc_retention_max_versions: 1
  mvcc_retention_ttl: "168h"

Semantics:

  • mvcc_retention_max_versions applies to closed historical versions
  • the current head is preserved separately and is never pruned
  • mvcc_retention_ttl protects versions newer than now - ttl
  • these settings define defaults for maintenance calls; they do not start background pruning on their own

Recommended starting points:

  • default deployment: 100 versions, no TTL
  • moderate churn: 50 versions, 24h TTL
  • audit-focused: 100 versions, 168h TTL

For query examples and maintenance usage, see Historical Reads & MVCC Retention.

Provider-backed at-rest encryption ​

NornicDB supports provider-backed storage encryption for Badger using a wrapped data-encryption key persisted in the data directory.

Supported provider modes:

  • password
  • local
  • aws-kms
  • azure-keyvault
  • gcp-cloudkms

Example:

yaml
database:
  encryption_enabled: true
  encryption_provider: "aws-kms"
  encryption_aws_region: "us-east-1"
  encryption_aws_kms_key_id: "arn:aws:kms:us-east-1:123456789012:key/..."
  encryption_audit_sign_events: true
  encryption_audit_sign_key: "replace-with-hmac-signing-key"
  encryption_rotation_enabled: true
  encryption_rotation_interval: "2160h"

See:

Per-database configuration overrides ​

Instance-level configuration (env, config file) is the default for every database. You can override specific settings per database so that embedding, search, HNSW, k-means, and related options can differ by database.

  • Precedence: For a given database, values resolve from built-in defaults, global YAML/environment configuration, explicit process/CLI overrides, then persisted per-database settings. Later sources win; any key not set per database inherits the process/global value.
  • Storage: Overrides are stored in the system database (same pattern as RBAC allowlist/privileges). They are loaded at startup and on every PUT so all nodes see the same view.
  • Management:
    • Admin API: GET /admin/databases/{dbName}/config returns canonical dotted names in overrides and effective; PUT /admin/databases/{dbName}/config with body { "overrides": { "db.nornic.embedding.model": "bge-m3", ... } } saves overrides. GET /admin/databases/config/keys returns each canonical key, its supported environmentVariable alternative, and type/category metadata. All require admin authentication.
    • UI: On the Databases page, users with the admin role see a settings (cog) button on each database card. Clicking it opens a configuration modal where you can set or clear overrides per key; "Use default" means that key is not overridden.
  • Allowed keys: GET /admin/databases/config/keys is the authoritative complete inventory. Use its canonical dotted names for persisted settings. Existing NORNICDB_* names remain supported alternatives for environment configuration and alternate YAML/API input; they are not deprecated. Writes are normalized to canonical names, and the canonical form wins a collision.
  • Effect: Settings returned with restartLevel: none are applied immediately by an explicit runtime applicator. Search/index/embedder/reranker settings rebuild the database's search service; search-result cache capacity and TTL resize the existing cache in place. Settings returned with restartLevel: process are persisted immediately and return pendingRestart: true, but the current runtime value remains active until restart.
  • Search pipeline and query embedding: The search pipeline must embed the query using the same effective config (and thus dimensions) as the index for that database to avoid vector dimension mismatches. The HTTP search handler uses per-database resolved config when embedding the query: it validates that the global embedder's output dimensions match the database's resolved embedding dimensions. If they differ (e.g. you set a per-DB override for embedding dimensions that does not match the global embedder), the API returns 400 Bad Request with a clear message instead of returning empty vector results. Align global embedding dimensions with per-DB overrides, or leave per-DB embedding dimensions unset so they match global.
  • Remote embedding providers (OpenAI, OrcaRouter, Ollama) per database: You can set db.nornic.embedding.provider, db.nornic.embedding.model, db.nornic.embedding.api.url, db.nornic.embedding.api.key, and db.nornic.embedding.dimensions so different databases use different models, endpoints, or API keys. When a database uses provider openai or orca, the resolved API key for that database (global default or per-DB setting) is used. Ollama typically does not require an API key.

Canonical settings with environment alternatives ​

Canonical names are the database settings contract and are persisted in _DbConfig. The table below lists settings that also have an environment alternative. Other implemented settings remain available under their canonical names from GET /admin/databases/config/keys.

The restart-bound index controls db.nornic.memory.index.{bm25,vector,metadata}.max use byte values and 0 means unlimited. Limits are independent and reject index mutations or startup builds before they exceed the configured accounted resident-byte ceiling. Metadata is checked separately for the BM25 and vector implementations. BM25 currently supports db.nornic.index.bm25.storage=memory. Vector storage supports auto, memory, and disk; explicit disk requires search-index persistence and a configured index path, while auto preserves the existing file-backed build behavior.

Canonical database settingSupported environment alternative
db.nornic.embedding.enabledNORNICDB_EMBEDDING_ENABLED
db.nornic.embedding.providerNORNICDB_EMBEDDING_PROVIDER
db.nornic.embedding.modelNORNICDB_EMBEDDING_MODEL
db.nornic.embedding.api.urlNORNICDB_EMBEDDING_API_URL
db.nornic.embedding.api.keyNORNICDB_EMBEDDING_API_KEY
db.nornic.embedding.dimensionsNORNICDB_EMBEDDING_DIMENSIONS
db.nornic.embedding.cache.sizeNORNICDB_EMBEDDING_CACHE_SIZE
db.nornic.embedding.properties.includeNORNICDB_EMBEDDING_PROPERTIES_INCLUDE
db.nornic.embedding.properties.excludeNORNICDB_EMBEDDING_PROPERTIES_EXCLUDE
db.nornic.embedding.include.labelsNORNICDB_EMBEDDING_INCLUDE_LABELS
db.nornic.embedding.labels.includeNORNICDB_EMBEDDING_LABELS_INCLUDE
db.nornic.embedding.labels.excludeNORNICDB_EMBEDDING_LABELS_EXCLUDE
db.nornic.embedding.gpu.layersNORNICDB_EMBEDDING_GPU_LAYERS
db.nornic.embedding.warmup.intervalNORNICDB_EMBEDDING_WARMUP_INTERVAL
db.nornic.search.min.similarityNORNICDB_SEARCH_MIN_SIMILARITY
db.nornic.search.bm25.enabledNORNICDB_SEARCH_BM25_ENABLED
db.nornic.search.bm25.warmingNORNICDB_SEARCH_BM25_WARMING
db.nornic.search.bm25.stemmerNORNICDB_SEARCH_BM25_STEMMER
db.nornic.search.vector.enabledNORNICDB_SEARCH_VECTOR_ENABLED
db.nornic.search.vector.warmingNORNICDB_SEARCH_VECTOR_WARMING
db.nornic.search.rerank.enabledNORNICDB_SEARCH_RERANK_ENABLED
db.nornic.search.rerank.providerNORNICDB_SEARCH_RERANK_PROVIDER
db.nornic.search.rerank.modelNORNICDB_SEARCH_RERANK_MODEL
db.nornic.search.rerank.api.urlNORNICDB_SEARCH_RERANK_API_URL
db.nornic.search.rerank.api.keyNORNICDB_SEARCH_RERANK_API_KEY
db.nornic.search.index.persist.delay.secNORNICDB_SEARCH_INDEX_PERSIST_DELAY_SEC
db.nornic.vector.ann.qualityNORNICDB_VECTOR_ANN_QUALITY
db.nornic.vector.hnsw.mNORNICDB_VECTOR_HNSW_M
db.nornic.vector.hnsw.ef.constructionNORNICDB_VECTOR_HNSW_EF_CONSTRUCTION
db.nornic.vector.hnsw.ef.searchNORNICDB_VECTOR_HNSW_EF_SEARCH
db.nornic.vector.hnsw.beam.factorNORNICDB_VECTOR_HNSW_BEAM_FACTOR
db.nornic.vector.hnsw.metal.min.candidatesNORNICDB_VECTOR_HNSW_METAL_MIN_CANDIDATES
db.nornic.vector.ivf.hnsw.enabledNORNICDB_VECTOR_IVF_HNSW_ENABLED
db.nornic.vector.ivf.hnsw.min.cluster.sizeNORNICDB_VECTOR_IVF_HNSW_MIN_CLUSTER_SIZE
db.nornic.vector.ivf.hnsw.max.clustersNORNICDB_VECTOR_IVF_HNSW_MAX_CLUSTERS
db.nornic.vector.cpu.brute.max.nNORNICDB_VECTOR_CPU_BRUTE_MAX_N
db.nornic.vector.gpu.brute.min.nNORNICDB_VECTOR_GPU_BRUTE_MIN_N
db.nornic.vector.gpu.brute.max.nNORNICDB_VECTOR_GPU_BRUTE_MAX_N
db.nornic.kmeans.clustering.enabledNORNICDB_KMEANS_CLUSTERING_ENABLED
db.nornic.kmeans.min.embeddingsNORNICDB_KMEANS_MIN_EMBEDDINGS
db.nornic.kmeans.cluster.intervalNORNICDB_KMEANS_CLUSTER_INTERVAL
db.nornic.kmeans.num.clustersNORNICDB_KMEANS_NUM_CLUSTERS
db.nornic.kmeans.max.iterationsNORNICDB_KMEANS_MAX_ITERATIONS
db.nornic.auto.links.enabledNORNICDB_AUTO_LINKS_ENABLED
db.nornic.auto.links.thresholdNORNICDB_AUTO_LINKS_THRESHOLD
db.nornic.auto.tlp.enabledNORNICDB_AUTO_TLP_ENABLED
db.nornic.auto.tlp.llm.qc.enabledNORNICDB_AUTO_TLP_LLM_QC_ENABLED
db.nornic.auto.tlp.llm.augment.enabledNORNICDB_AUTO_TLP_LLM_AUGMENT_ENABLED
db.nornic.embed.worker.num.workersNORNICDB_EMBED_WORKER_NUM_WORKERS
db.nornic.embed.scan.intervalNORNICDB_EMBED_SCAN_INTERVAL
db.nornic.embed.batch.delayNORNICDB_EMBED_BATCH_DELAY
db.nornic.embed.trigger.debounceNORNICDB_EMBED_TRIGGER_DEBOUNCE
db.nornic.embed.max.retriesNORNICDB_EMBED_MAX_RETRIES
db.nornic.embed.chunk.sizeNORNICDB_EMBED_CHUNK_SIZE
db.nornic.embed.chunk.overlapNORNICDB_EMBED_CHUNK_OVERLAP
db.nornic.mvcc.lifecycle.intervalNORNICDB_MVCC_LIFECYCLE_INTERVAL
db.nornic.query_cache.max_entriesNORNICDB_QUERY_CACHE_SIZE
db.nornic.query_cache.ttlNORNICDB_QUERY_CACHE_TTL

db.nornic.query_cache.ttl is an integer number of milliseconds. For example, use 300000 for five minutes. Duration strings such as 5m are rejected.

Durable search continuation is process-local and disabled by default. Set memory.search_cursor_max in YAML or NORNICDB_SEARCH_CURSOR_MAX to a positive cursor count to enable it. Set memory.search_cursor_ttl or NORNICDB_SEARCH_CURSOR_TTL to an integer number of milliseconds; the default is 300000.

memory.search_cursor_max_page_size / NORNICDB_SEARCH_CURSOR_MAX_PAGE_SIZE sets the maximum n for each continuation START or PULL. It defaults to 500; positive integers override it, while zero or an omitted value keeps the default. Environment variables override YAML. This is independent of the initial retrieval depth (limit) and total emitted result ceiling (max_results). Page responses advertise max_page_size (native gRPC uses response header metadata). Oversized pages return HTTP 400 page_size_exceeded with max_page_size; see search continuation for equivalent gRPC and Bolt errors.

Activation summary ​

The key metadata endpoint reports the activation contract for every setting:

  • Search rebuild: embedding provider/model/API URL/API key/dimensions/GPU layers; search minimum similarity, BM25 enabled/warming, vector enabled/warming, and reranker settings. These persist and immediately rebuild only the affected database's search service.
  • In-place cache update: db.nornic.search_result_cache.max_entries and db.nornic.search_result_cache.ttl. These mutate the existing search result cache without rebuilding the service.
  • Process restart: every other registered setting, including embedding enablement/cache/property selection, ANN/HNSW/IVF/k-means tuning, workers, auto-link/TLP, MVCC lifecycle, query/plan/analysis caches, transaction memory, Badger mode/caches and recovery budgets. A PUT persists the configured value and returns pendingRestart: true; it does not change the running value.

Do not infer activation from a setting family. Read restartLevel (and isDynamic in SHOW SETTINGS) because only settings with a concrete runtime applicator are dynamic.

db.nornic.query_cache.max_entries correlates with Neo4j's server.memory.query_cache.per_db_cache_num_entries: both control the number of cached queries allocated per database and default to 1000. They intentionally do not share a setting name. Neo4j declares its value as server.* DBMS configuration that is applied uniformly to each database. NornicDB persists this value in _DbConfig, so each database can choose an independent limit; that per-database mutation contract is a NornicDB extension and therefore uses db.nornic.*.

Settings metadata reports restartLevel: none for changes applied at runtime and restartLevel: process for changes persisted for the next NornicDB process start. NornicDB does not currently expose a database-only restart operation, so it does not advertise a database restart level. isDynamic in SHOW SETTINGS is true exactly when restartLevel is none. The registry requires every dynamic setting to name a concrete hot-reload applicator; settings without one are process-activated and cannot silently trigger an unrelated search rebuild.

Per-database search index control ​

Two orthogonal axes per index, four keys total.

Precedence ladder ​

Configuration values resolve through a fixed ladder, lowest to highest:

  1. Built-in defaults — bm25=true, vector=true, both warming=startup.
  2. Global config — cfg.Memory.Search* populated by YAML and env vars (NORNICDB_SEARCH_*).
  3. Explicit process overrides — supported command-line flags explicitly supplied to nornicdb serve.
  4. Persisted per-database settings — admin API values and values seeded from the YAML databases: block.

Per-database settings win in both directions. An override of true turns on a globally disabled index; an override of false turns off a globally enabled one. Clear the database setting to inherit the process/global value again.

Canonical keyEnvironment alternativeTypeDefaultMeaning
db.nornic.search.bm25.enabledNORNICDB_SEARCH_BM25_ENABLEDbooleantrueMaster switch for BM25 fulltext search.
db.nornic.search.bm25.warmingNORNICDB_SEARCH_BM25_WARMINGenumstartupBuild at startup or lazily on first search.
db.nornic.search.bm25.stemmerNORNICDB_SEARCH_BM25_STEMMERstringnoneBM25 stemmer plugin ID; none keeps exact tokens.
db.nornic.search.vector.enabledNORNICDB_SEARCH_VECTOR_ENABLEDbooleantrueMaster switch for vector search across every ANN strategy.
db.nornic.search.vector.warmingNORNICDB_SEARCH_VECTOR_WARMINGenumstartupBuild at startup or lazily on first search.

BM25 uses language-neutral NFKC normalization, Unicode case folding, and exact tokens by default. NornicDB ships no language stemmers. To use stemming, configure a process-level plugin directory with plugins.stemmers.directory or NORNICDB_STEMMER_PLUGINS_DIR, then select a registered plugin ID through search.bm25_stemmer, NORNICDB_SEARCH_BM25_STEMMER, or the per-database db.nornic.search.bm25.stemmer setting. See BM25 Stemmer Plugins. NORNICDB_BM25_PREFIX_MAX_EXPANSIONS optionally enables bounded prefix matching for every BM25 query term; its default is 0 (disabled). Enable it only when partial-token matching is required. NORNICDB_BM25_PREFIX_MIN_LEN (default 3) sets the minimum Unicode character count for terms eligible for expansion.

Behavior summary (all combinations supported):

BM25VectorFirst search request
on / startupon / startupHybrid (today's default).
on / startupon / lazySynchronous wait while vector warms; first response includes vector results.
on / lazyon / lazySynchronous wait while both warm; first response is fully ranked.
on / startupoff / —Lexical-only 200.
off / —on / startupVector-only 200 (HNSW falls back to random insertion order).
off / —off / —503 search_disabled_for_database, retryable: false — permanent.

Configure via (in order of effective precedence; later sources override earlier ones):

  • YAML global (memory.search_* block in nornicdb.yaml): sets the global default.
  • Env vars (NORNICDB_SEARCH_* at boot): override YAML on the global default.
  • CLI (--search-bm25-enabled, --search-bm25-warming, --search-vector-enabled, --search-vector-warming): explicit process overrides above global YAML/env values.
  • YAML per-DB (databases: map keyed by database name): seeds canonical persisted settings on first boot only.
  • Admin API (PUT /admin/databases/{name}/config): updates authoritative per-database settings at runtime.

Health checks must not target /nornicdb/search for warming=lazy or any *_enabled=false database — use /nornicdb/health (DB-agnostic) or /admin/databases/{name}/config (lookup-only) instead. Probing search on a lazy DB triggers the build on every probe; probing a disabled DB streams 503s that look like real failures in monitoring.

yaml databases: map ​

yaml
databases:
  hot_app_db: {} # both indexes default (enabled, startup)

  analytics:
    db.nornic.search.bm25.enabled: "false"
    NORNICDB_SEARCH_VECTOR_WARMING: "lazy"

  audit_logs:
    db.nornic.search.bm25.enabled: "false"
    db.nornic.search.vector.enabled: "false"

  exports_only:
    db.nornic.search.bm25.enabled: "true"
    db.nornic.search.vector.enabled: "false" # write embeddings; never load in-process

Each entry may use either the canonical dotted setting name or its documented NORNICDB_* environment-variable alternative; both forms are normalized to the canonical name before validation and persistence. Forms may be mixed across the map. If both forms identify the same setting in one database entry, the canonical dotted name wins.

The yaml databases: map is read into dbconfig.Store only on first boot for each (dbName, key) pair. Once an admin has PUT a value via /admin/databases/{name}/config, that value is authoritative across restarts and yaml changes for the same key are ignored. Operators who want yaml to win again can either delete the _DbConfig node from the system database or PUT the desired value back via the admin API.

Server Settings ​

yaml
server:
  bolt_enabled: true
  bolt_port: 7687
  bolt_server_announcement: "" # Optional compatibility override for the Bolt HELLO server string

  http_enabled: true
  http_port: 7474
  http_address: "127.0.0.1"
  http_trusted_proxies: [] # Exact proxy IPs or CIDRs
  https:
    enabled: false
    port: 7473
    cert_file: ""
    key_file: ""

For authenticated TLS termination, required forwarding headers, CORS, listener isolation, and native HTTPS examples, see Reverse Proxy and TLS Termination.

Bolt announcement override for strict Neo4j clients ​

Some Bolt clients, especially cypher-shell, check the Bolt HELLO success metadata and reject connections unless the announced server looks like Neo4j. NornicDB can override that announcement when you explicitly opt in.

Use this only when a client blocks on the server identity string.

Environment variable:

bash
export NORNICDB_BOLT_SERVER_ANNOUNCEMENT="Neo4j/5.26.0"

YAML:

yaml
server:
  bolt_server_announcement: "Neo4j/5.26.0"

Typical cypher-shell workflow:

bash
export NORNICDB_BOLT_SERVER_ANNOUNCEMENT="Neo4j/5.26.0"
./nornicdb serve
cypher-shell -a bolt://localhost:7687 -u neo4j -p password

Notes:

  • This changes only the Bolt server metadata returned during HELLO.
  • It does not claim full Neo4j product identity or feature parity.
  • Leave it unset unless a strict client requires it.

Bolt over WebSocket + TLS ​

NornicDB multiplexes four wire-level transports on the Bolt port (:7687 by default). The first 5 bytes of every accepted connection decide which path it takes:

First bytes on the wireWire-level transportMetric label
Bolt magic preamble 60 60 B0 17raw TCPtcp
TLS handshake (0x16), then Bolt magicTLS + rawtcp_tls
GET (HTTP/1.1 upgrade)WebSocketws
TLS handshake, then GET TLS + WebSocketws_tls

How clients reach each wire transport:

ClientURL the client usesWire bytesResulting metric label
Official Neo4j driver (Node, JVM, Python, .NET)bolt://host:7687Bolt magictcp
Official Neo4j driver (browser build of neo4j-driver)bolt://host:7687GET upgradews
Same drivers with TLSbolt+s://host:7687 (or bolt+ssc://)TLS firsttcp_tls / ws_tls
Routing wrappersneo4j://, neo4j+s://, neo4j+ssc://Same as aboveSame as above
Custom tool speaking raw WebSocketsws://host:7687/, wss://host:7687/GET upgradews / ws_tls

The official drivers' URL parsers reject ws:// / wss:// — the WebSocket transport is selected automatically by the browser build and produces a GET upgrade on the wire from a bolt:// URL. The ws:// / wss:// rows in the second table are for third-party clients that do their own WebSocket dial and write Bolt frames into BinaryMessage payloads.

YAML ​

yaml
server:
  bolt_max_connections: 0 # concurrent Bolt connections, every transport; 0 = no cap
  bolt_tls_enabled: true
  bolt_tls_cert: /etc/nornicdb/tls/cert.pem
  bolt_tls_key: /etc/nornicdb/tls/key.pem
  bolt_tls_require: false # true = reject any plaintext connection
  bolt_tls_client_ca_file: "" # mTLS: path to client-CA bundle
  bolt_tls_client_auth_mode: none # none | request | request_verify | require_verify
  bolt_sniff_timeout: 5s
  bolt_auth_timeout: 30s
  bolt_statement_timeout: 2m # optional fallback when RUN metadata has no tx_timeout; 0/omitted disables
  bolt_websocket_enabled: true # false ⇒ 426 Upgrade Required on WS attempts
  bolt_websocket_allowed_origins: "*" # or "https://app.example.com,https://admin.example.com"
  bolt_websocket_max_message_size: 65536
  bolt_websocket_write_buffer_size: 262144
  bolt_websocket_ping_interval: 30s
  bolt_websocket_pong_timeout: 60s

Environment variables ​

KeyDefaultNotes
NORNICDB_BOLT_MAX_CONNECTIONS0concurrent connections, every transport; 0 = no cap
NORNICDB_BOLT_TLS_ENABLEDfalseenables TLS-on-first-byte sniffing
NORNICDB_BOLT_TLS_CERTpath to cert PEM
NORNICDB_BOLT_TLS_KEYpath to key PEM
NORNICDB_BOLT_TLS_REQUIREfalsereject plaintext (raw OR ws)
NORNICDB_BOLT_TLS_CLIENT_CApath to CA bundle for mTLS
NORNICDB_BOLT_TLS_CLIENT_AUTH_MODEnonenone / request / request_verify / require_verify
NORNICDB_BOLT_SNIFF_TIMEOUT5sbound on transport-sniff peek
NORNICDB_BOLT_AUTH_TIMEOUT30sbound on pre-HELLO handshake/auth
NORNICDB_BOLT_STATEMENT_TIMEOUTdisabledfallback cap for a Bolt RUN without client tx_timeout
NORNICDB_BOLT_WEBSOCKET_ENABLEDtrueset false for purely-TCP deployments
NORNICDB_BOLT_WEBSOCKET_ALLOWED_ORIGINS*comma-separated; * = any
NORNICDB_BOLT_WEBSOCKET_MAX_MESSAGE_SIZE65536bytes; matches Neo4j MAX_WEBSOCKET_FRAME_SIZE
NORNICDB_BOLT_WEBSOCKET_WRITE_BUFFER_SIZE262144bufio writer size for WS sessions
NORNICDB_BOLT_WEBSOCKET_PING_INTERVAL30sserver WS ping cadence
NORNICDB_BOLT_WEBSOCKET_PONG_TIMEOUT60spong arrival deadline

Discovery probe ​

A plain GET / to the Bolt port (no Upgrade headers) returns a 200 OK discovery response. Health checks and curl probes work without configuration.

When NORNICDB_AUTH_PROVIDER=oauth is set with NORNICDB_OAUTH_* filled in, the discovery body advertises the OAuth provider in the JSON shape Neo4j browser drivers consume. The client_secret is never exposed.

Cert rotation ​

bolt_tls_cert and bolt_tls_key are re-read on a 5-second background ticker; a successful reload swaps the cached certificate atomically. New connections immediately see the new cert; in-flight TLS sessions continue on the old cert until they close.

Operator update protocol: atomic rename. Write the new cert to cert.pem.new, then mv cert.pem.new cert.pem. Reading mid-write returns half a PEM; the rotator preserves the previous cert until the next successful reload.

RequireTLS=true ​

When set, plaintext bolt:// and ws:// upgrade attempts are rejected with the canonical Neo4j error An unencrypted connection attempt was made where encryption is required. and the bolt_connections_rejected_total{reason="requires_tls"} counter increments.

WebSocketEnabled=false ​

When WS is disabled:

  • Plain GET / (no Upgrade headers) → still returns the 200 discovery response (health checks unaffected).
  • A real WS upgrade attempt → returns 426 Upgrade Required with body pointing at bolt://<host>:<port>/ and increments bolt_connections_rejected_total{reason="ws_disabled"}.

Async Write Settings ​

The async write engine provides write-behind caching for improved throughput. Async-eligible auto-commit writes return immediately after updating the cache and are flushed to disk asynchronously. Mutations that still need the implicit transactional path continue to execute synchronously and produce durable receipts.

Current behavior:

  • Pure auto-commit CREATE statements are the primary eventual-consistency path
  • Schema commands, system commands, and read-modify-write mutations such as MATCH ... CREATE, CREATE ... SET, MERGE, DELETE, and SET remain on the durable transactional path
  • Eventual HTTP responses return 202 Accepted, X-NornicDB-Consistency: eventual, and optimistic metadata instead of a durable receipt

Configuration ​

yaml
# === Async Write Settings ===
# These control the async write-behind cache for better throughput
database:
  async_writes_enabled: false # Durable writes are the default; opt in
  async_flush_interval: auto  # Initial rotation delay; runtime adapts 5ms-1s
  async_max_ops: 0            # Generation-size cap (0 = researched 100k default)
  # Deprecated legacy caps (fall back to their sum when async_max_ops is unset):
  # async_max_node_cache_size: 0
  # async_max_edge_cache_size: 0

Environment Variables ​

VariableDefaultDescription
NORNICDB_ASYNC_WRITES_ENABLEDfalseEnable/disable async writes (durable by default)
NORNICDB_ASYNC_FLUSH_INTERVALautoInitial rotation delay; adapts to 5ms-1s
NORNICDB_ASYNC_MAX_OPS100000Max buffered ops before a forced rotation (0=auto)
NORNICDB_ASYNC_MAX_NODE_CACHE_SIZE0Deprecated legacy cap on buffered nodes
NORNICDB_ASYNC_MAX_EDGE_CACHE_SIZE0Deprecated legacy cap on buffered edges

Performance Tuning ​

For High Throughput (bulk operations):

yaml
database:
  async_writes_enabled: true
  async_flush_interval: 200ms # Initial delay; runtime adapts
  async_max_ops: 200000        # Raise for bulk inserts

For Lower Staleness Window (still eventual consistency):

yaml
database:
  async_writes_enabled: true
  async_flush_interval: 10ms # Initial delay; not a consistency guarantee
  async_max_ops: 5000        # Smaller = less memory risk

For Maximum Durability:

yaml
database:
  strict_durability: true
  async_writes_enabled: false

See durability boundaries for sync behavior and failure limits.

With async_writes_enabled: true, do not assume every mutation becomes eventual. The setting enables the async write-behind engine and lets eligible queries use it; durable transactional mutations still return 200 OK and include receipt metadata.

Memory Management ​

Use memory accounting and limits for runtime GC targets, physical admission, transaction pools, native/model memory, and diagnostics. Use low-memory storage for Badger capacities and cache counts; transaction memory limits defines detailed accounting and client errors.

async_max_ops: 0 selects the automatic generation cap (100,000 operations), not unlimited bytes. Explicit legacy node/edge caps fall back to their sum only when the primary cap is unset. Payload size and retained/draining generations still determine actual RAM use. The adaptive buffer explains scheduling; it does not establish a hard memory or loss-window limit.

Vector Search Configuration ​

Embedding Settings ​

Global embedding settings resolve in this order: explicit CLI flags > environment variables > YAML > built-in defaults. Omitted flags preserve the loaded values, including YAML API credentials. An explicit flag still wins when its value equals the default, is empty, or is zero (for example, --embedding-cache=0). A config file containing only unrelated or per-database settings does not replace explicit global embedding flags.

yaml
embedding:
  provider: local # or ollama, openai, voyage
  model: bge-m3
  dimensions: 1024
  # Provider-specific free-form mode (for example: text or contextualized)
  mode: text

Note: embedding generation is disabled by default in current releases. Enable it explicitly with NORNICDB_EMBEDDING_ENABLED=true (or nornicdb serve --embedding-enabled) to get semantic search without manually storing vectors.

Embedding text: which properties are used ​

By default, the embedding worker builds text from all node properties plus node labels. Managed embedding metadata is stored internally (in EmbedMeta, not Properties). You can restrict this so that only specific properties are embedded, or exclude certain properties.

Use this when you want to:

  • Embed only one field (e.g. only content) to avoid re-embedding stored embeddings or noisy fields.
  • Exclude internal or large fields (e.g. internal_id, raw_html) from the embedding text.

YAML (under embedding_worker):

yaml
embedding_worker:
  # Optional: only these property keys are used when building embedding text (empty = all).
  properties_include: [content] # e.g. only "content", or [content, title, description]
  # Optional: these property keys are never used (in addition to built-in metadata skips).
  properties_exclude: [internal_id, raw_html]
  # Whether to prepend node labels to the embedding text (default: true).
  include_labels: true

Environment variables:

VariableDefaultDescription
NORNICDB_EMBEDDING_PROPERTIES_INCLUDE(empty)Comma-separated list of property keys to use. If set, only these keys (and optionally labels) are embedded. Example: content or content,title,description.
NORNICDB_EMBEDDING_PROPERTIES_EXCLUDE(empty)Comma-separated list of property keys to exclude from embedding text. Example: internal_id,raw_html.
NORNICDB_EMBEDDING_INCLUDE_LABELStrueSet to false to omit node labels from the embedding text (e.g. when embedding only a single field).

Node eligibility is configured separately with NORNICDB_EMBEDDING_LABELS_INCLUDE and NORNICDB_EMBEDDING_LABELS_EXCLUDE (comma-separated, both empty by default). The YAML keys are embedding_worker.eligible_labels and embedding_worker.excluded_labels. See Which nodes are automatically embedded for precedence, restart, and existing-vector behavior.

Behavior:

  • If properties_include is set, only those keys (minus any in the exclude list) are used.
  • properties_exclude is always applied; it is also applied when include is set (so an excluded key is never embedded even if listed in include).
  • Precedence: defaults → config file → environment variables (env wins).

Search Similarity New ​

Configure minimum similarity thresholds for vector search:

yaml
search:
  min_similarity: 0.5 # Default threshold (0.0-1.0)

Programmatic Configuration ​

You can also configure similarity settings programmatically:

go
// Set default minimum similarity
searchService.SetDefaultMinSimilarity(0.7)

// Get current default
current := searchService.GetDefaultMinSimilarity()

// Per-search override
results, err := searchService.Search(ctx, &SearchOptions{
    Query: "machine learning",
    MinSimilarity: &[]float64{0.8}[0], // Override for this search only
})

Apple Intelligence Compatibility ​

For Apple Intelligence integration, use lower similarity thresholds:

yaml
search:
  min_similarity: 0.3 # Lower threshold for AI assistants

Search index persistence ​

By default, BM25 (full-text) and vector (HNSW) search indexes are built in memory on startup by scanning storage. When persist search indexes is enabled, NornicDB saves these indexes to disk under the data directory and loads them on startup when present, skipping the full scan and speeding up startup for large graphs.

Experimental: Search index persistence is currently experimental. If persisted artifacts are missing/incompatible and a rebuild is required, startup can still be long on large datasets. Observed reference point: rebuilding IVF-HNSW for ~1M embeddings can take ~30 minutes on startup (hardware dependent).

When to use:

  • Large databases where rebuilding indexes on every startup is slow.
  • Restarts or deployments where you want search to be ready immediately after storage recovery.

YAML (under database):

yaml
database:
  persist_search_indexes: true # EXPERIMENTAL. Default: false. Requires data_dir to be set.

Environment variable:

VariableDefaultDescription
NORNICDB_PERSIST_SEARCH_INDEXESfalseEXPERIMENTAL. When true, save and load BM25, vector, and HNSW indexes under DataDir/search/<dbname>/ (e.g. bm25.v2, vectors, hnsw). Has no effect if NORNICDB_DATA_DIR (or config data_dir) is not set.

Behavior:

  • Indexes are written under data_dir/search/<database_name>/ (e.g. bm25.v2, vectors, hnsw).
  • After node index/remove operations, changes are persisted after a short debounce delay (configurable via NORNICDB_SEARCH_INDEX_PERSIST_DELAY_SEC); on graceful shutdown, indexes are flushed to disk. Snapshot files are buffered and atomically replaced, so interruption leaves the previous valid snapshot available.
  • The durable storage clean-shutdown marker is written after graph writers stop and storage flushes, before search-index persistence begins. If a platform terminates the remaining search save, the next startup validates or rebuilds search snapshots without unnecessarily rebuilding storage's temporal and MVCC indexes.
  • On startup, if both index files exist and are compatible with the current format version, they are loaded and the full storage iteration is skipped; otherwise indexes are rebuilt as usual.
  • Storage recovery (WAL) runs first; search indexes are built or loaded after storage is consistent.

Vector search strategy and HNSW tuning ​

Vector search chooses a strategy automatically based on dataset size and features (GPU, clustering). All thresholds and HNSW parameters are configurable via environment variables.

Runtime transition behavior:

  • Strategy checks run on index mutations (IndexNode / RemoveNode).
  • The service switches among CPU brute-force, GPU brute-force, and global HNSW using configured threshold crossings.
  • Brute-force CPU/GPU switches do not rebuild ANN graphs.
  • Brute-force/HNSW transitions run with debounced scheduling and background build/swap so query serving continues.
  • Writes during transition are replayed before cutover to keep the target index current.

Strategy selection (order of precedence):

  1. GPU brute-force – when GPU is enabled and vector count is in [NORNICDB_VECTOR_GPU_BRUTE_MIN_N, NORNICDB_VECTOR_GPU_BRUTE_MAX_N]. Its GPU copy of the vectors is built when it is first chosen.
  2. CPU brute-force – only when NORNICDB_VECTOR_CPU_BRUTE_MAX_N is greater than 0 and vector count is below that value.
  3. Cluster-based (IVF-HNSW or k-means) – when clustering is enabled and built.
  4. Global HNSW – default strategy when neither GPU brute-force, CPU brute-force opt-in, nor clustering is chosen.

By default, NORNICDB_VECTOR_CPU_BRUTE_MAX_N=0, so CPU brute-force is opt-in and HNSW is active regardless of dataset size. To opt into exact CPU brute-force below a threshold, set NORNICDB_VECTOR_CPU_BRUTE_MAX_N to that vector count.

VariableDefaultDescription
Strategy thresholds
NORNICDB_VECTOR_CPU_BRUTE_MAX_N0Max vector count to use CPU brute-force exact search. 0 disables automatic CPU brute-force so HNSW is the default.
NORNICDB_VECTOR_GPU_BRUTE_MIN_N5000Min vector count to use GPU brute-force (exact search).
NORNICDB_VECTOR_GPU_BRUTE_MAX_N15000Max vector count for GPU brute-force; above this, HNSW is preferred by default.
NORNICDB_VECTOR_IVF_HNSW_ENABLEDfalseWhen clustered, use IVF-HNSW (per-cluster HNSW) when available. Disabled by default.
NORNICDB_VECTOR_IVF_HNSW_MIN_CLUSTER_SIZE200Deprecated; ignored. Every nonempty routed cluster gets a graph.
NORNICDB_VECTOR_IVF_HNSW_MAX_CLUSTERS1024Deprecated; ignored. All routed clusters must have graphs.
Hybrid lexical-semantic routing
NORNICDB_VECTOR_ROUTING_MODEhybridCluster routing mode: hybrid (lexical+semantic) or semantic (centroid-only).
NORNICDB_VECTOR_HYBRID_ROUTING_W_SEM0.7Weight for semantic (centroid) routing score.
NORNICDB_VECTOR_HYBRID_ROUTING_W_LEX0.3Weight for lexical (BM25-term profile) routing score.
NORNICDB_VECTOR_HYBRID_ROUTING_LEX_TOP_TERMS64Number of high-value terms kept per cluster lexical profile.
K-means clustering
NORNICDB_KMEANS_NUM_CLUSTERS(auto)Number of clusters. Unset or 0 = auto from dataset size at run time (√(n/2), clamped 10–8192). Set to a positive value to fix K (e.g. 500).
NORNICDB_KMEANS_MAX_ITERATIONS5Max k-means iterations (early stop when stable). Minimum is clamped to 5.
NORNICDB_KMEANS_SEED_MAX_TERMS256Max BM25 high-IDF terms used to build seed candidates when always-on lexical seeding is attempted.
NORNICDB_KMEANS_SEED_DOCS_PER_TERM1Max seed docs selected per term when always-on lexical seeding is attempted.
HNSW index (quality preset)
NORNICDB_VECTOR_ANN_QUALITYfastPreset: fast | balanced | accurate | compressed. See tables below.
NORNICDB_VECTOR_HNSW_M(preset)Max connections per node (e.g. 16 or 32). Overrides preset.
NORNICDB_VECTOR_HNSW_EF_CONSTRUCTION(preset)Candidate list size during index build. Overrides preset.
NORNICDB_VECTOR_HNSW_EF_SEARCH(preset)Candidate list size during search; higher = better recall, slower. Overrides preset.
NORNICDB_VECTOR_HNSW_BEAM_FACTOR4Multiplies requested candidate depth to set the HNSW traversal beam. Higher values improve recall and distinct-node depth for chunked documents at additional query cost.
NORNICDB_VECTOR_PQ_SEGMENTS(auto)Compressed mode only. Defaults to approximately dimensions / 8 (up to 128), choosing a divisor of the embedding dimension; 1024 dimensions use 128 segments. Explicit values must divide the dimension.
NORNICDB_VECTOR_PQ_BITS8Compressed mode only. Bits per PQ code (currently clamped to 4-8).
NORNICDB_VECTOR_IVFPQ_NPROBE(auto)Compressed mode only. IVF lists probed per query; defaults to max(16, one eighth of the lists). An explicit value overrides the adaptive default.
NORNICDB_VECTOR_IVFPQ_RERANK_TOPK200Compressed mode only. Minimum approximate candidates sent to exact rerank; larger requested depths are preserved.
NORNICDB_VECTOR_IVFPQ_TRAINING_SAMPLE_MAX200000Compressed mode only. Maximum vectors sampled for IVFPQ training.
NORNICDB_VECTOR_IVFPQ_OVERFLOW_MAX512Compressed mode only. Maximum exact outlier vectors retained alongside PQ. Set 0 to disable.
HNSW Metal (GPU)
NORNICDB_VECTOR_HNSW_METAL_MIN_CANDIDATES0If greater than 0, use Metal for HNSW search when candidate count meets threshold. 0 = disabled.
HNSW build acceleration
NORNICDB_HNSW_BUILD_GPU_ENABLEDtrueAttempt GPU-assisted HNSW construction on supported hosts. Falls back to CPU if the accelerator is unavailable or fails.
NORNICDB_HNSW_BUILD_GPU_BATCH_SIZE2048Number of vectors processed per construction batch.
NORNICDB_HNSW_BUILD_GPU_CANDIDATE_K128Minimum candidate depth before CPU graph linking; at least M and efConstruction (native GPU kernels cap returned candidates at 256).
NORNICDB_HNSW_BUILD_GPU_DISTANCE_PRECISIONfp32Distance precision for GPU build candidate search.
NORNICDB_HNSW_BUILD_GPU_BEAM_WIDTH64Metal graph-beam width for approximate layer-0 construction candidate search.
NORNICDB_HNSW_BUILD_GPU_BEAM_UNION_MAX4096Maximum graph-expanded candidate union scored per query group.
NORNICDB_HNSW_BUILD_GPU_BEAM_QUERY_GROUP512Number of construction queries grouped into one graph-beam candidate search.
HNSW maintenance
NORNICDB_HNSW_MAINT_INTERVAL_MS30000Interval (ms) for HNSW maintenance (tombstone checks).
NORNICDB_HNSW_MIN_REBUILD_INTERVAL_SEC60Min interval between HNSW rebuilds (seconds).
NORNICDB_HNSW_TOMBSTONE_REBUILD_RATIO0.50Rebuild when tombstone ratio exceeds this.
NORNICDB_HNSW_MAX_TOMBSTONE_OVERHEAD_FACTOR2.0Max tombstone overhead before rebuild.
NORNICDB_HNSW_REBUILD_ENABLEDtrueEnable periodic HNSW rebuilds when tombstone ratio is high.
Index persistence
NORNICDB_SEARCH_INDEX_PERSIST_DELAY_SEC30Debounce delay (seconds) before writing BM25/vector indexes to disk after updates.

HNSW quality presets:

PresetMefConstructionefSearchUse case
fast1610050Faster queries, lower recall.
balanced16200100Good balance.
accurate32400200Higher recall, slower search.

To reduce latency (e.g. if search is ~4s), try NORNICDB_VECTOR_ANN_QUALITY=fast or lower NORNICDB_VECTOR_HNSW_EF_SEARCH (e.g. 50). Keep NORNICDB_VECTOR_CPU_BRUTE_MAX_N=0 unless you explicitly want exact CPU brute-force below a chosen vector count.

Compressed ANN mode (IVFPQ) ​

NORNICDB_VECTOR_ANN_QUALITY=compressed enables the compressed ANN path designed to reduce memory footprint at large scale.

How it works:

  1. Build/load an IVF/PQ index (coarse lists + compressed codes).
  2. Probe a bounded number of lists (nprobe) for approximate candidates.
  3. Score candidates in compressed space.
  4. Re-rank a bounded top window using exact vectors for result quality.

This mode is integrated end-to-end: build, persist, startup load/rebuild, search query path, and compatibility checks.

Safety behavior:

  • If compressed mode prerequisites are not satisfied for a run (for example, invalid dimensions/segments combination or insufficient vectors for current profile), NornicDB logs a clear diagnostic and safely falls back to the standard path instead of failing the service.

Quick enable (environment):

bash
export NORNICDB_VECTOR_ANN_QUALITY=compressed
export NORNICDB_VECTOR_PQ_BITS=8
export NORNICDB_VECTOR_IVFPQ_RERANK_TOPK=200
export NORNICDB_VECTOR_IVFPQ_TRAINING_SAMPLE_MAX=200000

Tuning guidance (compressed mode):

  • Increase NORNICDB_VECTOR_IVFPQ_NPROBE to improve recall (usually increases latency).
  • Increase NORNICDB_VECTOR_IVFPQ_RERANK_TOPK to improve final quality consistency (usually increases latency and exact-score IO).
  • Increase NORNICDB_VECTOR_IVFPQ_OVERFLOW_MAX when a corpus contains rare topics far from the trained coarse centroids. This uses four bytes per vector dimension for each retained outlier.
  • New and updated vectors are searched through an exact mutation overlay until the next full compressed rebuild; deleted vector IDs are tombstoned. Both are persisted with the IVF/PQ bundle, and a mismatched vector-store generation invalidates the bundle instead of silently loading stale candidates.
  • Increase NORNICDB_VECTOR_PQ_SEGMENTS or NORNICDB_VECTOR_PQ_BITS to improve compressed-space fidelity (increases memory/build cost).
  • Keep NORNICDB_VECTOR_PQ_SEGMENTS a divisor of embedding dimensions.

Tradeoff snapshot (latest benchmark matrix) ​

Reference run: BenchmarkANNQueryPipelineChunked, benchtime=2s, count=3, Apple M3 Max.

Latency (ns/op), averaged:

text
Query Latency vs Dataset Size (ns/op)
Scale: 1 block ~= 2,000 ns

N=1500
HNSW   5,810  |███
IVFPQ 22,995  |███████████

N=3000
HNSW   5,748  |███
IVFPQ 42,643  |█████████████████████

N=6000
HNSW   5,828  |███
IVFPQ 38,894  |███████████████████

N=12000
HNSW   5,642  |███
IVFPQ 48,735  |████████████████████████

Memory tradeoff (current implementation):

  1. Per-query working-set memory (heap delta, MiB), averaged from the same runs:
Dataset sizeHNSW heap deltaIVFPQ heap delta
15001.561.57
30001.572.08
60001.572.08
120001.582.08
text
Query Heap Delta vs Dataset Size (MiB)
Scale: 1 block ~= 0.25 MiB

N=1500
HNSW   1.56 MiB |██████
IVFPQ  1.57 MiB |██████

N=3000
HNSW   1.57 MiB |██████
IVFPQ  2.08 MiB |████████

N=6000
HNSW   1.57 MiB |██████
IVFPQ  2.08 MiB |████████

N=12000
HNSW   1.58 MiB |██████
IVFPQ  2.08 MiB |████████

Per-query memory summary:

  • HNSW path: ~1.56-1.59 MiB
  • Compressed IVFPQ path: ~1.57-2.08 MiB
  1. Index-size economics (how many embeddings fit per node):

Reference run: BenchmarkANNQualityMatrixChunked/full_n=12000 (same hardware family).

MetricHNSWCompressed IVFPQ
Build-time index heap (heap_build_mib)3.61 MiB1.08 MiB
Relative embeddings per same index-heap budget1.0x~3.34x

Current compressed profile internals (benchmark profile: dims=32, PQ segments=16):

  • Raw float32 vector payload: 32 * 4 = 128 bytes/vector
  • Compressed code payload (current IVFPQ accounting): 16 + 2 = 18 bytes/vector
  • Payload ratio: ~7.1x smaller compressed representation

Interpretation (current state only):

  • Compressed ANN currently trades higher query latency for better index memory economics.
  • In current measurements, compressed query memory pressure is now close to HNSW at smaller slices and remains bounded at larger slices.
  • In current measurements, compressed mode supports roughly 3.34x more embeddings per equivalent in-memory index budget on this benchmark shape.
  • Use compressed mode when your primary objective is ANN scale economics and predictable bounded candidate behavior; use fast|balanced|accurate when lowest query latency is the primary objective.

Search timing diagnostics ​

Use these flags to identify whether latency comes from embedding, vector retrieval, BM25, or fusion:

VariableDefaultDescription
NORNICDB_SEARCH_LOG_TIMINGSfalseLogs per-search service stage timing (vector_ms, bm25_ms, fusion_ms, candidates, fallback).
NORNICDB_SEARCH_DIAG_TIMINGSfalseLogs HTTP-handler timing breakdown (embed_total, search_total, embed_calls, chunk stats).

Typical diagnostic pattern for fulltext-only/fallback traffic after optimization:

  • embed_calls=0
  • embed_total=0s
  • search_total in microseconds

Observed reference on Apple M3 Max (64GB RAM), varied cache-busting queries:

  • Embedding-query path: sequential p50 ~11.28ms, p95 ~25.84ms; concurrent (8 workers) p50 ~76.36ms, p95 ~87.41ms
  • Fulltext-only path: sequential p50 ~0.57ms, p95 ~2.77ms; handler-internal total commonly ~15-100us (HTTP overhead can be higher)

A/B testing hybrid routing on/off ​

Use the same data directory and run two profiles to compare startup/build time and query latency.

Profile A: hybrid routing ON (default)

bash
export NORNICDB_PERSIST_SEARCH_INDEXES=true
export NORNICDB_VECTOR_ANN_QUALITY=fast
export NORNICDB_KMEANS_MAX_ITERATIONS=5
export NORNICDB_VECTOR_ROUTING_MODE=hybrid
export NORNICDB_VECTOR_HYBRID_ROUTING_W_SEM=0.7
export NORNICDB_VECTOR_HYBRID_ROUTING_W_LEX=0.3
./bin/nornicdb serve

Profile B: hybrid routing OFF (semantic-only routing)

bash
export NORNICDB_PERSIST_SEARCH_INDEXES=true
export NORNICDB_VECTOR_ANN_QUALITY=fast
export NORNICDB_KMEANS_MAX_ITERATIONS=5
export NORNICDB_VECTOR_ROUTING_MODE=semantic
./bin/nornicdb serve

Watch logs for:

  • Vector search strategy: IVF-HNSW or k-means routing mode
  • k-means completion and iterations=...
  • first query latency and p95 latency under representative load

Heimdall AI Assistant ​

Heimdall is the cognitive guardian and AI chat assistant. It supports local (GGUF BYOM), ollama, openai, orca, vllm, and litellm chat providers.

VariableDefaultDescription
NORNICDB_HEIMDALL_ENABLEDfalseEnable the AI assistant
NORNICDB_HEIMDALL_PROVIDERlocalBackend: local, ollama, openai, orca, vllm, or litellm
NORNICDB_HEIMDALL_API_URL(see below)Provider base URL. OrcaRouter defaults to https://api.orcarouter.ai; see provider docs for all defaults.
NORNICDB_HEIMDALL_API_KEY(empty)Required for OpenAI and Orca; optional LiteLLM master/virtual key or vLLM key
NORNICDB_HEIMDALL_MODEL(varies)Local GGUF name, Ollama/OpenAI/vLLM model name, or required LiteLLM model_name alias

Advanced llama.cpp context features (local provider only, most models work with defaults):

VariableDefaultDescription
NORNICDB_HEIMDALL_CTX_TYPE0Context type: 0=default, 1=MTP
NORNICDB_HEIMDALL_POOLING_TYPE-1Pooling: -1=none, 1=mean, 2=cls, 3=last
NORNICDB_HEIMDALL_ATTENTION_TYPE0Attention: 0=causal, 1=non-causal
NORNICDB_HEIMDALL_FLASH_ATTN-1Flash attention: -1=auto, 0=disabled, 1=enabled
NORNICDB_HEIMDALL_LAZY_MODE1Lazy tensor loading: 0=off, 1=auto, 2=on

Streaming (SSE) is supported for chat completions when the client requests it; the OpenAI and Ollama providers stream tokens as they are generated.

See Heimdall AI Assistant for full configuration, provider examples, and YAML. To expose MCP memory tools (store, recall, link, etc.) in the Bifrost agentic loop, set NORNICDB_HEIMDALL_MCP_ENABLE=true and optionally NORNICDB_HEIMDALL_MCP_TOOLS (comma-separated allowlist); see Enabling MCP tools in the agentic loop.

Search Rerank (Stage-2 Reranking) ​

Stage-2 reranking improves vector/hybrid search by re-scoring top candidates with a reranker model. It is independent of Heimdall and supports local (GGUF, like embeddings) or external (ollama/openai/http) providers.

VariableDefaultDescription
NORNICDB_SEARCH_RERANK_ENABLEDfalseEnable Stage-2 reranking for vector/hybrid search
NORNICDB_SEARCH_RERANK_PROVIDERlocalBackend: local (GGUF), ollama, openai, or http
NORNICDB_SEARCH_RERANK_MODEL(see below)For local: GGUF filename (e.g. bge-reranker-v2-m3-Q4_K_M.gguf). For API: model name (e.g. rerank-english-v3.0)
NORNICDB_SEARCH_RERANK_API_URL(see below)Rerank API URL for non-local (default for ollama: http://localhost:11434/rerank)
NORNICDB_SEARCH_RERANK_API_KEY(empty)API key for Cohere, OpenAI, etc.
NORNICDB_SEARCH_RERANK_MAX_DOCUMENT_CHARS2048Maximum characters sent per candidate (identifying properties + matched chunk with its neighbours); …_BYTES is deprecated and read as characters

Advanced llama.cpp context features (local provider only, most models work with defaults):

VariableDefaultDescription
NORNICDB_RERANK_CTX_TYPE0Context type: 0=default, 1=MTP
NORNICDB_RERANK_POOLING_TYPE4Pooling: 1=mean, 2=cls, 3=last, 4=rank
NORNICDB_RERANK_ATTENTION_TYPE1Attention: 0=causal, 1=non-causal
NORNICDB_RERANK_FLASH_ATTN0Flash attention: -1=auto, 0=disabled, 1=enabled
NORNICDB_RERANK_LAZY_MODE1Lazy tensor loading: 0=off, 1=auto, 2=on

Local models live in NORNICDB_MODELS_DIR (default ./models). Download the default reranker with make download-bge-reranker.

Env var invocation: Use export NORNICDB_SEARCH_RERANK_ENABLED=true (and other vars) before running ./nornicdb serve, or put all vars on one logical line with backslashes—otherwise the shell may run each line as a separate command and only the last line’s vars are passed to the process.

YAML:

yaml
search_rerank:
  enabled: true
  provider: local # local | ollama | openai | http
  model: bge-reranker-v2-m3-Q4_K_M.gguf
  api_url: ""
  api_key: ""
  lazy_mode: 1 # 0=off, 1=auto, 2=on (local provider only)

See Cross-Encoder Reranking for full configuration, local GGUF vs external API, and examples.

Memory Decay Configuration ​

yaml
decay:
  enabled: true
  recalculate_interval: 1h
  decay_rate: 0.1 # How quickly memories fade
yaml
auto_links:
  enabled: true
  similarity_threshold: 0.82 # Threshold for automatic relationships

Encryption Configuration ​

yaml
encryption:
  enabled: false
  password: "your-secure-password" # Use environment variable in production

Environment Variables ​

All configuration options can be set via environment variables using the pattern NORNICDB_<SECTION>_<KEY>:

bash
# Server configuration
export NORNICDB_SERVER_BOLT_PORT=7687
export NORNICDB_SERVER_HTTP_PORT=7474

# Async writes
export NORNICDB_ASYNC_WRITES_ENABLED=true
export NORNICDB_ASYNC_FLUSH_INTERVAL=50ms

# Search
export NORNICDB_SEARCH_MIN_SIMILARITY=0.5
export NORNICDB_PERSIST_SEARCH_INDEXES=true   # Save/load BM25 and vector indexes (requires data_dir)

# Embeddings
export NORNICDB_EMBEDDING_ENABLED=true
export NORNICDB_EMBEDDING_PROVIDER=local           # local | ollama | openai
export NORNICDB_EMBEDDING_MODEL=bge-m3
export NORNICDB_EMBEDDING_DIMENSIONS=1024
export NORNICDB_EMBEDDING_API_URL=http://localhost:11434
export NORNICDB_MODELS_DIR=./models                # used by provider=local

# Embedding llama.cpp context features (advanced, local provider only)
# Most models work with defaults; override only when a model requires it.
# export NORNICDB_EMBEDDING_CTX_TYPE=0             # 0=default, 1=MTP
# export NORNICDB_EMBEDDING_POOLING_TYPE=1         # 1=mean, 2=cls, 3=last, 4=rank
# export NORNICDB_EMBEDDING_ATTENTION_TYPE=1       # 0=causal, 1=non-causal (BERT-style)
# export NORNICDB_EMBEDDING_FLASH_ATTN=-1          # -1=auto, 0=disabled, 1=enabled
# export NORNICDB_EMBEDDING_LAZY_MODE=1             # 0=off, 1=auto, 2=on

Qdrant gRPC Endpoint (Qdrant SDK Compatibility) ​

NornicDB can expose a Qdrant-compatible gRPC endpoint so existing Qdrant SDKs can connect without modification.

User guide: docs/user-guides/qdrant-grpc.md

Configuration (YAML) ​

yaml
features:
  qdrant_grpc_enabled: true
  qdrant_grpc_listen_addr: ":6334"
  qdrant_grpc_max_vector_dim: 4096
  qdrant_grpc_max_batch_points: 1000
  qdrant_grpc_max_top_k: 1000
  qdrant_grpc_tls_enabled: true
  qdrant_grpc_tls_cert: "/tls/grpc.crt"
  qdrant_grpc_tls_key: "/tls/grpc.key"
  # Optional mTLS:
  qdrant_grpc_tls_client_ca: "/tls/client-ca.crt"
  qdrant_grpc_tls_client_auth_mode: "require_verify"

  # Optional: override required permissions per RPC (advanced)
  qdrant_grpc_rbac:
    methods:
      # Key format: "<Service>/<Method>" (short service name)
      # Values: read, write, create, delete, admin, schema, user_manage
      "Points/Upsert": "write"
      "Points/Search": "read"

Environment variables ​

VariableDefaultDescription
NORNICDB_QDRANT_GRPC_ENABLEDfalseEnable the Qdrant-compatible gRPC server
NORNICDB_QDRANT_GRPC_LISTEN_ADDR:6334gRPC listen address
NORNICDB_QDRANT_GRPC_MAX_VECTOR_DIM4096Maximum vector dimension
NORNICDB_QDRANT_GRPC_MAX_BATCH_POINTS1000Max points per upsert
NORNICDB_QDRANT_GRPC_MAX_TOP_K1000Max search results per query
NORNICDB_QDRANT_GRPC_TLS_ENABLEDfalseEnable native TLS on the gRPC listener
NORNICDB_QDRANT_GRPC_TLS_CERTunsetServer certificate path
NORNICDB_QDRANT_GRPC_TLS_KEYunsetServer private-key path
NORNICDB_QDRANT_GRPC_TLS_CLIENT_CAunsetCA used to verify client certificates
NORNICDB_QDRANT_GRPC_TLS_CLIENT_AUTH_MODEnonenone, request, request_verify, or require_verify

Authenticated public gRPC listeners must enable native TLS and provide a certificate and key. request_verify and require_verify also require a client CA. TLS protects the transport; clients must still send valid Basic, Bearer, or API-key metadata when application authentication is enabled.

Embedding ownership ​

  • If NORNICDB_EMBEDDING_ENABLED=true, NornicDB owns embeddings; Qdrant vector mutation RPCs may be rejected to avoid conflicting sources of truth.
  • If you want Qdrant clients to upsert/update/delete vectors directly, set NORNICDB_EMBEDDING_ENABLED=false.

Configuration Validation ​

NornicDB validates configuration on startup and will:

  1. Reject invalid values (e.g., negative cache sizes)
  2. Apply sensible defaults for missing settings
  3. Log warnings for potentially problematic combinations
  4. Fail fast on critical configuration errors

Performance Impact ​

Async Write Settings ​

SettingImpactRecommendation
async_writes_enabled: trueBuffered acknowledgement for eligible writesOpt in only after qualifying durability and memory requirements
async_flush_interval: autoDelay adapts to measured drain latencyMeasure workload and recovery behavior
async_max_ops: 100000Memory usage vs bulk performanceRaise only with ample RAM

Search Similarity ​

ThresholdUse CaseImpact
0.7-1.0High precisionFewer, more relevant results
0.5-0.7BalancedGood for most applications
0.3-0.5High recallMore results, good for AI assistants

Troubleshooting ​

Common Issues ​

High memory usage:

  • Reduce async_max_ops (the buffered generation-size cap)
  • Monitor during bulk operations
  • Consider disabling async writes for memory-constrained environments

Stale data reads:

  • Reduce async_flush_interval for more frequent writes
  • Disable async writes if strong consistency is required
  • Monitor WAL size and compaction

Poor search results:

  • Adjust min_similarity based on your embedding model
  • Consider model-specific thresholds (e.g., lower for Apple Intelligence)
  • Test with your specific embedding provider

Monitoring ​

Monitor these metrics to optimize configuration:

bash
# Async write performance
curl http://localhost:7474/metrics | grep async

# Cache hit rates
curl http://localhost:7474/metrics | grep cache

# Search performance
curl http://localhost:7474/metrics | grep search

Example Configurations ​

Development Environment ​

yaml
database:
  async_writes_enabled: true
  async_flush_interval: 10ms # Fast feedback
  async_max_ops: 5000

search:
  min_similarity: 0.3 # More results for testing

Production High-Throughput ​

yaml
database:
  async_writes_enabled: true
  async_flush_interval: 100ms # Initial delay; measure the workload
  async_max_ops: 200000

search:
  min_similarity: 0.7 # Higher precision

Memory-Constrained ​

See memory management for process and transaction budgets.

yaml
storage:
  mode: low
database:
  async_writes_enabled: false # Disable to save memory
  # or small caps:
  # async_max_ops: 2000

search:
  min_similarity: 0.8 # Reduce result processing
Analytics privacy