| Tested on | Rocky Linux 10.2 (Red Quartz) |
|---|---|
| Package | 389-ds-base 3.2.0-8.el10_2 |
| Applies to | RHEL, Rocky Linux, AlmaLinux, Oracle Linux, CentOS Stream, Fedora |
| Privilege | sudo or root |
| Scope | Tune 389 Directory Server LMDB map size, entry cache, DN cache, and normalized DN cache using , monitor dbmon, and memory budgeting on MDB backends. |
| Related guides | Install 389 Directory Server Configure LDAP indexes dsconf commands |
An LMDB-backed 389 Directory Server instance can still run out of database space, cache working sets inefficiently, or pressure host memory if capacity is guessed instead of measured. LMDB exposes three global limits (nsslapd-mdb-max-size, nsslapd-mdb-max-readers, nsslapd-mdb-max-dbs). Entry and DN caches remain separate per-backend settings.
This guide covers the full LMDB capacity and cache-memory workflow:
- Confirming the instance uses LMDB
- Sizing and increasing the LMDB maximum map
- Configuring readers and named databases
- Monitoring database growth and cache hit ratios
- Tuning entry, DN, and normalized DN caches from evidence
- Budgeting memory across backends and instances
- Accounting for the operating-system page cache
- Troubleshooting
MDB_MAP_FULLand cache exhaustion
Before you start:
- Install 389 Directory Server — running instance with a suffix (this lab uses
ldap1ondc=example,dc=com) - Suffixes and backends — backend names, suffix mapping, and per-backend cache attributes
- Configure 389 Directory Server Indexes for Faster LDAP Searches — index growth affects LMDB size and named-database count
- dsconf commands — online configuration and monitoring
The lab instance is ldap1 on ldap1.example.com with LDAP port 389. The primary backend is userroot for suffix dc=example,dc=com.
Understand LMDB and Directory Server caches
LDAP reads and writes pass through several memory layers before they reach disk:
LDAP operation
|
+--> Entry cache
|
+--> DN cache
|
+--> Normalized DN cache
|
+--> LMDB memory-mapped pages
|
v
Linux operating-system page cache
|
v
Disk| Component | Scope | Purpose |
|---|---|---|
| LMDB map size | Entire instance | Maximum size of the LMDB environment |
| LMDB readers | Entire instance | Maximum simultaneous read operations |
| LMDB named databases | Entire instance | Limit for backends and index databases |
| Entry cache | Per backend | Stores decoded LDAP entries |
| DN cache | Per backend | Maps entry IDs to DNs and RDNs |
| Normalized DN cache | Entire instance | Caches normalized DN strings |
| OS page cache | Host or container | Keeps frequently accessed LMDB pages in RAM |
LMDB maximum size is not an entry-cache allocation. Increasing the LMDB map does not automatically increase the Directory Server entry or DN caches.
Distinguish LMDB settings from BDB settings
New 389 Directory Server 3.x instances default to LMDB. Confirm the active backend implementation before changing anything:
dsconf ldap1 backend config get | grep nsslapd-backend-implementSample output:
nsslapd-backend-implement: mdbDo not apply BDB-only tuning to an LMDB instance. These settings belong to Berkeley DB, not MDB:
nsslapd-dbcachesize
BDB transaction logs
BDB lock tables
BDB checkpoints
BDB deadlock tuningOn LMDB, focus on:
nsslapd-mdb-max-size
nsslapd-mdb-max-readers
nsslapd-mdb-max-dbs
nsslapd-cachememsize
nsslapd-dncachememsize
nsslapd-ndn-cache-max-size
OS page cache| Setting | LMDB relevance |
|---|---|
nsslapd-mdb-max-size |
Yes — maximum LMDB environment size |
nsslapd-mdb-max-readers |
Yes — simultaneous LMDB readers |
nsslapd-mdb-max-dbs |
Yes — named databases inside LMDB |
nsslapd-cachememsize |
Yes — per-backend entry cache (separate from the map) |
nsslapd-dncachememsize |
Yes — per-backend DN cache |
nsslapd-ndn-cache-max-size |
Yes — server-wide normalized DN cache |
nsslapd-dbcachesize |
BDB-specific; do not tune as an LMDB cache |
| BDB transaction-log settings | BDB-specific |
| BDB lock-table settings | BDB-specific |
The Red Hat configuration reference maintains separate BDB and LMDB configuration branches. If you are converting an older instance, use the dedicated BDB-to-LMDB migration procedure instead of mixing settings from both engines.
Record the baseline and monitor with dbmon
Collect instance, backend, database, and host memory data before changing limits. Run these checks under representative workload rather than immediately after a cold start.
Read the global database configuration:
dsconf ldap1 backend config getRecord LMDB globals and entry and DN cache auto-sizing together:
dsconf ldap1 backend config get | grep -E 'nsslapd-(cache-autosize|backend-implement|mdb-max)'Sample output:
nsslapd-backend-implement: mdb
nsslapd-mdb-max-size: 2791731200
nsslapd-mdb-max-readers: 0
nsslapd-mdb-max-dbs: 512
nsslapd-cache-autosize: 25List suffix backends:
dsconf ldap1 backend suffix listCapture cache statistics:
dsconf ldap1 monitor dbmonSample output on this LMDB lab:
DB Monitor Report: 2026-07-17 23:55:14
--------------------------------------------------------
Normalized DN Cache:
- Cache Hit Ratio: 43%
- Free Space: 19.89 MB
- Free Percentage: 99.5%
- DN Count: 676
- Evictions: 0
Backends:
- dc=example,dc=com (userroot):
- Entry Cache Hit Ratio: 4%
- Entry Cache Count: 516
- Entry Cache Free Space: 891.44 MB
- Entry Cache Free Percentage: 99.5%
- Entry Cache Average Size: 9.05 KBLMDB instances do not print a BDB-style Database Cache section in dbmon. Per-backend DN cache lines appear when the monitor reports them; this small lab shows entry and normalized-DN statistics first.
Limit the report to one backend when you are tuning a single suffix:
dsconf ldap1 monitor dbmon -b userrootCheck on-disk database size:
du -sh /var/lib/dirsrv/slapd-ldap1/dbSample output:
4.5M /var/lib/dirsrv/slapd-ldap1/dbInspect host memory:
free -hSample output:
total used free shared buff/cache available
Mem: 3.8Gi 2.4Gi 429Mi 23Mi 1.3Gi 1.4Gi
Swap: 952Mi 340Mi 612MiCheck the Directory Server process footprint:
pid=$(pgrep -o ns-slapd)grep -E 'VmSize|VmRSS|RssAnon|RssFile|VmSwap' /proc/"$pid"/statusSample output:
VmSize: 3177020 kB
VmRSS: 106428 kB
RssAnon: 77816 kB
RssFile: 28604 kB
VmSwap: 0 kBWhere systemd or a container caps memory, record the limit as well:
systemctl show dirsrv@ldap1 --property=MemoryMax --property=MemoryHighSample output:
MemoryHigh=infinity
MemoryMax=infinityUse a baseline table like this before any change:
| Metric | Lab value |
|---|---|
| LMDB maximum size | 2.60 GiB (2791731200 bytes) |
| Entry/DN cache auto-size | nsslapd-cache-autosize: 25 |
| Current database size | 4.5 MiB |
| Available host memory | 1.4 GiB (MemAvailable) |
ns-slapd RSS |
104 MiB |
| Entry cache hit ratio | 4% (cold cache) |
| Entry cache free percentage | 99.5% |
| Normalized DN cache hit ratio | 43% |
| Normalized DN evictions | 0 |
The combination of 4% entry hit ratio and 99.5% free space does not indicate an undersized entry cache. It means the cache has barely been populated or the workload shows little entry reuse.
Low hit ratios immediately after restart are normal until the cache warms under real traffic.
How to read dbmon
Red Hat recommends interpreting hit ratio together with cache occupancy, average entry size, and a representative workload before increasing cache size.
| Observation | Interpretation |
|---|---|
| Low hit ratio, high free percentage | Cold cache, scanning workload, or little entry reuse; increasing the cache is unlikely to help |
| Low hit ratio, almost no free space, and poor latency | The active working set may exceed the cache |
| High hit ratio and almost no free space | Cache is full but may be working efficiently |
| High free percentage after representative warm-up | Current allocation is larger than the observed working set |
| Rising NDN evictions with repeated DN-heavy traffic | Normalized DN cache may require more space |
Capture dbmon after restart, after warm-up, during normal traffic, during peak traffic, and after any proposed change. Do not tune from one idle sample.
Check LMDB configuration and calculate map size
Display the three LMDB globals:
dsconf ldap1 backend config get | grep -E 'nsslapd-(backend-implement|mdb-max)'Sample output:
nsslapd-backend-implement: mdb
nsslapd-mdb-max-size: 2791731200
nsslapd-mdb-max-readers: 0
nsslapd-mdb-max-dbs: 512The attributes are stored under cn=mdb,cn=config,cn=ldbm database,cn=plugins,cn=config. You can query them directly:
ldapsearch -LLL -Y EXTERNAL -H ldapi://%2Fvar%2Frun%2Fslapd-ldap1.socket -b "cn=mdb,cn=config,cn=ldbm database,cn=plugins,cn=config" -s base nsslapd-mdb-max-size nsslapd-mdb-max-readers nsslapd-mdb-max-dbsSample output:
dn: cn=mdb,cn=config,cn=ldbm database,cn=plugins,cn=config
nsslapd-mdb-max-size: 2791731200
nsslapd-mdb-max-readers: 0
nsslapd-mdb-max-dbs: 512Red Hat Directory Server 13 documents these defaults for new installations:
| Setting | Documented default |
|---|---|
nsslapd-mdb-max-size |
20 GiB |
nsslapd-mdb-max-readers |
0 (automatically calculated) |
nsslapd-mdb-max-dbs |
512 |
This lab instance uses a 2.60 GiB map from dscreate, not the 20 GiB documentation default. Check the running instance instead of assuming distribution or upgrade history.
LMDB growth comes from:
- LDAP entries
- Standard indexes
- VLV browsing indexes
- Replication metadata and changelog databases
- Additional suffixes
- Temporary growth during imports and rebuilds
- Future directory growth
Minimum sizing formula:
Required LMDB maximum =
current database size
+ projected data growth
+ projected index growth
+ operational safety marginFor migration sizing, Red Hat measures the current database and adds at least 20% headroom. Example:
Current database: 18 GiB
20% margin: 3.6 GiB
Minimum map: 21.6 GiBGrowing production directories need capacity for the expected retention period, not only the 20% migration minimum.
| Component | Planning column |
|---|---|
| Current LMDB database | |
| Expected user growth | |
| New indexes | |
| Replication changelog | |
| Import or reindex headroom | |
| Safety margin | |
| Selected maximum |
Tune LMDB maximum size, readers, and named databases
Increase the LMDB maximum size
Set a new map limit before the database approaches the current maximum:
dsconf ldap1 backend config set --mdb-max-size 40Gdsconf accepts bytes or unit suffixes such as G. The value may be rounded to the nearest system page boundary. A restart is required:
dsctl ldap1 restartVerify the stored value:
dsconf ldap1 backend config get | grep nsslapd-mdb-max-sizeConfirm the instance answers LDAP searches:
dsctl ldap1 statusldapsearch -LLL -x -H ldap://127.0.0.1:389 -y /root/dm.pw -D "cn=Directory Manager" -b "dc=example,dc=com" -s base "(objectClass=*)"An undersized map cannot hold planned data. An arbitrarily huge value is not automatically better either; Red Hat warns that an excessively large memory-mapped limit can affect performance while still not reserving that much physical RAM.
Do not delete database files or shrink the maximum below the current on-disk database size.
Maximum readers
nsslapd-mdb-max-readers limits simultaneous LMDB read operations. The documented default 0 lets Directory Server calculate the reader limit automatically at startup.
Keep automatic calculation unless the error log shows reader exhaustion or load testing proves a higher value is required. When manual evidence supports a change, update the MDB configuration entry and restart once:
ldapmodify -Y EXTERNAL -H ldapi://%2Fvar%2Frun%2Fslapd-ldap1.socket <<'EOF'
dn: cn=mdb,cn=config,cn=ldbm database,cn=plugins,cn=config
changetype: modify
replace: nsslapd-mdb-max-readers
nsslapd-mdb-max-readers: 2048
EOFUse a value justified by your concurrency measurements, not a generic recommendation. Restart and retest concurrent searches:
dsctl ldap1 restartVerify:
dsconf ldap1 backend config get | grep -E 'nsslapd-mdb-max-(readers|dbs)'Maximum named databases
nsslapd-mdb-max-dbs limits named databases inside the LMDB environment. Suffix backends, default indexes, additional attribute indexes, VLV indexes, changelogs, and internal databases each consume named slots.
The documented default is 512. Red Hat estimates roughly 35 named databases per suffix with default indexes and one additional named database per extra index. Increase the limit only when suffix, index, and changelog counts approach the cap.
When counting shows you are near the limit, raise nsslapd-mdb-max-dbs and restart:
ldapmodify -Y EXTERNAL -H ldapi://%2Fvar%2Frun%2Fslapd-ldap1.socket <<'EOF'
dn: cn=mdb,cn=config,cn=ldbm database,cn=plugins,cn=config
changetype: modify
replace: nsslapd-mdb-max-dbs
nsslapd-mdb-max-dbs: 1024
EOFdsctl ldap1 restartVerify:
dsconf ldap1 backend config get | grep -E 'nsslapd-mdb-max-(readers|dbs)'Sample output after a manual increase (example only):
nsslapd-mdb-max-readers: 2048
nsslapd-mdb-max-dbs: 1024Changes to readers, named databases, and maximum map size all require a restart.
Tune entry, DN, and normalized DN caches
Current Red Hat guidance recommends automatic cache sizing in most deployments. Manual values remain available when dbmon statistics and latency measurements justify them.
Read per-backend cache settings:
dsconf ldap1 backend suffix get userroot | grep -iE 'cache|memsize'Sample output:
nsslapd-cachememsize: 939524096
nsslapd-cachesize: -1
nsslapd-dncachememsize: 134217728nsslapd-cachesize: -1 means the cache has no separate entry-count limit. The byte limit remains nsslapd-cachememsize. Check the global nsslapd-cache-autosize setting to determine whether Directory Server calculated entry and DN cache sizes automatically. On this lab, nsslapd-cache-autosize: 25 is active and nsslapd-cachememsize: 939524096 is about 896 MiB. nsslapd-dncachememsize: 134217728 is 128 MiB.
When auto-sizing is enabled, manually changing nsslapd-cachememsize or nsslapd-dncachememsize can be rejected or replaced by values calculated during startup. Red Hat's manual cache procedure disables auto-sizing first, then sets per-backend byte limits.
| Cache | Setting | Main workload |
|---|---|---|
| Entry cache | nsslapd-cachememsize |
Searches and entry reads |
| DN cache | nsslapd-dncachememsize |
DN lookup, rename, move, and ModDN |
| Normalized DN cache | nsslapd-ndn-cache-max-size |
Repeated DN normalization |
Entry cache
The entry cache stores decoded LDAP entries. A hit avoids reading the entry from LMDB and converting its stored representation.
Estimate required size from monitor data:
Required entry cache ≈
number of active entries
× average entry cache sizeExample using dbmon averages:
100,000 active entries
× 8.9 KiB average
≈ 890 MiBAdd headroom for other backends and host processes. Disable global auto-sizing, set the per-backend byte limit, and restart:
dsconf ldap1 backend config set --cache-autosize=0dsconf ldap1 backend suffix set --cache-memsize=2147483648 userrootdsctl ldap1 restartThat example sets 2 GiB on userroot. Verify both settings:
dsconf ldap1 backend config get | grep nsslapd-cache-autosizedsconf ldap1 backend suffix get userroot | grep nsslapd-cachememsizeSample output:
nsslapd-cache-autosize: 0nsslapd-cachememsize: 2147483648nsslapd-cache-autosize is global. Disabling it stops automatic entry and DN cache sizing for the entire instance. Budget both caches for every backend before switching to manual values.
Warm the cache under the same workload and compare entry hit ratio, free percentage, search latency, process RSS, MemAvailable, and swap activity. Do not chase a cosmetic 100% hit ratio when latency already meets application targets.
Red Hat deprecates the older entry-count-based nsslapd-cachesize setting in favour of byte-based nsslapd-cachememsize.
DN cache
The DN cache maps entry IDs to DNs and RDNs. It matters most for rename, move, ModDN, subtree operations, and entryrdn processing.
Estimate:
Required DN cache ≈
number of cached DNs
× average DN cache sizeSet a manual DN cache only when measurements support it. On RHEL 10.2, nsslapd-cache-autosize sizes both entry and DN caches; changing nsslapd-dncachememsize also requires an instance restart.
# Skip if auto-sizing was already disabled in the entry-cache procedure
dsconf ldap1 backend config set --cache-autosize=0dsconf ldap1 backend suffix set --dncache-memsize=20971520 userrootSkip the cache-autosize=0 command when you already ran it for manual entry-cache sizing.
dsctl ldap1 restartThat example sets 20 MiB. Verify:
dsconf ldap1 backend config get | grep nsslapd-cache-autosizedsconf ldap1 backend suffix get userroot | grep nsslapd-dncachememsizeSample output:
nsslapd-cache-autosize: 0nsslapd-dncachememsize: 20971520Retest rename, move, subtree rename, and DN-heavy searches, then recheck dsconf ldap1 monitor dbmon -b userroot for DN cache hit ratio, count, and free space when the report includes those lines.
Normalized DN cache
Check the server-wide normalized DN cache:
dsconf ldap1 config get nsslapd-ndn-cache-enabled nsslapd-ndn-cache-max-sizeSample output:
nsslapd-ndn-cache-enabled: on
nsslapd-ndn-cache-max-size: 20971520Enable when needed:
dsconf ldap1 config replace nsslapd-ndn-cache-enabled=onSet the maximum size in bytes:
dsconf ldap1 config replace nsslapd-ndn-cache-max-size=67108864Restart and verify with dsconf ldap1 monitor dbmon. Increase the limit only when the cache is enabled, evictions climb, and the workload repeatedly normalizes the same DNs.
Budget memory across backends and instances
Build a host-level budget before raising multiple caches:
Entry cache for backend 1
+ Entry cache for backend 2
+ DN caches
+ Normalized DN cache
+ ns-slapd heap and thread memory
+ LMDB pages resident in OS page cache
+ replication and plug-in working memory
+ import or task memory
+ operating-system requirements
+ other processesExample planning table:
| Consumer | Planned memory |
|---|---|
userroot entry cache |
4 GiB |
configRoot entry cache |
512 MiB |
| DN caches | 256 MiB |
| Normalized DN cache | 128 MiB |
Other ns-slapd memory |
2 GiB |
| OS and LMDB page cache | 8 GiB |
| Safety reserve | 4 GiB |
Multiple Directory Server instances on one host need the same calculation per instance. Do not size each instance as though it owns all available RAM.
Account for Linux page cache, swap, and THP
LMDB reads through a memory-mapped database file. Frequently accessed pages can remain in the Linux page cache in addition to Directory Server entry and DN caches. A large virtual map does not mean every mapped byte is resident RAM.
Monitor host behaviour during tuning:
vmstat 1Watch for falling MemAvailable, sustained swap activity, major page faults, container memory pressure, and OOM-killer events.
Check Transparent Huge Pages on the host:
cat /sys/kernel/mm/transparent_hugepage/enabledSample output:
[always] madvise neverRed Hat recommends disabling THP on systems running Directory Server because its allocation pattern can inflate resident memory and hurt performance. Keep full operating-system tuning outside this article; the operational rule here is to verify THP state before blaming Directory Server caches for unexpected RSS growth.
Plan imports, reindexing, and bulk operations
Large imports, index rebuilds, VLV index builds, and replication initialization can temporarily increase database growth, memory use, disk I/O, page-cache activity, and cache churn.
Before a large operation:
- Check LMDB map headroom against projected growth.
- Check free disk space.
- Record
dbmonstatistics. - Stop unrelated bulk tasks.
- Confirm container or systemd memory limits.
- Monitor database size during the operation.
- Recheck the LMDB map after completion.
Do not set the permanent entry cache equal to a temporary import-memory spike. Import-cache configuration and repeatable benchmarking belong in dedicated performance and import guides.
Test tuning changes
Change one setting at a time:
Baseline
→ Increase one cache or LMDB limit
→ Restart
→ Warm the cache
→ Repeat the same workload
→ Compare results| Metric | Before | After |
|---|---|---|
| LMDB maximum size | ||
| Database size | ||
| Entry cache hit ratio | ||
| DN cache hit ratio | ||
| NDN cache evictions | ||
Search etime |
||
| Rename or ModDN time | ||
| Process RSS | ||
Host MemAvailable |
||
| Swap activity |
Use the same repeatable workload for every comparison. Keep full workload generation in your performance-testing procedure rather than duplicating it here.
Troubleshoot LMDB and cache problems
| Symptom | Likely cause | Fix |
|---|---|---|
MDB_MAP_FULL or environment mapsize limit reached |
nsslapd-mdb-max-size is too small for current data and index growth |
Increase nsslapd-mdb-max-size, restart, and verify the new value; freeing disk space alone does not raise the LMDB map limit |
| Failure after adding indexes or suffixes | nsslapd-mdb-max-dbs exhausted |
Count required named databases, increase the limit, restart |
| Reader exhaustion in error log | Too few LMDB readers for concurrency | Prefer nsslapd-mdb-max-readers: 0; raise manually only with evidence |
| Low entry hit ratio with high free space after warm-up | Scanning workload, cold cache, or little entry reuse | Investigate query patterns before increasing nsslapd-cachememsize |
| Low hit ratio, almost no free space, and poor latency | Active working set may exceed entry cache | Increase nsslapd-cachememsize only when dbmon, latency, and occupancy justify it |
| Low DN-cache hit ratio during rename-heavy work | Undersized DN cache or deep trees | Increase nsslapd-dncachememsize, retest ModDN and move workloads |
| Unexpected process RSS | Large caches, resident LMDB pages, plug-ins, imports, THP | Review every backend cache, THP state, and container limits; map size ≠ RSS |
OOM killer terminates ns-slapd |
Total memory budget exceeded | Reduce manual caches, restore headroom, review THP and cgroup limits |
| Cache change has no effect | Wrong backend, no restart, nsslapd-cache-autosize override, cold cache |
Confirm backend name, auto-size setting, restart, and warm cache under real workload |
Operational recommendations:
- Confirm the instance uses LMDB before tuning.
- Size the map for planned growth, not only current data.
- Leave
nsslapd-mdb-max-readers=0unless measured evidence says otherwise. - Increase
nsslapd-mdb-max-dbsonly when suffix and index count requires it. - Tune entry and DN caches per backend from
dbmonafter warm-up. - Do not apply BDB database-cache advice to LMDB.
- Preserve RAM for the OS page cache and other processes.
- Change one parameter at a time and retest.
What's next
After you complete this guide, continue with:
- Configure LDAP Search Limits in 389 Directory Server — lookthrough and ID-list scan limits
- Configure LDAP Threads and Connections in 389 Directory Server — worker threads and connection handling
- Migrate 389 Directory Server from Berkeley DB to LMDB — BDB to LMDB conversion
- Configure Low Disk Space Protection in 389 Directory Server — disk usage and low-space alerts
Summary
- Verify that the instance uses the MDB backend.
- Record database, memory, and cache statistics with
dsconf monitor dbmon. - Calculate a safe LMDB maximum size with growth and safety margin.
- Increase the map before the database approaches the limit.
- Leave automatic reader sizing unless exhaustion is observed.
- Watch named-database consumption as suffixes and indexes grow.
- Tune entry, DN, and normalized-DN caches from measured statistics, not guesses.
- Budget memory across every backend and instance on the host.
- Preserve operating-system page-cache and safety headroom.
- Validate every change with the same repeatable workload.
References
- 389 Directory Server project
- Red Hat Directory Server 13 — Monitoring database activity using the command line (§8.6)
- Red Hat Directory Server 13 — Performance tuning: identifying required cache sizes (§9.7.2)
- Red Hat Directory Server 13 — Database plug-in configuration attributes (§6.4)
- Red Hat Directory Server 12 — Migrating BDB to LMDB (map sizing)
- Red Hat Enterprise Linux 10 — LMDB configuration parameters

