quickwit-oss/quickwit · error
`nodes` should not be empty
Error message
`nodes` should not be empty
What it means
In the search job placer's handling of a coverage/refresh report event, the code picks the node with the best affinity for a split and expects the candidate node list to be non-empty. The preceding if-condition already returns early when nodes is empty, so the expect asserts an internal invariant; a panic means that guard was bypassed or the max_by_key iterator logic changed.
Source
Thrown at quickwit/quickwit-search/src/search_job_placer.rs:87
impl EventSubscriber<ReportSplitsRequest> for SearchJobPlacer {
async fn handle_event(&mut self, evt: ReportSplitsRequest) {
let mut nodes: HashMap<SocketAddr, SearcherNode> =
self.searcher_pool.pairs().into_iter().collect();
if nodes.is_empty() {
return;
}
let mut splits_per_node: HashMap<SocketAddr, Vec<ReportSplit>> =
HashMap::with_capacity(nodes.len().min(evt.report_splits.len()));
for report_split in evt.report_splits {
let node_addr = nodes
.iter()
.max_by_key(|(_node_addr, node)| {
node_affinity(&node.node_id, &report_split.split_id)
})
// This actually never happens thanks to the if-condition at the
// top of this function.
.map(|(node_addr, _node)| *node_addr)
.expect("`nodes` should not be empty");
splits_per_node
.entry(node_addr)
.or_default()
.push(report_split);
}
for (node_addr, report_splits) in splits_per_node {
if let Some(searcher_node) = nodes.get_mut(&node_addr) {
let report_splits_req = ReportSplitsRequest { report_splits };
let _ = searcher_node.client.report_splits(report_splits_req).await;
}
}
}
}
impl fmt::Debug for SearchJobPlacer {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("SearchJobPlacer").finish()
}View on GitHub (pinned to a39730c5cd)
Solutions
- Verify the early-return guard at the top of handle_event covers the same `nodes` value used in the expect path; fix any divergence.
- If the cluster can legitimately be empty mid-event, handle it explicitly: return early or defer the split assignment instead of expecting non-emptiness.
- Add a unit test with an empty node list hitting the event path to lock in the guard behavior.
Example fix
// before
.map(|(node_addr, _node)| *node_addr)
.expect("`nodes` should not be empty");
// after
let Some(node_addr) = nodes
.iter()
.max_by_key(|(_node_addr, node)| node_affinity(&node.node_id, &report_split.split_id))
.map(|(node_addr, _node)| *node_addr)
else { continue; }; Defensive patterns
Strategy: validation
Validate before calling
if nodes.is_empty() { return; } // ensure the early-return guard runs before assignment Type guard
fn non_empty(nodes: &[Node]) -> Option<&[Node]> { if nodes.is_empty() { None } else { Some(nodes) } } Prevention
- Keep the empty-nodes guard in the same function/scope as the max_by_key selection.
- Add tests for cluster events with zero live nodes.
- Prefer if-let/let-else over expect when emptiness is a runtime possibility.
When it happens
Trigger: handle_event processing a report for a split when the `nodes` map/vec is empty and the early-return guard at the top of the function did not fire — i.e. only after modifying the guard logic or calling this path with an empty node set through new code paths.
Common situations: Seen during cluster changes: all indexing/search nodes left the cluster while a control-plane event was in flight, exposing a race if the empty-check happens before node list shrinks; or during development refactors of search_job_placer.rs.
Understand the failure class
Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.
Related errors
- `assign_jobs` should return at least one client or fail.
- failed to assign search jobs: there are no available searche
- failed to assign search jobs: there are no searcher nodes ca
- node not found in pending
- OTP logs or traces do not support VRL transforms
AI-assisted analysis of quickwit-oss/quickwit@a39730c5cd (2026-09-08).
Data as JSON: /api/errors/7bcf4d60d9f53fc4.
Report an issue: GitHub.