{"record":{"id":"0c86d125d66b36c3","repo":"risingwavelabs/risingwave","slug":"concurrent-backup-job-is-not-supported-existent-j","errorCode":null,"errorMessage":"concurrent backup job is not supported: existent job {}","messagePattern":"concurrent backup job is not supported: existent job (.+?)","errorType":"exception","errorClass":"BackupError","httpStatus":null,"severity":"error","filePath":"src/meta/src/backup_restore/backup_manager.rs","lineNumber":203,"sourceCode":"            env,\n            hummock_manager,\n            Arc::new(MetaMetrics::default()),\n            (\n                risingwave_backup::storage::unused().await,\n                StoreConfig::default(),\n            ),\n        )\n    }\n\n    /// Starts a backup job in background. It's non-blocking.\n    /// Returns job id.\n    pub async fn start_backup_job(\n        self: &Arc<Self>,\n        remarks: Option<String>,\n    ) -> MetaResult<MetaBackupJobId> {\n        let mut guard = self.running_job_handle.lock().await;\n        if let Some(job) = (*guard).as_ref() {\n            bail!(format!(\n                \"concurrent backup job is not supported: existent job {}\",\n                job.job_id\n            ));\n        }\n        // The reasons to limit number of meta snapshot are:\n        // 1. limit size of `MetaSnapshotManifest`, which is kept in memory by\n        // `ObjectStoreMetaSnapshotStorage`.\n        // 2. limit number of pinned SSTs returned by\n        // `list_pinned_ssts`, which subsequently is used by GC.\n        const MAX_META_SNAPSHOT_NUM: usize = 100;\n        let current_number = self\n            .backup_store\n            .load()\n            .0\n            .manifest()\n            .await\n            .snapshot_metadata\n            .len();","sourceCodeStart":185,"sourceCodeEnd":221,"githubUrl":"https://github.com/risingwavelabs/risingwave/blob/6469eb736d691e8e9b8a419a57edd6429ca77417/src/meta/src/backup_restore/backup_manager.rs#L185-L221","documentation":"BackupManager allows only one meta backup job at a time; `start_backup_job` checks `running_job_handle` under a lock and bails if a job is already registered. The error includes the id of the currently running job.","triggerScenarios":"Calling start_backup_job (e.g. via `risectl meta backup` or internal recovery flows) while another backup job is still running and has not been finished or cleaned up.","commonSituations":"Operator triggers backup twice in quick succession; a previous backup crashed without clearing the running-job handle; automated scripts with overlapping schedules.","solutions":["Wait for the existing job (id in the message) to complete before starting a new one","If the previous job is dead/stale, restart the meta node or clear the running job handle so a new backup can start","Add scheduling/locking in tooling to prevent concurrent backup invocations"],"exampleFix":"// before\nlet id = backup_manager.start_backup_job(None).await?; // may conflict\n// after\nif let Some(job) = backup_manager.get_running_job().await { /* wait or abort */ }\nlet id = backup_manager.start_backup_job(None).await?;","handlingStrategy":"try-catch","validationCode":"if backup_manager.get_running_job().await.is_some() { /* defer or abort */ }","typeGuard":null,"tryCatchPattern":"match backup_manager.start_backup_job(remarks).await {\n    Err(e) if e.to_string().contains(\"concurrent backup job\") => { /* wait for running job */ }\n    other => other?,\n}","preventionTips":["Serialize backup triggers with an external scheduler/lock","Monitor running backup jobs before initiating new ones","Add retry-with-backoff around start_backup_job"],"tags":["rust","meta","backup","concurrency"],"backgroundTag":"invalid-state-transition","analyzedSha":"6469eb736d691e8e9b8a419a57edd6429ca77417","analyzedAt":"2026-09-11T21:06:21.487Z","contentChangedAt":"2026-09-11T21:06:21.487Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}