neondatabase/neon · error

failed to parse layer name: {e}

Error message

failed to parse layer name: {e}

What it means

layer_map_analyzer reads a listing of files and parses every name with LayerName::from_str, which accepts only generated image and delta layer names (the key-range and LSN-boundary format, for example 000000067F00010001000000000000000001-0000000000000000000001). Any other string fails, and the underlying parse error is attached to this message.

Source

Thrown at pageserver/ctl/src/layer_map_analyzer.rs:80

        let start = match self
            .holes
            .binary_search_by_key(&key_range.start, |hole| hole.0.start)
        {
            Ok(index) => index,
            Err(index) => {
                if index == 0 {
                    return false;
                }
                index - 1
            }
        };
        self.holes[start].0.end >= key_range.end
    }
}

pub(crate) fn parse_filename(name: &str) -> anyhow::Result<LayerFile> {
    let layer_name =
        LayerName::from_str(name).map_err(|e| anyhow!("failed to parse layer name: {e}"))?;

    let holes = Vec::new();
    Ok(LayerFile {
        key_range: layer_name.key_range().clone(),
        lsn_range: layer_name.lsn_as_range(),
        is_delta: layer_name.is_delta(),
        holes,
    })
}

// Finds the max_holes largest holes, ignoring any that are smaller than MIN_HOLE_LENGTH"
async fn get_holes(path: &Utf8Path, max_holes: usize, ctx: &RequestContext) -> Result<Vec<Hole>> {
    let file = VirtualFile::open(path, ctx).await?;
    let file_id = page_cache::next_file_id();
    let block_reader = FileBlockReader::new(&file, file_id);
    let summary_blk = block_reader.read_blk(0, ctx).await?;
    let actual_summary = Summary::des_prefix(summary_blk.as_ref())?;
    let tree_reader = DiskBtreeReader::<_, DELTA_KEY_SIZE>::new(

View on GitHub (pinned to 8f60b04da4)

Solutions

  1. Feed only layer filenames; filter the directory listing before analysis.
  2. Read the attached cause {e} to see which component failed (key, LSN, or layer suffix).
  3. Use the pageserver's own layer listing output as the input source so names are guaranteed well-formed.

Example fix

# before: directory contains index_part.json alongside layers
ls timeline-dir | pageserver_ctl layer-map-analyze ...

# after: filter to layer files only
ls timeline-dir | grep -E '^[0-9A-F]{40}-[0-9A-F]{25}$' | pageserver_ctl layer-map-analyze ...
Defensive patterns

Strategy: validation

Validate before calling

#!/usr/bin/env bash
# only feed generated layer filenames to the analyzer
ls "$timeline_dir" | grep -E '^[0-9A-F]{40}-[0-9A-F]{25}$' | pageserver_ctl layer-map-analyze ...

Type guard

fn is_layer_filename(name: &str) -> bool {
    LayerName::from_str(name).is_ok()
}

Try / catch

match parse_filename(name) {
    Ok(layer) => layers.push(layer),
    Err(e) if e.to_string().starts_with("failed to parse layer name") => {
        // skip non-layer files (index_part.json, READMEs) instead of aborting the analysis
        eprintln!("skipping non-layer file {name}: {e}");
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Pointing the analyzer at a directory that contains non-layer files (index_part.json, README, temp files) or supplying mistyped and partial layer filenames.

Common situations: Analyzing a whole timeline directory instead of a pure layer directory; stray files uploaded by other tooling; layer names from an incompatible naming version.

Understand the failure class

Related errors


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