{"record":{"id":"7845839772268db8","repo":"neondatabase/neon","slug":"pageserver-has-less-than-limit-to-first-n-targets","errorCode":null,"errorMessage":"pageserver has less than limit_to_first_n_targets={limit} tenants","messagePattern":"pageserver has less than limit_to_first_n_targets=(.+?) tenants","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pageserver/pagebench/src/util/cli/targets.rs","lineNumber":26,"sourceCode":"    pub(crate) limit_to_first_n_targets: Option<usize>,\n    pub(crate) targets: Option<Vec<TenantTimelineId>>,\n}\n\npub(crate) async fn discover(\n    api_client: &Arc<mgmt_api::Client>,\n    spec: Spec,\n) -> anyhow::Result<Vec<TenantTimelineId>> {\n    let mut timelines = if let Some(targets) = spec.targets {\n        targets\n    } else {\n        mgmt_api::util::get_pageserver_tenant_timelines_unsharded(api_client).await?\n    };\n\n    if let Some(limit) = spec.limit_to_first_n_targets {\n        timelines.sort(); // for determinism\n        timelines.truncate(limit);\n        if timelines.len() < limit {\n            anyhow::bail!(\"pageserver has less than limit_to_first_n_targets={limit} tenants\");\n        }\n    }\n\n    info!(\"timelines:\\n{:?}\", timelines);\n    info!(\"number of timelines:\\n{:?}\", timelines.len());\n\n    Ok(timelines)\n}\n","sourceCodeStart":8,"sourceCodeEnd":35,"githubUrl":"https://github.com/neondatabase/neon/blob/8f60b04da47ffefe0e52bda2440134b42874eb75/pageserver/pagebench/src/util/cli/targets.rs#L8-L35","documentation":"Shared target discovery truncates the sorted timeline list to limit_to_first_n_targets and then requires that at least that many timelines existed. When the pageserver exposes fewer timelines than the limit, the bench aborts with the requested limit in the message. This fires even for limit=1 on an empty list, so it usually precedes the caller's own empty-list checks.","triggerScenarios":"Passing --limit-to-first-n-targets N when the pageserver has fewer than N timelines; bench defaults (idle_streams uses 1) against a tenant-less pageserver; tenants deleted between discovery runs.","commonSituations":"Single-tenant dev pageservers used with multi-target bench profiles; stale scripts assuming old tenant counts; a wrong management API endpoint yielding an empty list that undershoots any limit of 1 or more.","solutions":["Lower the limit to the number of timelines actually present; count them via the management API tenant list first","Create or attach additional timelines if the bench genuinely needs N targets","Pass explicit targets instead of a numeric limit where the bench allows it"],"exampleFix":"# before: server has 2 timelines\npagebench basebackup --limit-to-first-n-targets 4 ...\n# after\npagebench basebackup --limit-to-first-n-targets 2 ...","handlingStrategy":"validation","validationCode":"let available = mgmt_api::util::get_pageserver_tenant_timelines_unsharded(&client).await?;\nlet limit = spec.limit_to_first_n_targets.unwrap_or(available.len());\nanyhow::ensure!(\n    available.len() >= limit,\n    \"need {limit} timelines, found {}\",\n    available.len()\n);","typeGuard":null,"tryCatchPattern":"let timelines = match discover(&client, spec).await {\n    Ok(ts) => ts,\n    Err(e) if e.to_string().contains(\"limit_to_first_n_targets\") => {\n        eprintln!(\"lower --limit-to-first-n-targets or add timelines\");\n        return Err(e);\n    }\n    Err(e) => return Err(e),\n};","preventionTips":["Count GET /v1/tenant timelines before choosing a limit","Treat limits as derived from the environment, not hardcoded","Prefer explicit target lists for reproducible benches"],"tags":["pagebench","targets","discovery","cli","capacity"],"backgroundTag":"requested-limit-exceeds-available","analyzedSha":"8f60b04da47ffefe0e52bda2440134b42874eb75","analyzedAt":"2026-08-16T23:39:28.135Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}