{"record":{"id":"291115859c1fdf1b","repo":"neondatabase/neon","slug":"too-many-versions","errorCode":null,"errorMessage":"too many versions","messagePattern":"too many versions","errorType":"exception","errorClass":"DownloadError","httpStatus":null,"severity":"warning","filePath":"libs/remote_storage/src/s3_bucket.rs","lineNumber":518,"sourceCode":"            })\n            .map_err(DownloadError::Other)?;\n            fn none_if_empty(v: Option<String>) -> Option<String> {\n                v.filter(|v| !v.is_empty())\n            }\n            version_id_marker = none_if_empty(response.next_version_id_marker);\n            key_marker = none_if_empty(response.next_key_marker);\n            if version_id_marker.is_none() {\n                // The final response is not supposed to be truncated\n                if response.is_truncated.unwrap_or_default() {\n                    return Err(DownloadError::Other(anyhow::anyhow!(\n                        \"Received truncated ListObjectVersions response for prefix={prefix:?}\"\n                    )));\n                }\n                break;\n            }\n            if let Some(max_keys) = max_keys {\n                if versions_and_deletes.len() >= max_keys.get().try_into().unwrap() {\n                    return Err(DownloadError::Other(anyhow::anyhow!(\"too many versions\")));\n                }\n            }\n        }\n        Ok(VersionListing {\n            versions: versions_and_deletes,\n        })\n    }\n\n    pub fn bucket_name(&self) -> &str {\n        &self.bucket_name\n    }\n}\n\npin_project_lite::pin_project! {\n    struct ByteStreamAsStream {\n        #[pin]\n        inner: aws_smithy_types::byte_stream::ByteStream\n    }","sourceCodeStart":500,"sourceCodeEnd":536,"githubUrl":"https://github.com/neondatabase/neon/blob/8f60b04da47ffefe0e52bda2440134b42874eb75/libs/remote_storage/src/s3_bucket.rs#L500-L536","documentation":"list_versions_with_permit accepts an optional max_keys limit; after each page it checks whether accumulated versions plus delete markers already meet or exceed that limit and fails with 'too many versions' rather than silently truncating. In practice this surfaces through time_travel_recover, which forwards its complexity_limit as max_keys.","triggerScenarios":"Calling time_travel_recover (or list_versions) with a max_keys/complexity_limit smaller than the number of object versions plus delete markers under the prefix, e.g. a timeline prefix with heavy layer upload/delete churn.","commonSituations":"Time-travel retention runs on busy tenants whose version churn per prefix exceeds the configured complexity limit; limits tuned before branching-heavy workloads increased version counts.","solutions":["Increase the max_keys/complexity_limit passed to the listing or time-travel API","Reduce version count under the prefix (lifecycle rules, retention cleanup)","If a complete enumeration is intended, pass None as the limit"],"exampleFix":"// before\nlet limit = NonZeroU32::new(1000);\nstorage.time_travel_recover(&prefix, ts, done_if_after, &cancel, limit).await?;\n// after: raise the complexity limit to cover actual version churn\nlet limit = NonZeroU32::new(100_000);\nstorage.time_travel_recover(&prefix, ts, done_if_after, &cancel, limit).await?;","handlingStrategy":"try-catch","validationCode":"use std::num::NonZeroU32;\n\n// pick a limit comfortably above measured version churn per prefix,\n// or None when a complete enumeration is required anyway\nfn pick_limit(measured_versions: u32) -> Option<NonZeroU32> {\n    NonZeroU32::new(measured_versions.saturating_mul(2).max(1000))\n}","typeGuard":"fn is_too_many_versions(err: &remote_storage::DownloadError) -> bool {\n    matches!(err, remote_storage::DownloadError::Other(e)\n        if e.to_string().contains(\"too many versions\"))\n}","tryCatchPattern":"match storage.list_versions(&prefix, mode, limit, &cancel).await {\n    Err(DownloadError::Other(e)) if e.to_string().contains(\"too many versions\") => {\n        // decide: raise the limit and retry, or shed this tenant/prefix\n    }\n    other => other?,\n}","preventionTips":["Size the complexity limit from versions-per-prefix metrics","Alert when a prefix approaches the configured limit","Pass None for full enumerations such as GC planning"],"tags":["s3","rust","pagination","limits","time-travel"],"backgroundTag":"result-limit-exceeded","analyzedSha":"8f60b04da47ffefe0e52bda2440134b42874eb75","analyzedAt":"2026-08-16T23:39:28.135Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}