{"record":{"id":"b94022d4a54f6b99","repo":"risingwavelabs/risingwave","slug":"expect-data-directory","errorCode":null,"errorMessage":"expect data_directory","messagePattern":"expect data_directory","errorType":"exception","errorClass":"BackupError","httpStatus":null,"severity":"error","filePath":"src/storage/backup/src/meta_snapshot_v2.rs","lineNumber":232,"sourceCode":"        storage_url_from_snapshot\n            .strip_prefix(\"hummock+\")\n            .map(|s| s.to_owned())\n            .ok_or_else(|| {\n                BackupError::Other(anyhow!(\n                    \"invalid state_store from metadata snapshot: {}\",\n                    storage_url_from_snapshot\n                ))\n            })\n    }\n\n    fn storage_directory(&self) -> BackupResult<String> {\n        Itertools::exactly_one(self.system_parameters.iter().filter_map(|m| {\n            if m.name == \"data_directory\" {\n                return Some(m.value.clone());\n            }\n            None\n        }))\n        .map_err(|_| BackupError::Other(anyhow!(\"expect data_directory\")))\n    }\n\n    fn table_change_log_object_ids(&self) -> HashSet<HummockRawObjectId> {\n        self.hummock_table_change_logs\n            .iter()\n            .flat_map(|m| {\n                // We cannot use `change_log_ssts` here because `to_table_change_log` returns an owned value, not a reference.\n                let EpochNewChangeLog {\n                    new_value,\n                    old_value,\n                    ..\n                } = to_table_change_log(m);\n                new_value\n                    .into_iter()\n                    .chain(old_value)\n                    .map(|t| t.object_id.as_raw())\n            })\n            .collect()","sourceCodeStart":214,"sourceCodeEnd":250,"githubUrl":"https://github.com/risingwavelabs/risingwave/blob/6469eb736d691e8e9b8a419a57edd6429ca77417/src/storage/backup/src/meta_snapshot_v2.rs#L214-L250","documentation":"MetaSnapshotV2::storage_directory extracts the data_directory system parameter and requires exactly one entry via Itertools::exactly_one; otherwise it throws \"expect data_directory\". The snapshot must record the cluster's data directory so restore can rebuild the catalog path.","triggerScenarios":"Calling storage_directory() on a V2 snapshot whose system_parameters list has zero or multiple entries named \"data_directory\" — parameters missing from the backup, duplicated rows, or a corrupted parameters section.","commonSituations":"Backups taken from clusters where data_directory was never persisted; duplicate system_parameter rows from config migration bugs; legacy snapshots predating the parameter's capture; tests decoding snapshots with empty parameter lists.","solutions":["Inspect the snapshot's system_parameters and confirm a single data_directory entry exists before restoring.","Recreate the backup from a healthy cluster so data_directory is recorded.","If migrating a legacy snapshot, inject the data_directory parameter into the decoded model before calling storage_directory.","Deduplicate system_parameter rows in the source cluster (keep the one from the latest config version) and re-snapshot."],"exampleFix":"// before: assuming presence\nlet dir = snapshot.storage_directory()?;\n// after: fallback to restore-time config\nlet dir = snapshot.storage_directory()\n    .unwrap_or_else(|_| restore_config.data_directory.clone());","handlingStrategy":"fallback","validationCode":"let n = snapshot.system_parameters.iter().filter(|m| m.name == \"data_directory\").count();\nif n != 1 { return Err(anyhow!(\"snapshot has {} data_directory entries, expected 1\", n)); }","typeGuard":"fn has_single_param<'a>(ps: &'a [SystemParam], name: &str) -> Option<&'a str> {\n    let mut it = ps.iter().filter(|p| p.name == name).map(|p| p.value.as_str());\n    match (it.next(), it.next()) { (Some(v), None) => Some(v), _ => None }\n}","tryCatchPattern":"match snapshot.storage_directory() {\n    Ok(dir) => dir,\n    Err(e) if e.to_string().contains(\"expect data_directory\") => {\n        log::warn!(\"snapshot lacks data_directory; using restore config\");\n        restore_config.data_directory.clone()\n    }\n    Err(e) => return Err(e.into()),\n}","preventionTips":["Deduplicate system_parameter rows before taking backups.","Verify both state_store and data_directory exist in each snapshot as part of backup validation.","For legacy snapshots, inject missing parameters programmatically before restore."],"tags":["rust","backup","restore","system-parameters","missing-field"],"backgroundTag":"missing-required-config-field","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"}