neondatabase/neon · error · BasebackupError

invalid SlruKind::Clog record: block.len()={}

Error message

invalid SlruKind::Clog record: block.len()={}

What it means

During basebackup SLRU assembly, every clog (pg_xact) page image must be exactly BLCKSZ (8192) or BLCKSZ+8 (8200, the long-page-header form). Any other length means the image reconstructed from timeline layers is malformed, so the pageserver aborts the basebackup rather than shipping corrupt data.

Source

Thrown at pageserver/src/basebackup.rs:260

where
    W: AsyncWrite + Send + Sync + Unpin,
{
    fn new(ar: &'a mut Builder<&'b mut W>) -> Self {
        Self {
            ar,
            buf: Vec::new(),
            current_segment: None,
            total_blocks: 0,
        }
    }

    async fn add_block(&mut self, key: &Key, block: Bytes) -> Result<(), BasebackupError> {
        let (kind, segno, _) = key.to_slru_block()?;

        match kind {
            SlruKind::Clog => {
                if !(block.len() == BLCKSZ as usize || block.len() == BLCKSZ as usize + 8) {
                    return Err(BasebackupError::Server(anyhow!(
                        "invalid SlruKind::Clog record: block.len()={}",
                        block.len()
                    )));
                }
            }
            SlruKind::MultiXactMembers | SlruKind::MultiXactOffsets => {
                if block.len() != BLCKSZ as usize {
                    return Err(BasebackupError::Server(anyhow!(
                        "invalid {:?} record: block.len()={}",
                        kind,
                        block.len()
                    )));
                }
            }
        }

        let segment = (kind, segno);
        match self.current_segment {

View on GitHub (pinned to 8f60b04da4)

Solutions

  1. Check pageserver logs for layer read or reconstruction errors near the failure
  2. Retry the basebackup at a different LSN or after a restart to rule out transient reads
  3. Validate the tenant's layers and their remote copies
  4. If corruption is confirmed, rebuild the tenant from a healthy branch or backup

Example fix

// before: trust any returned image
let img = timeline.get_rel_page(key, Version::at(lsn), ctx).await?;
// after: validate size at read time to catch corruption early
if !matches!(img.len(), 8192 | 8200) {
    tracing::error!(?key, len = img.len(), "unexpected clog page size");
    anyhow::bail!("corrupt clog image for {key}");
}
Defensive patterns

Strategy: validation

Validate before calling

// periodic scrub: verify every clog block image size without taking a basebackup
for key in scan_slru_keys(SlruKind::Clog).await? {
    let img = timeline.get(key, lsn, ctx).await?;
    if !matches!(img.len(), 8192 | 8200) {
        alert_corruption(ttid, key, img.len());
    }
}

Type guard

fn is_valid_clog_page(img: &bytes::Bytes) -> bool {
    matches!(img.len(), 8192 | 8200)
}

Try / catch

match serve_basebackup(request).await {
    Ok(b) => b,
    Err(e) if e.to_string().contains("invalid SlruKind::Clog record") => {
        // schedule layer validation; surface a corruption alert, do not retry
        corruption_alert(ttid, e).await;
        return Err(e);
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: A clog block image of unexpected size returned for a key decoded by to_slru_block(): corrupted layer files (local or remote), a page-image reconstruction bug, or pg-version dispatch producing the wrong page layout.

Common situations: Bit rot or partial writes in layer storage; corrupted remote-storage objects after transfer failures; old branches written by different neon versions.

Related errors


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