DB
Operations

Memory governor

Set the process envelope and understand normal, ramp, block, seal, and spill behavior.

The memory governor is the process-wide escape valve for sustained pressure. It forces ingest to slow or stop before the configured memory boundary becomes an OOM. It is enabled by default.

Resolve the limit

--memory-limit-bytes=0 selects automatically:

  1. cgroup v2 memory.max;
  2. cgroup v1 memory.limit_in_bytes; or
  3. system RAM;

then uses 90% of that value. A nonzero value is used verbatim.

For predictable operations, set an external container/cgroup limit and an explicit engine limit below it, leaving space for runtime overhead and behavior outside the tracked engine state.

At boot, verify the log line that reports whether the governor is enabled, its resolved limit, limit source, and governed-usage source. If no limit can be resolved, the governor reports that it is inactive.

What usage means

On cgroup v2, the governor observes cgroup usage and subtracts reclaimable clean file cache; dirty/writeback cache remains charged. On cgroup v1 it uses the equivalent cgroup accounting path. Outside a usable cgroup it falls back to process RSS.

This is deliberately closer to the memory pressure seen by the kernel than an application-object counter alone.

Pressure tiers

TierEntryBehavior
Normalbelow 80%Ordinary configured admission and background behavior
Rampat or above 80%Graduated ingest delay; pressure flush/seal/spill work becomes more aggressive
Blockat or above 95%New ingest waits only for the configured bounded backpressure interval; if pressure does not fall, it returns a retryable pre-WAL rejection

A 2% downgrade hysteresis prevents rapid tier oscillation. A tier downgrades only after usage falls below its threshold minus that gap.

Keep --memory-governor=on in production. off is a benchmark or focused diagnostic posture and removes both the last process-wide ingest gate and the shared exact-native aggregate-state backstop.

Exact-native query-state backstop

The pressure tiers above govern process/cgroup memory and ingest. Query execution has an additional hard counter for exact native aggregate state across all concurrent statements:

ControlDefaultEffective value
--query-global-exact-memory-bytes512 MiBConfigured value, capped at 50% of the resolved process/cgroup limit

If limit detection is unavailable, NYXDB retains the finite configured value. The startup log reports the configured/effective exact-state ceiling, process limit, governor status, and local statement memory limit; capture that line with deployment evidence.

count_distinct, uniq, and uniq_exact are exact in the current release. Their native buckets, metadata arrays, allocation-replacement peaks, and string arenas reserve physical bytes against the statement and process counters before allocation. A query with --query-max-operator-memory-bytes=0 still participates in the process counter. Only the explicit --memory-governor=off escape valve sets the global exact-state ceiling to unlimited.

When either limit rejects growth, the query fails with NYXDB_EXEC_BUDGET_EXCEEDED: ... memory ...; it is not truncated and does not return a partial relation. Destruction releases the exact reservation before the failure crosses the one-shot boundary, so a subsequent query can proceed in the same process. Inspect rows_scanned, peak_memory_bytes, and error in system.query_log when investigating this condition.

Tail, seal, and spill

The committed tail has independent row and byte caps:

  • --committed-tail-max-rows, default 10,000,000;
  • --committed-tail-max-bytes, default 2 GiB; and
  • --committed-tail-backpressure-wait-ms, default 30 s.

The delay ramp begins at the configured ratio (default 0.5) and rises between the configured minimum and maximum delay. At the cap, blocking admission waits for bounded drain capacity and then returns a retryable rejection.

Two different mechanisms move different objects:

  • --sealed-memory-max-bytes and --sealed-spill-after-ms govern sealed in-memory NYXP parts for eligible disk-resident append tables. Oldest parts become registered durable disk parts, advancing durable coverage.
  • --committed-tail-spill=on governs sealed raw committed-tail chunks. It serializes spill segments beneath --data-dir; those segments are not registered table parts, and recovery validates and reattaches them. The feature is off by default and requires --data-dir.

Spill is not a substitute for flush/compaction health. It trades RAM for local I/O and recovery state.

Observe

SELECT name, value, description
FROM system.metrics
WHERE name IN (
  'memory_bytes_total',
  'committed_tail_rows',
  'committed_tail_max_rows',
  'committed_tail_bytes',
  'committed_tail_max_bytes',
  'committed_tail_spilled_bytes',
  'storage_sealed_in_memory_bytes'
);

SELECT name, value
FROM system.events
WHERE name IN (
  'memory_governor_ramp_engagements',
  'memory_governor_blocks'
);

SELECT sequence, rank, bytes, allocations, trigger, backtrace
FROM system.memory_profile
ORDER BY sequence DESC, rank ASC;

An empty system.memory_profile means no usable profile is available—not that there are no allocations.

system.metrics.memory_bytes_total is process RSS. Under cgroups, the governor uses cgroup usage minus reclaimable clean cache instead; the current system tables do not expose that governed usage, resolved limit, factor, or tier as one authoritative row. Reconcile pressure incidents against the boot and throttled memory report logs rather than comparing RSS directly with the governor's cgroup threshold.

Respond to sustained Ramp or Block

  1. Preserve logs, metrics/events, live queries, subscriptions, and the latest memory profile.
  2. Stop or reduce nonessential ingest; do not restart blindly.
  3. Check committed-tail growth versus flush and part progress.
  4. Inspect high-memory queries and operator/cardinality limit failures.
  5. Check stream pending bytes/overflow and downstream consumption.
  6. Confirm the configured limit matches the actual cgroup.
  7. Fix the bottleneck or capacity mismatch before raising a limit.

Raise memory only after identifying what is resident and proving the host has corresponding headroom. Otherwise the change postpones the same failure.

On this page