{"record":{"id":"ecc878179104f4e3","repo":"influxdata/influxdb","slug":"mapped-to-out-of-bounds-shard","errorCode":null,"errorMessage":"mapped to out-of-bounds shard","messagePattern":"mapped to out-of-bounds shard","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"core/sharder/src/round_robin.rs","lineNumber":47,"sourceCode":"            shards: shards.into_iter().collect(),\n        }\n    }\n\n    /// Return the next `T` to be used.\n    pub fn next(&self) -> &T {\n        // Grab and increment the current counter.\n        let counter = COUNTER.with(|cell| {\n            let mut cell = cell.borrow_mut();\n            let new_value = cell.wrapping_add(1);\n            *cell = new_value;\n            new_value\n        });\n\n        // Reduce it to the range of [0, N) where N is the number of shards in\n        // this sharder.\n        let idx = counter % self.shards.len();\n\n        self.shards.get(idx).expect(\"mapped to out-of-bounds shard\")\n    }\n}\n\nimpl<T, U> Sharder<U> for RoundRobin<Arc<T>>\nwhere\n    T: Send + Sync + Debug,\n    U: Send + Sync + Debug,\n{\n    type Item = Arc<T>;\n\n    fn shard(\n        &self,\n        _table: &str,\n        _namespace: &data_types::NamespaceName<'_>,\n        _payload: &U,\n    ) -> Self::Item {\n        Arc::clone(self.next())\n    }","sourceCodeStart":29,"sourceCodeEnd":65,"githubUrl":"https://github.com/influxdata/influxdb/blob/d28e26e048401c53cbb98cf2d6ab0cf1e98048ca/core/sharder/src/round_robin.rs#L29-L65","documentation":"RoundRobin::next() takes a thread-local counter, reduces it with counter % self.shards.len(), and indexes the shard list. Because the modulo already guarantees idx < len, the .expect('mapped to out-of-bounds shard') is a defensive invariant guard that should be unreachable. Note that if the shard list is empty, this method panics earlier at counter % 0 ('attempt to calculate the remainder with a divisor of zero'), not at this expect.","triggerScenarios":"Calling RoundRobin::next() (directly or via the Sharder::shard impl that ignores table/namespace). With a non-empty shards Vec the expect cannot fire; an empty Vec panics at the modulo first. It only fires if the shards field is corrupted between the len() read and the .get(), which is effectively impossible in safe Rust.","commonSituations":"Practically unseen. The related real-world failure is constructing RoundRobin::new(empty_iterator) and then calling next(), which panics in the modulo — typically from a discovery/health-check layer passing zero healthy nodes into the sharder.","solutions":["If you saw this exact message, capture the shards Vec contents and report it as a sharder bug.","For the adjacent real failure: never construct RoundRobin with an empty iterator — assert at construction in your own wrapper (RoundRobin::new itself does not check).","Add a non-empty assert or return-Err wrapper around RoundRobin::new at the topology-loading boundary so a zero-node cluster fails at startup with a clear message."],"exampleFix":"// before\nlet sharder = RoundRobin::new(discovered_nodes); // may be empty => panic on first next()\n\n// after\nassert!(!nodes.is_empty(), \"cannot shard to zero nodes\");\nlet sharder = RoundRobin::new(discovered_nodes);","handlingStrategy":"validation","validationCode":"// RoundRobin::new does not check emptiness — check before constructing:\nlet shards: Vec<_> = endpoints.into_iter().collect();\nassert!(!shards.is_empty(), \"RoundRobin requires at least one shard\");\nlet sharder = RoundRobin::new(shards);","typeGuard":"fn non_empty<T>(v: &[T]) -> bool { !v.is_empty() }","tryCatchPattern":null,"preventionTips":["Guard the node list from discovery/health checks so a zero-node snapshot never reaches RoundRobin::new.","Fail at startup with a descriptive message instead of at first request (modulo-by-zero panic).","Log the shard count when constructing sharders so degenerate configurations are visible."],"tags":["rust","sharding","round-robin","invariant","panic","influxdb"],"backgroundTag":"empty-shard-list","analyzedSha":"d28e26e048401c53cbb98cf2d6ab0cf1e98048ca","analyzedAt":"2026-08-16T19:53:34.623Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}