Skip to main content

Memory

By the end of this page you will know every memory-related config key ScramDB exposes, what each one actually controls, and how to size them for your hardware.

Memory is the highest-payoff tuning lever in ScramDB (see Tuning and Observability). Getting it right means your working set stays resident in RAM instead of round-tripping to disk on every query.

Byte-size values​

Every byte-size field below accepts either a plain integer (bytes) or a human-readable size with a case-insensitive suffix: B (or no suffix), KB/K, MB/M, GB/G, TB/T, PB/P. "4GB", "512MB", "768mb", and "1TB" are all valid. This grammar is shared by every byte-size field in the config, not just the memory ones.

The buffer pool​

[storage]
buffer_pool_size_bytes = "8GB"
KeyDefaultWhat it controls
buffer_pool_size_bytesunset (0 = auto)Explicit buffer pool size. When unset or 0, ScramDB auto-sizes from buffer_pool_percent of detected available RAM.
buffer_pool_percent60Percent of available RAM to use when buffer_pool_size_bytes is not explicitly set. Ignored once you set an explicit nonzero value.
buffer_pool_cap"64GB"Absolute ceiling on the auto-sizer, regardless of detected RAM.

RAM detection is cgroup-aware: ScramDB reads /sys/fs/cgroup/memory.max (cgroup v2) or memory.limit_in_bytes (cgroup v1) first, and falls back to /proc/meminfo if neither is set. This means a container memory limit is honored automatically when sizing the pool. The buffer pool is resolved once, at startup.

Execution memory​

Per-query execution memory, separate from the buffer pool:

KeyDefaultWhat it controls
execution_memory_bytesunset (0 = auto)Explicit per-query execution memory budget. When unset, ScramDB auto-sizes it to 10% of detected RAM (capped by execution_memory_cap, floored at 16MB). An explicit value is honored exactly: it is floor-raised for safety but never silently shrunk to fit; a total that cannot fit is refused at startup with the offending fields named.
execution_memory_percent0 (off)Percent of detected RAM to use, resolved the same cgroup-aware way as the buffer pool and capped by execution_memory_cap. If you set BOTH this and execution_memory_bytes, the explicit byte count wins (it is the more specific statement) and the ignored percent is logged.
execution_memory_cap"64GB"Ceiling on the auto-sizer, consulted whenever the budget is auto-sized (percent set, or neither key set).

The memory watchdog​

[storage]
memory_hard_limit_fraction = 0.8

memory_hard_limit_fraction (default 0.8, must be between 0.1 and 0.95) is the fraction of detected RAM the memory watchdog treats as a hard ceiling. If process RSS crosses this line, active queries are cancelled loudly rather than letting the OS OOM-kill the whole process.

Per-operator memory ([storage.memory])​

These are the equivalent of work_mem and maintenance_work_mem in a traditional database.

[storage.memory]
per_operator_bytes = "128MB"
maintenance_bytes = "512MB"
KeyDefaultWhat it controls
per_operator_bytes"64MB"Budget per sort or hash operator (work_mem equivalent).
hash_mem_multiplier2.0Multiplies per_operator_bytes for a hash-based operator (hash join or hash aggregate build). Must be >= 1.0.
maintenance_bytes"256MB"Working-memory budget for ANALYZE and CREATE INDEX (maintenance_work_mem equivalent). Reserved from the shared execution memory pool in ONE call before collection starts, so a value above that pool could never be satisfied: ScramDB clamps it at startup to half the execution pool and logs the clamp. Raise execution_memory_bytes to lift it.
read_buffer_pool_bytesunset (0 = auto)Hard cap on the page pool's total working set (checked-out plus idle-recycled bytes combined), with deadlock-free backpressure once reached. When unset, ScramDB auto-sizes it to 8% of detected RAM, capped at 16GB. Leave it unset unless you have measured a reason: a fixed value is wrong at both ends of the hardware range (half of RAM on a small container; only ~28 concurrent page units on a 188GB box, which throttles bulk load badly). Raised at startup, loudly, if it falls below the deadlock-free floor of (prefetch_depth + 1) * 1MB.

per_operator_bytes, maintenance_bytes, and read_buffer_pool_bytes are actively wired and consumed today, they are real levers, not stubs. working_set_bytes / working_set_percent (default 75) is the envelope the startup budget check below fits every budget into.

The startup budget check​

Every budget above is resolved once, at startup, and checked as a sum before the server accepts a connection. The envelope is the lesser of [storage.memory] working_set_bytes and memory_hard_limit_fraction times detected RAM; the WAL buffers and a fixed runtime margin come out of it first.

Two outcomes, both loud, never a silent clamp:

  • Auto-sized budgets are fitted. A budget you did not pin (percent-based, or left unset) is scaled down proportionally to fit the envelope.
  • Explicit budgets are refused, not shrunk. If the byte counts you wrote cannot fit, startup fails naming each field and the envelope. Starting anyway would mean the watchdog cancelling queries for using exactly the memory the config granted them. Lower one, or remove it to have it sized for the machine.

If the deadlock-free floors alone do not fit, ScramDB says so and names the knobs: lower [execution] workers (the buffer-pool floor scales with worker count), raise working_set_bytes if you lowered it by hand, or give the machine more RAM.

Fields that parse and validate but are not yet enforced​

One field in [storage.memory] exists in the config surface, validates correctly, and stores the value, but nothing reads it for its documented purpose yet:

KeyDefaultIntended purposeCurrent status
effective_cache_bytes / effective_cache_percentunset/0 (auto) / 50A cost hint for the query planner, similar in spirit to PostgreSQL's effective_cache_size.Configured and stored, not yet consumed by the planner's cost model.

Set it if you want the config to be forward-compatible with a future release, but do not expect it to change query plans today.

Verifying a memory change​

There is no buffer-pool hit-ratio metric exposed on /metrics. The honest verification path is comparative timing:

  1. Apply your memory change and restart ScramDB.
  2. Pick a query over data that fits inside your configured working set.
  3. Run it twice back to back:
psql -h 127.0.0.1 -p 5432 -c '\timing on' -c "SELECT count(*) FROM orders WHERE customer_id = 42;"
psql -h 127.0.0.1 -p 5432 -c '\timing on' -c "SELECT count(*) FROM orders WHERE customer_id = 42;"

Expected: the second run is faster than the first, because the data is now served from the buffer pool instead of disk. If the two runs are about the same speed and both are slow, your buffer pool is likely too small for this working set, or the query is not actually memory-bound (check for a missing index first, see Query Performance).

Next​

See Parallelism for the worker-count lever, or Storage Tiering for how the buffer pool fits into the hot/warm/cold picture.