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.

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.

Appendix: 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)