{"record":{"id":"8c5c4034f6ec54f6","repo":"microsoft/FASTER","slug":"number-of-non-mutable-pages-u-is-less-than-knumheadpages-4","errorCode":null,"errorMessage":"Number of non-mutable pages (%u) is less than 'kNumHeadPages' (4)","messagePattern":"Number of non-mutable pages \\(%u\\) is less than 'kNumHeadPages' \\(4\\)","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cc/src/core/persistent_memory_malloc.h","lineNumber":605,"sourceCode":"      throw std::invalid_argument{ \"Must have at least 2 non-head pages\" };\n    }\n    // The latest N pages should be mutable.\n    // If mutable fraction is 0, then allocate minimum size possible (i.e. 2 mutable pages)\n    num_mutable_pages_ = (log_mutable_fraction > 0) ? static_cast<uint32_t>(log_mutable_fraction * buffer_size_) : 2;\n    log_debug(\"Num mutable_pages = %u\", num_mutable_pages_);\n\n    if(num_mutable_pages_ <= 1) {\n      // Need at least two mutable pages: one to write to, and one to open up when the previous\n      // mutable page is full.\n      throw std::invalid_argument{ \"Must have at least 2 mutable pages\" };\n    }\n\n    // Make sure we have at least 'kNumHeadPages' immutable pages.\n    // Otherwise, we will not be able to dump log to disk when our in-memory log is full.\n    // If the user is certain that we will never need to dump anything to disk\n    // (this is the case in compaction), skip this check.\n    if(!has_no_backing_storage_ && buffer_size_ - num_mutable_pages_ < kNumHeadPages) {\n      log_error(\"Number of non-mutable pages (%u) is less than 'kNumHeadPages' (4)\", buffer_size_ - num_mutable_pages_, kNumHeadPages);\n      log_info(\"For given log size (%.3lf MiB) set mutable fraction to *no more* than %.3lf.\",\n        static_cast<double>(log_size) / (1 << 20),\n        1.0 - static_cast<double>((kNumHeadPages * kPageSize))/static_cast<double>(log_size));\n\n      throw std::invalid_argument{ \"Must have at least 'kNumHeadPages' immutable pages\" };\n    }\n\n    page_status_ = new FullPageStatus[buffer_size_];\n\n    pages_ = new uint8_t* [buffer_size_];\n    for(uint32_t idx = 0; idx < buffer_size_; ++idx) {\n      if (pre_allocate_log_) {\n        pages_[idx] = reinterpret_cast<uint8_t*>(aligned_alloc(sector_size, kPageSize));\n        std::memset(pages_[idx], 0, kPageSize);\n        // Mark the page as accessible.\n        page_status_[idx].status.store(FlushStatus::Flushed, CloseStatus::Open);\n      } else {\n        pages_[idx] = nullptr;","sourceCodeStart":587,"sourceCodeEnd":623,"githubUrl":"https://github.com/microsoft/FASTER/blob/321d872eabda6a0345c8bd76419f89723ed864ae/cc/src/core/persistent_memory_malloc.h#L587-L623","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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"],"exampleFix":"// before\nexperimental::LogSettings settings;\nsettings.epoch = &epoch;\nsettings.fraction_mutable = 0.9;          // leaves < 4 immutable pages on a small log\nf2 = std::make_unique<FasterKv>(...);\n// after\nsize_t log_size = 1ULL << 30;             // or raise log size\ndouble max_fraction = 1.0 - (4.0 * kPageSize) / log_size;\nsettings.fraction_mutable = std::min(0.9, max_fraction); // guarantee >= 4 immutable pages","handlingStrategy":"validation","validationCode":"size_t kPageSize = static_cast<size_t>(Devices::FileSystem::page_size()); // or FASTER kPageSize\nsize_t min_log_size = 4 * kPageSize; // plus whatever mutable pages you need\nif (log_size <= min_log_size || mutable_fraction > 1.0 - (4.0 * kPageSize) / (double)log_size) {\n  throw std::invalid_argument(\"log too small for kNumHeadPages immutable pages\");\n}","typeGuard":"bool IsValidLogConfig(size_t log_size, double mutable_fraction, size_t page_size) {\n  if (log_size < 4 * page_size) return false;\n  return mutable_fraction <= 1.0 - (4.0 * static_cast<double>(page_size)) / static_cast<double>(log_size);\n}","tryCatchPattern":"try {\n  store = std::make_unique<FasterKv>(...);\n} catch (const std::invalid_argument& e) {\n  // message mentions 'kNumHeadPages immutable pages'\n  std::cerr << \"Log configuration invalid: \" << e.what() << \"\\n\";\n  // fall back to a larger log size or lower mutable fraction and retry\n}","preventionTips":["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"],"tags":["configuration","memory-log","invalid-argument","faster"],"backgroundTag":"invalid-config-value","analyzedSha":"321d872eabda6a0345c8bd76419f89723ed864ae","analyzedAt":"2026-09-15T22:18:00.693Z","contentChangedAt":"2026-09-15T22:18:00.693Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}