microsoft/FASTER · error
Number of non-mutable pages (%u) is less than…
Error message
Number of non-mutable pages (%u) is less than 'kNumHeadPages' (4)
What it means
PersistentMemoryMalloc's constructor partitions the log into pages and marks some fraction as mutable. It requires that at least kNumHeadPages (4) pages remain immutable so that when the in-memory log fills up, log data can always be flushed/dumped to disk. If buffer_size_ minus num_mutable_pages_ is below 4, the constructor throws std::invalid_argument.
Solutions
- Increase the log size so that at least 4 pages remain immutable: log_size >= (num_mutable_pages + 4) * kPageSize
- Lower the mutable fraction to at most 1.0 - (4 * kPageSize) / log_size, as printed by the accompanying log_info message
- If the allocator truly needs no disk dump (e.g. compaction scratch), construct it with the no-backing-storage option to skip the check
- Double-check kPageSize and requested log size units (bytes vs MiB) to avoid unintentionally tiny logs
Example fix
// before experimental::LogSettings settings; settings.epoch = &epoch; settings.fraction_mutable = 0.9; // leaves < 4 immutable pages on a small log f2 = std::make_unique<FasterKv>(...); // after size_t log_size = 1ULL << 30; // or raise log size double max_fraction = 1.0 - (4.0 * kPageSize) / log_size; settings.fraction_mutable = std::min(0.9, max_fraction); // guarantee >= 4 immutable pages
Defensive patterns
Strategy: validation
Validate before calling
size_t kPageSize = static_cast<size_t>(Devices::FileSystem::page_size()); // or FASTER kPageSize
size_t min_log_size = 4 * kPageSize; // plus whatever mutable pages you need
if (log_size <= min_log_size || mutable_fraction > 1.0 - (4.0 * kPageSize) / (double)log_size) {
throw std::invalid_argument("log too small for kNumHeadPages immutable pages");
} Type guard
bool IsValidLogConfig(size_t log_size, double mutable_fraction, size_t page_size) {
if (log_size < 4 * page_size) return false;
return mutable_fraction <= 1.0 - (4.0 * static_cast<double>(page_size)) / static_cast<double>(log_size);
} Try / catch
try {
store = std::make_unique<FasterKv>(...);
} catch (const std::invalid_argument& e) {
// message mentions 'kNumHeadPages immutable pages'
std::cerr << "Log configuration invalid: " << e.what() << "\n";
// fall back to a larger log size or lower mutable fraction and retry
} Prevention
- Compute the max allowed mutable fraction as 1.0 - (4*kPageSize)/log_size and clamp before construction
- Keep unit tests that construct the store with your production log settings so size regressions fail fast
- Remember the check is skipped for no-backing-storage allocators — don't rely on that path for normal stores
When it happens
Trigger: Constructing PersistentMemoryMalloc (or a FASTER store with a custom log size and mutable_fraction/LogMutableFraction setting) where the log is so small or the mutable fraction so high that fewer than 4 pages of headroom remain: e.g. log_size slightly above 4*page_size with mutable_fraction close to 1.0, or a tiny log (e.g. a few MiB) where rounding to pages yields fewer than 4 non-mutable pages. Skipped entirely when has_no_backing_storage_ is set (compaction).
Common situations: Configuring a small in-memory log for testing with default or near-1.0 mutable fraction; miscalculating log size after changing kPageSize or memory-market settings; compaction-like usage forgetting the no-backing-storage flag is what normally suppresses this check.
Understand the failure class
Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.
Related errors
- LogSettings.LogDevice needs to be specified (e.g., use…
- Segment ( ) must be at least of page size ( )
- Memory size ( ) must be configured to be either 1 (i.e., 0…
- Page size must be at least of device sector size
- LogSettings.ObjectLogDevice needs to be specified (e.g.…
AI-assisted analysis of microsoft/FASTER@321d872eab (2026-09-15).
Data as JSON: /api/errors/8c5c4034f6ec54f6.
Report an issue: GitHub.
Appendix: source
Thrown at cc/src/core/persistent_memory_malloc.h:605
throw std::invalid_argument{ "Must have at least 2 non-head pages" };
}
// The latest N pages should be mutable.
// If mutable fraction is 0, then allocate minimum size possible (i.e. 2 mutable pages)
num_mutable_pages_ = (log_mutable_fraction > 0) ? static_cast<uint32_t>(log_mutable_fraction * buffer_size_) : 2;
log_debug("Num mutable_pages = %u", num_mutable_pages_);
if(num_mutable_pages_ <= 1) {
// Need at least two mutable pages: one to write to, and one to open up when the previous
// mutable page is full.
throw std::invalid_argument{ "Must have at least 2 mutable pages" };
}
// Make sure we have at least 'kNumHeadPages' immutable pages.
// Otherwise, we will not be able to dump log to disk when our in-memory log is full.
// If the user is certain that we will never need to dump anything to disk
// (this is the case in compaction), skip this check.
if(!has_no_backing_storage_ && buffer_size_ - num_mutable_pages_ < kNumHeadPages) {
log_error("Number of non-mutable pages (%u) is less than 'kNumHeadPages' (4)", buffer_size_ - num_mutable_pages_, kNumHeadPages);
log_info("For given log size (%.3lf MiB) set mutable fraction to *no more* than %.3lf.",
static_cast<double>(log_size) / (1 << 20),
1.0 - static_cast<double>((kNumHeadPages * kPageSize))/static_cast<double>(log_size));
throw std::invalid_argument{ "Must have at least 'kNumHeadPages' immutable pages" };
}
page_status_ = new FullPageStatus[buffer_size_];
pages_ = new uint8_t* [buffer_size_];
for(uint32_t idx = 0; idx < buffer_size_; ++idx) {
if (pre_allocate_log_) {
pages_[idx] = reinterpret_cast<uint8_t*>(aligned_alloc(sector_size, kPageSize));
std::memset(pages_[idx], 0, kPageSize);
// Mark the page as accessible.
page_status_[idx].status.store(FlushStatus::Flushed, CloseStatus::Open);
} else {
pages_[idx] = nullptr;View on GitHub (pinned to 321d872eab)