Berkeley DB Reference Version 2026.09.11

Deployment health: what to alarm on

Berkeley DB holds several of its resources in statically-sized shared regions. When one of them is exhausted, the failure does not appear where the resource was consumed: it appears later, as a bare ENOMEM returned by some unrelated but perfectly legitimate operation — a DB_ENV->txn_begin(), a DB->put(), a cursor open. By then the deployment is already broken, and the statistics that would have explained it describe a state that no longer exists.

This section names the small set of numbers that predict that failure before it happens, where to read each one, what bounds it, and what to do when it moves. Every number below is available through the existing statistics APIs and printed by db_stat; none of them requires a special build, a debug library, or a restart to observe.

The one number per resource: utilization

For each statically-sized resource, the signal is a ratio:

utilization = in-use / configured-maximum

Reading it requires that a maximum actually be configured. If you have not set one, the resource grows on demand until the region itself is exhausted, there is no denominator, and the health lines say so explicitly (no maximum configured) rather than printing a misleading 0%. Configuring the maxima is therefore step one of making a deployment alarmable, not merely a tuning nicety:

Resource Configure with
Lockers DB_ENV->set_lk_max_lockers()
Lock objects DB_ENV->set_lk_max_objects()
Locks DB_ENV->set_lk_max_locks()
Transactions DB_ENV->set_tx_max()
Mutexes DB_ENV->mutex_set_max()

For sizing guidance see Configuring locking: sizing the system and Sizing a database environment.

The signals

Each row gives the programmatic source (stable, part of the statistics API), the human-readable db_stat line, and the bound that makes the number meaningful.

Locker slots DB_LOCK_STAT.st_nlockers / st_maxlockers, from DB_ENV->lock_stat(). db_stat -c: Locker slots in use. One locker per active transaction, per non-transactional cursor, and per open database handle — plus, under serializable snapshot isolation, per committed reader whose SIREAD markers have not yet been garbage-collected (see below). Exhaustion returns ENOMEM from the next operation that needs a locker.

Lock object slots DB_LOCK_STAT.st_nobjects / st_maxobjects. db_stat -c: Lock object slots in use. One per distinct page (or record, for Queue) under lock.

Lock slots DB_LOCK_STAT.st_nlocks / st_maxlocks. db_stat -c: Lock slots in use.

Mutex slots DB_MUTEX_STAT.st_mutex_inuse / st_mutex_max, from DB_ENV->mutex_stat(). db_stat -x: Mutex slots in use. This is the resource most likely to be exhausted first by the retention effects described below, because every retained locker and every retained transaction detail holds one. Note the denominator: st_mutex_max is the ceiling the region will grow to, whereas st_mutex_cnt is merely what has been allocated so far — utilization against st_mutex_cnt will look alarming while being perfectly healthy.

Active transaction slots DB_TXN_STAT.st_nactive / st_maxtxns, from DB_ENV->txn_stat(). db_stat -t: Active transaction slots in use.

The leading indicators: snapshot and SSI retention

The two signals below are the ones worth watching most closely, because they move before the utilization ratios do, and because they are the ones that grow as a function of application behaviour rather than of concurrency.

Under snapshot isolation a committed transaction's resources cannot always be released at commit time:

Both populations are reclaimed, and both are bounded — but the healthy steady state is a sawtooth, not a flat line. Reclamation is triggered (at checkpoint, and for SIREAD markers when the live marker count passes a fraction of the lock-object table), so the population climbs, is swept, and climbs again. A sawtooth is health. A staircase is the problem.

Retained snapshot transaction details DB_TXN_STAT.st_nsnapshot, with st_maxnsnapshot as the high-water mark. db_stat -t: Snapshot txn details retained (MVCC/SSI). This counts transaction details parked awaiting reclamation, from either cause above. It is reported against st_maxtxns because it consumes the same transaction-region and mutex-region slots that active transactions do. A value that rises monotonically across checkpoints is the signature to act on; a value that oscillates within a band is the expected shape.

Live SSI SIREAD markers db_stat -c: SSI committed-reader SIREAD markers live. Reported against its sweep threshold rather than a configured maximum, because that threshold — a fraction of the allocated lock objects — is its bound. This is a deliberately approximate count maintained as a garbage-collection hint, so read it as a trend, not an exact population. Programmatically, track st_nlockers and st_nsnapshot, which move with it and are exact.

Sample every scrape interval (a minute is ample; these are cheap reads). Alarm on sustained values, not on single samples: the sawtooth means an instantaneous reading near a peak is normal.

Alarm Condition What it means Action
Warning any utilization > 0.8 sustained over several intervals Headroom is nearly gone; the next burst may fail Raise the corresponding maximum. If it is lockers or mutexes, first check for a long-lived snapshot transaction (below)
Critical any utilization > 0.95, or trending up across intervals Exhaustion is imminent; ENOMEM is close Raise the maximum now; plan a restart if the region cannot be grown in place
Critical st_nsnapshot rises monotonically across three or more checkpoints Retained details are not being reclaimed: almost always one long-lived snapshot or SSI transaction holding back the oldest-reader frontier Find and end the long-lived transaction (see below). Checkpoint more often as mitigation, not as a fix
Warning SIREAD markers sustained near or above 100% of the sweep threshold The marker sweep is not keeping up with the SSI read rate Increase the lock-object allocation (which raises the sweep threshold proportionally), or reduce the rate of serializable read-only transactions
Warning st_maxnlockers / st_maxnsnapshot (high-water marks) approach the configured maxima even while current values look fine A past burst nearly exhausted the region Size for the burst, not the average

Finding the long-lived transaction. This is the single most common root cause behind retention alarms, because one forgotten open snapshot transaction pins every version and marker newer than its snapshot:

db_stat -h <home> -t

Read the Active transactions: list. A transaction whose begin LSN is far behind the current log position, or one carrying a large mvcc refcount, is the culprit. Long-running read-only reporting queries under DB_TXN_SNAPSHOT or DB_TXN_SERIALIZABLE are the usual source. The remedy is in the application: commit it, or break it into shorter transactions. Note that raising the maxima only buys time against this cause — the retention is proportional to how long the transaction stays open.

A note on DB_STAT_CLEAR. Reading these values with DB_STAT_CLEAR resets the high-water marks (st_maxnlockers, st_maxnsnapshot, st_mutex_inuse_max) that several of the alarms above depend on. Monitoring should read without DB_STAT_CLEAR so that it does not destroy the history it is meant to be watching, and so that two independent monitors do not blind each other. The current-value counters are unaffected either way.

Cost

These are counters the subsystems already maintain; the utilization ratios are derived when statistics are read, not accumulated on any operation path. Reading them is a region-lock-protected copy, the same cost as any other statistics call, and it does not perturb what it measures. Scraping once a minute is free at any realistic scale.