neondatabase/neon · warning · DownloadError

max keys reached

Error message

max keys reached

What it means

list_versions paginates until it has collected every version, but if the caller supplied max_keys it stops and errors as soon as the accumulated count reaches that limit. This is a deliberate guard refusing to return a silently-truncated result set: hitting it means at least max_keys versions exist and the listing was cut short.

Source

Thrown at libs/remote_storage/src/gcs_bucket.rs:304

                               )
                           )
                        }
                        Ok(
                            GCSVersion {
                                key: self.gcs_object_to_relative_path(&name),
                                last_modified: to_system_time(updated).unwrap(),
                                id: VersionId(generation.expect("no version id")),
                                time_deleted: to_system_time(time_deleted),
                            }
                        )
                    }).collect::<Result<Vec<GCSVersion>, _>>();
                
            versions.versions.extend(version_listing?);

            if let Some(max_keys) = max_keys {
                if versions.versions.len() >= max_keys.get().try_into().unwrap() {
                    return Err(DownloadError::Other(
                        anyhow::anyhow!("max keys reached") 
                    ));
                }
            }
            
            if continuation_token.is_none() {
                break
            }
        }
        
        Ok(versions)
        
    }

    async fn put_object(
        &self,
        byte_stream: impl Stream<Item = std::io::Result<Bytes>> + Send + Sync + 'static,
        fs_size: usize,
        to: &RemotePath,

View on GitHub (pinned to 8f60b04da4)

Solutions

  1. Raise or remove the max_keys limit for the listing if the caller can handle the full result set
  2. Treat this error as the explicit 'too many versions' signal and paginate in max_keys-sized batches via continuation tokens
  3. Reduce version churn: compact or delete old versions so listings fit the limit

Example fix

// before: hard error once the limit is hit
if let Some(max_keys) = max_keys {
    if versions.versions.len() >= max_keys.get().try_into().unwrap() {
        return Err(DownloadError::Other(anyhow::anyhow!("max keys reached")));
    }
}

// after: truncate explicitly and signal, rather than erroring
if let Some(max_keys) = max_keys {
    let limit: usize = max_keys.get().try_into().unwrap();
    if versions.versions.len() >= limit {
        versions.versions.truncate(limit);
        versions.truncated = true;
        return Ok(versions);
    }
}
Defensive patterns

Strategy: validation

Validate before calling

// Size max_keys to the expected version count before calling (e.g. from prior metrics).
const EXPECTED_VERSIONS_PER_KEY: usize = 1000;
let max_keys = MaxKeys::new(EXPECTED_VERSIONS_PER_KEY.next_power_of_two());
// or omit max_keys entirely when the caller can stream the full set

Try / catch

// Treat 'max keys reached' as a truncation signal, not a crash.
let listing = match storage.list_versions(key, Some(max_keys), &cancel).await {
    Ok(v) => v,
    Err(DownloadError::Other(e)) if format!("{e:#}").contains("max keys reached") => {
        tracing::warn!("listing for {key} hit max_keys={}; continuing with degraded history", max_keys.get());
        // escalate to a paginated walk without the limit, or degrade gracefully
        return handle_truncated(key).await;
    }
    Err(e) => return Err(e),
};

Prevention

When it happens

Trigger: Calling a listing API with max_keys set while the bucket/prefix contains at least max_keys object versions; heavily churned keys (many re-uploads/deletes) under a versioned bucket.

Common situations: Time-travel or GC listings over buckets with heavy version churn; tooling that passes a conservative default max_keys; capacity probing that sets a small limit.

Related errors


AI-assisted analysis of neondatabase/neon@8f60b04da4 (2026-08-16). Data as JSON: /api/errors/21fac78ea8b22257. Report an issue: GitHub.