neondatabase/neon · error

incompatible resource_multipler and spread_factor

Error message

incompatible resource_multipler and spread_factor

What it means

Thrown during FileCache config validation in the Neon autoscaling vm-monitor. The cache-sizing algorithm splits total memory along two lines whose intersection must exist and stay at or above `min_remaining_after_cache`; that is only guaranteed when `resource_multiplier * (spread_factor + 1.0) < 1.0`. The `ensure!` fires when the two coupled config values violate this invariant, making the size formula unsolvable.

Source

Thrown at libs/vm_monitor/src/filecache.rs:114

        // and
        //
        //   size = `resource_multiplier` × total
        //
        // .. where `total` is the total resources. These are isomorphic to the typical 'y = mx + b'
        // form, with y = "size" and x = "total".
        //
        // These lines intersect at:
        //
        //               `min_remaining_after_cache`
        //   ———————————————————————————————————————————————————
        //    1 - `resource_multiplier` × (`spread_factor` + 1)
        //
        // We want to ensure that this value (a) exists, and (b) is >= `min_remaining_after_cache`. This is
        // guaranteed when '`resource_multiplier` × (`spread_factor` + 1)' is less than 1.
        // (We also need it to be >= 0, but that's already guaranteed.)

        let intersect_factor = self.resource_multiplier * (self.spread_factor + 1.0);
        anyhow::ensure!(
            intersect_factor < 1.0,
            "incompatible resource_multipler and spread_factor"
        );
        Ok(())
    }

    /// Calculate the desired size of the cache, given the total memory
    pub fn calculate_cache_size(&self, total: u64) -> u64 {
        // *Note*: all units are in bytes, until the very last line.
        let available = total.saturating_sub(self.min_remaining_after_cache.get());
        if available == 0 {
            return 0;
        }

        // Conversions to ensure we don't overflow from floating-point ops
        let size_from_spread =
            i64::max(0, (available as f64 / (1.0 + self.spread_factor)) as i64) as u64;

View on GitHub (pinned to 8f60b04da4)

Solutions

  1. Lower `resource_multiplier` so that `resource_multiplier * (spread_factor + 1.0) < 1.0` (e.g. with spread_factor 0.5, keep the multiplier below ~0.667)
  2. Or lower `spread_factor`; at spread_factor 0.0 the multiplier only needs to be < 1.0
  3. Revert to the shipped/default vm-monitor file cache configuration for your deployment
  4. Add a config sanity test in CI that runs the same intersect-factor check

Example fix

// before — fails validation: 0.9 * (0.5 + 1.0) = 1.35 >= 1.0
config.resource_multiplier = 0.9;
config.spread_factor = 0.5;

// after — passes: 0.5 * (0.5 + 1.0) = 0.75 < 1.0
config.resource_multiplier = 0.5;
config.spread_factor = 0.5;
Defensive patterns

Strategy: validation

Validate before calling

if config.resource_multiplier * (config.spread_factor + 1.0) >= 1.0 {
    return Err(anyhow::anyhow!(
        "refusing to start: resource_multiplier * (spread_factor + 1) must be < 1.0"
    ));
}

Prevention

When it happens

Trigger: Validating a `FileCache` config where the pair is too large together, e.g. `resource_multiplier = 0.9` with `spread_factor = 0.5` gives 0.9 × (0.5 + 1.0) = 1.35 ≥ 1.0 and bails. Any pair where the product with (spread_factor + 1) reaches or exceeds 1.0 triggers it.

Common situations: Hand-tuning vm-monitor memory settings; raising `spread_factor` to smooth cache growth without lowering `resource_multiplier`; copying configs between environments with different memory budgets.

Related errors


AI-assisted analysis of neondatabase/neon@8f60b04da4 (2026-08-16). Data as JSON: /api/errors/ec82ebd65c1b9a73. Report an issue: GitHub.