{"record":{"id":"4b871b24df2310cb","repo":"neondatabase/neon","slug":"backup-prev-backup-prev-provided-prev-lsn-pr","errorCode":null,"errorMessage":"backup_prev {backup_prev} != provided_prev_lsn {provided_prev_lsn}","messagePattern":"backup_prev (.+?) != provided_prev_lsn (.+?)","errorType":"exception","errorClass":"BasebackupError","httpStatus":null,"severity":"error","filePath":"pageserver/src/basebackup.rs","lineNumber":151,"sourceCode":"        // provide prev_lsn. (get_last_record_rlsn() might return it as\n        // zero, though, if no WAL has been generated on this timeline\n        // yet.)\n        let end_of_timeline = timeline.get_last_record_rlsn();\n        if req_lsn == end_of_timeline.last {\n            (end_of_timeline.prev, req_lsn)\n        } else {\n            (Lsn(0), req_lsn)\n        }\n    } else {\n        // Backup was requested at end of the timeline.\n        let end_of_timeline = timeline.get_last_record_rlsn();\n        (end_of_timeline.prev, end_of_timeline.last)\n    };\n\n    // Consolidate the derived and the provided prev_lsn values\n    let prev_record_lsn = if let Some(provided_prev_lsn) = prev_lsn {\n        if backup_prev != Lsn(0) && backup_prev != provided_prev_lsn {\n            return Err(BasebackupError::Server(anyhow!(\n                \"backup_prev {backup_prev} != provided_prev_lsn {provided_prev_lsn}\"\n            )));\n        }\n        provided_prev_lsn\n    } else {\n        backup_prev\n    };\n\n    info!(\n        \"taking basebackup lsn={lsn}, prev_lsn={prev_record_lsn} \\\n        (full_backup={full_backup}, replica={replica}, gzip={gzip_level:?})\",\n    );\n    let span = info_span!(\"send_tarball\", backup_lsn=%lsn);\n\n    let io_concurrency = IoConcurrency::spawn_from_conf(\n        timeline.conf.get_vectored_concurrent_io,\n        timeline\n            .gate","sourceCodeStart":133,"sourceCodeEnd":169,"githubUrl":"https://github.com/neondatabase/neon/blob/8f60b04da47ffefe0e52bda2440134b42874eb75/pageserver/src/basebackup.rs#L133-L169","documentation":"While serving a basebackup, the pageserver derives prev_lsn from the timeline state: Lsn(0) when the backup targets a client-specified LSN, or the last record's prev for an end-of-timeline backup. If the request also carries prev_lsn, it must equal the derived non-zero value; otherwise the request is rejected because client and server disagree about the WAL position the backup is anchored to.","triggerScenarios":"End-of-timeline basebackups where the compute's idea of the last record differs from the pageserver's: after failover or re-attach, timeline migration to another pageserver, LSN regression after restoration, or WAL ingestion racing the request; also version skew in how prev_lsn is computed.","commonSituations":"A compute reconnecting to a recovered pageserver; a timeline attached to two pageservers (split brain); old compute images paired with new pageservers.","solutions":["Retry the basebackup so the client resynchronizes with current timeline state, or omit prev_lsn so the server-derived value is used","Verify compute and pageserver run matching neon versions","Confirm exactly one pageserver is attached and active for the timeline","If it persists, capture both LSN values from the message and report it; it usually indicates an LSN bookkeeping bug"],"exampleFix":"// before: client always echoes its cached prev_lsn\nlet req = BasebackupRequest { lsn, prev_lsn: Some(cached_prev), .. };\n// after: on 'backup_prev != provided_prev_lsn', retry letting the server derive it\nmatch send_basebackup(lsn, Some(cached_prev)).await {\n    Err(e) if e.to_string().contains(\"backup_prev\") => {\n        send_basebackup(lsn, None).await?\n    }\n    r => r?,\n}","handlingStrategy":"retry","validationCode":"// before requesting, reconcile the client's last-record view with the server\nlet server_info = timeline_info_from_mgmt_api().await?;\nif client_prev != server_info.last_record_lsn.prev {\n    // refresh client state instead of sending a stale prev_lsn\n}","typeGuard":null,"tryCatchPattern":"match pageserver_basebackup(req).await {\n    Ok(stream) => stream,\n    Err(e) if e.to_string().contains(\"backup_prev\") => {\n        // prev_lsn disagreement: resync and retry without prev_lsn\n        pageserver_basebackup(req.with_prev_lsn(None)).await?\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Keep compute and pageserver versions aligned","Fetch fresh timeline state before requesting end-of-timeline backups","Alert on repeated prev_lsn mismatches; they indicate state divergence","Never attach the same timeline to two active pageservers"],"tags":["pageserver","basebackup","lsn","consistency","replication"],"backgroundTag":"lsn-mismatch","analyzedSha":"8f60b04da47ffefe0e52bda2440134b42874eb75","analyzedAt":"2026-08-16T23:39:28.135Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}