Thursday, July 9, 2026

fapolicyd 1.6: Manually Sizing the fapolicyd Trust Database

The earlier articles in this series separated fapolicyd status, metrics, and timing into reports that answer different questions. This article is about one practical status question:

How big should the trust database be?
The short answer is that most systems should use:
db_max_size = auto
A fixed value can still make sense when configuration management requires explicit limits, disk allocation needs review, or change control prefers known bounds. If you set the trust database size manually, you need to know what is being sized and how much room reloads can need.

This article explains generation-aware trust database state, the separate LMDB environment generation, and how to read the status report when choosing a manual db_max_size.

What the Trust Database Stores

Trust is one of the inputs used by fapolicyd policy. The trust database is the compact lookup table for trusted file records. The configured trust backends provide the input: rpmdb builds records from the package database, and the file backend reads local trust files for software that is not package-managed.

A dumped trust record shows the trust source, path, size, and recorded digest. The size and digest are used when the configured integrity mode asks fapolicyd to verify that the file on disk still matches the trusted record.

For sizing, think in records rather than path length. A trust entry occupies one fixed-size record slot. A short path and a long path still consume one record; they just leave different amounts of padding inside that record. The useful sizing questions are how many records exist, how many generations may be present during reload, and how much LMDB environment headroom remains.

The trust database is stored in LMDB. LMDB uses a memory-mapped environment with a configured maximum map size. That maximum is what `db_max_size` controls in fapolicyd.conf.

The current trust database code stores logical trust contents in named LMDB databases. A small metadata database records which named database is active. During reload, a separate candidate named "database" can be built from backend snapshots and then published as the new active generation.

If the map is too small, LMDB can return MDB_MAP_FULL. In practical terms, fapolicyd did not have enough LMDB map space to complete the write operation it was trying to do.

Why Generations Matter

It is tempting to describe a trust database reload as replacing old contents with new contents. For a live daemon, the more useful model is generation-aware state.

fapolicyd already performs reload, update, and new mount handling at controlled points in its event loop. The 1.6 work makes the identity and ownership of live state more explicit: decision configuration, rules, trust contents, and LMDB storage can be reported as distinct generations. That is an operational improvement today and a clearer ownership model for future decision workers.

There are two related but different generation concepts:

  • Trust database generation   logical trust contents inside LMDB
  • LMDB environment generation physical data.mdb file generation

The trust database generation is the normal trust reload mechanism. During a reload, fapolicyd builds a candidate named "database" inside the existing LMDB environment and imports backend snapshots into it. Only after the candidate is complete does the daemon publish it as the active trust database generation.

Readers that already started on the old generation keep using it until they finish. New readers move to the new generation. When no readers are left on the old generation, fapolicyd can reclaim it.


Trust database generations let fapolicyd publish a complete candidate while old readers finish on the previous generation.


This model is useful in three ways:

  1. Readers get a stable generation for the decision they already started.
  2. Failed reloads preserve the last good trust database.
  3. Status can report active, retired, and delayed trust generations explicitly.

That last point is important for operations. The report can show which trust generation is active, when it took effect, how many entries it has, and whether retired generations are still waiting for readers to drain.

Environment Generations and High-Water Pages

The LMDB environment generation is not the normal trust reload mechanism. It identifies the physical LMDB environment itself.

LMDB stores data in data.mdb. The map size is the maximum size of that memory-mapped environment. It is not the same thing as active trust data, and it is not the same thing as resident memory. The kernel maps the environment and backs pages as they are used.

The high-water mark is the highest LMDB page number reached inside data.mdb. In the status report, Trust database allocated high-water pages is a storage/allocation measurement for the LMDB environment. It tells you how far the environment has grown, not how many pages the active trust generation currently needs.

Reload churn can make the high-water mark larger than the active trust database. LMDB is copy-on-write, and rebuild-style updates can leave free pages inside the environment. Those unused pages may be reused later, but the data.mdb file does not necessarily shrink just because the active generation is smaller. Compaction rewrites the environment so those holes no longer inflate the on-disk file. A smaller, denser environment can also slightly reduce lookup work, but the main benefit is storage cleanup.

Normal trust database generations publish logical contents inside the current environment. They do not replace the LMDB environment.

Environment replacement is the heavier operation. fapolicyd builds a replacement LMDB environment in a temporary directory from backend snapshots, validates it, closes the live environment during a controlled maintenance window, swaps the replacement data.mdb into place, and reopens LMDB. If validation, rename, or reopen fails, the old environment is preserved or restored.

 

Trust DB generations publish logical contents; LMDB environment generations mark physical data.mdb replacement.
 

That distinction matters for sizing. A normal reload needs enough space to build a candidate generation while the current generation still exists. Compaction resets the physical high-water mark, but it does not make an undersized manual map large enough. If the final active database and reload working set need more room than db_max_size provides, increasing the map size is the answer.

The Status Fields to Watch

Start with the status report:

fapolicyd-cli --check-status

For trust database sizing, capture the resource configuration and resource utilization sections:

fapolicyd-cli --check-status | sed -n '/Resource configuration:/,/Health indicators:/p'

The important fields are:

Trust database max pages
Trust database pages in use
Trust database allocated high-water pages
Retired trust database generations
Oldest retired trust database generation age
Max trust database generation reclaim delay
Trust database resize recommended
Trust database resize reason
Trust database resize target
Trust database compaction recommended
Trust database compaction reason
Trust database compaction target
Trust database pages in use is the active trust database footprint.

Trust database allocated high-water pages is the LMDB environment high-water mark: how much of data.mdb has been allocated inside the map. It can be higher than active pages after reload churn because unused pages may remain in the file until reuse or compaction.

Trust database resize recommended is the field to watch when you manually set db_max_size. It is shown only for numeric manual sizing. It is omitted when db_max_size = auto because auto mode is expected to size the map itself.

If the resize recommendation is yes, the status report also prints Trust database resize target. That is the recommended minimum size in MiB for reload headroom.

Why Reload Needs Headroom

This is the sizing change administrators are most likely to run into on 1.6.

The new trust database reload path builds a candidate generation while the current generation remains available to readers. For a period of time, the LMDB environment needs room for both generations, plus copy-on-write metadata and freelist work before old pages can be reused.

That means a manual map that was large enough for one active trust database may be too small for the 1.6 reload model. Manual sizing now has to account for roughly two trust generations being present during reload. The easier answer is to set:

db_max_size = auto
and let fapolicyd apply the same reload-headroom logic it reports in status.

The manual recommendation is based on reload headroom, not only the final active database size:

  • active trust data should not consume the whole map
  • reload working set should have room for current plus candidate generation

The active target is about steady-state operation. The reload target is about package updates, trust-file updates, and daemon reloads that would otherwise hit MDB_MAP_FULL.


Manual db_max_size needs room for the active trust DB, the candidate generation, and LMDB reload overhead.

How to Choose a Manual Size

If you still want a manual value, start from the daemon's recommendation. Run the status report after the trust database is current and after the system has the package set you expect to run in production.

If status says:

Trust database resize recommended: yes
Trust database resize target: N MiB
then N MiB is the minimum value to use for db_max_size.

Add margin on systems that change often. Package count can grow; language runtimes, developer tools, Java stacks, browser stacks, container tools, and large application suites can add many trusted files. Local trust files under /etc/fapolicyd/ can add entries outside the package database.

A reasonable manual process is to install the expected package set, add expected local trust files, restart fapolicyd, run fapolicyd-cli --check-status, use at least the reported target if resize is recommended, add local growth margin, and check again after several package update or trust reload cycles.

Changing db_max_size in the file does not replace the active LMDB map sizing policy. After editing /etc/fapolicyd/fapolicyd.conf, restart the daemon and check status again.

For fleets, prefer db_max_size = auto unless there is a specific reason to enforce one fixed value everywhere. Different VMs can have different package and local trust footprints, and auto mode lets each host size to its actual database.

Resize Is Not Compaction

The status report has both resize and compaction guidance because they solve different problems.

Resize means the configured map is too small for the reload target. The fix is to increase db_max_size or use auto sizing.

Compaction means the LMDB environment high-water mark is much larger than the active trust database footprint. The fix is a controlled environment rebuild, usually at startup or through the administrative compaction command.

These are easy to confuse. If active pages are large and the resize target is above your manual db_max_size, compaction will not solve the capacity problem. The rebuilt environment still has to fit the active data and future reload working set.

If active pages are modest but allocated high-water pages are high, status may recommend compaction. That is a file layout and high-water reset problem, not necessarily a sign that the final trust data is too large.

If compaction is recommended and you are in a maintenance window, use the administrative compaction command and then check status again:

sudo fapolicyd-cli --compact-trustdb && fapolicyd-cli --check-status
A successful controlled replacement should advance the LMDB environment generation.

What Can Make the Database Grow

More installed trusted files generally mean more trust database entries, but package count is not the only input. Developer workstations often have more language runtimes, interpreters, plugins, browser components, build tools, and nested toolchains than small servers. Local trust files can add application trees under /opt, vendor software outside the package manager, or internal tools copied by configuration management.

Path length is not a useful manual sizing heuristic. A 10-character path and a 400-character path both consume one trust record slot; the difference is padding inside the fixed record. Use the status report and its resize target rather than path shape.

Reload frequency matters for high-water behavior. Repeated package updates, trust updates, and reload tests can push LMDB's allocated high-water mark up even when the final active database returns to roughly the same size.

Long-lived readers matter for reclamation. If retired trust generations remain pinned, old named databases cannot be reclaimed yet. The status report shows retired generation count and age so you can tell whether readers are delaying cleanup.

What Happens If Manual Size Is Too Small

If the manual map is too small, fapolicyd will not silently increase it. On reload, fapolicyd tries to preserve the last good trust database if the new candidate cannot be built. That means a failed reload does not complete the update, but the old trust contents remain active.

The status report is meant to make that risk visible before it becomes an operational problem. The warning sign is:

Trust database resize recommended: yes
The action is to increase db_max_size to at least the reported target, with whatever additional margin your environment needs.

The Simpler Answer

Manual sizing can be done, and the status report gives enough information to distinguish three situations: the map is too small for reload headroom, the file high-water mark should be compacted, or old readers are delaying generation reclamation. That is better than guessing from a generic database-full message.

But LMDB map sizing still has to account for the active database, candidate generation, copy-on-write behavior, high-water growth, package churn, local trust files, and reader lifetimes. It is easy to choose a number that works today and is too small after the next package set change.

The recommended answer is to switch to:

db_max_size = auto
Auto sizing lets fapolicyd apply the same sizing logic when it starts or rebuilds the trust database. Manual sizing is still available for sites that need it, but auto sizing is the better default for most systems.

No comments: