{"record":{"id":"93957a5ffed762d5","repo":"influxdata/influxdb","slug":"sharder-mapped-input-to-non-existant-bucket","errorCode":null,"errorMessage":"sharder mapped input to non-existant bucket","messagePattern":"sharder mapped input to non-existant bucket","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"core/sharder/src/jumphash.rs","lineNumber":104,"sourceCode":"        H: Hash,\n    {\n        let mut state = self.hasher;\n        key.hash(&mut state);\n        let mut key = state.finish();\n\n        let mut b = -1;\n        let mut j = 0;\n        while j < self.shards.len() as i64 {\n            b = j;\n            key = key.wrapping_mul(2862933555777941757).wrapping_add(1);\n            j = ((b.wrapping_add(1) as f64) * (((1u64 << 31) as f64) / (((key >> 33) + 1) as f64)))\n                as i64\n        }\n\n        assert!(b >= 0);\n        self.shards\n            .get(b as usize)\n            .expect(\"sharder mapped input to non-existant bucket\")\n    }\n\n    /// Consistently hash a table and namespace to a `T`. For use in a situation where you don't\n    /// have a payload.\n    pub fn shard_for_query(&self, table: &str, namespace: &str) -> &T {\n        // The derived hash impl for HashKey is hardened against prefix\n        // collisions when combining the two fields.\n        self.hash(&HashKey { table, namespace })\n    }\n}\n\n#[derive(Hash)]\nstruct HashKey<'a> {\n    table: &'a str,\n    namespace: &'a str,\n}\n\n/// A [`JumpHash`] sharder mapping a [`MutableBatch`] reference according to the","sourceCodeStart":86,"sourceCodeEnd":122,"githubUrl":"https://github.com/influxdata/influxdb/blob/d28e26e048401c53cbb98cf2d6ab0cf1e98048ca/core/sharder/src/jumphash.rs#L86-L122","documentation":"JumpHash implements Google's jump consistent hash: the loop advances bucket b while j < shards.len(), so on exit b must be a valid index. The .expect('sharder mapped input to non-existant bucket') is a defensive invariant guard after assert!(b >= 0) — by construction of the loop it should be unreachable, because b always holds a j that was < shards.len(). Seeing it means the jump-hash arithmetic produced an out-of-range bucket (e.g. an f64-to-i64 cast edge in the j computation) or the shards vector was mutated/aliased after construction.","triggerScenarios":"Calling JumpHash::hash / shard / shard_for_query on a sharder whose internal state no longer matches what the loop assumed. The constructor already asserts a non-empty shard set, so an empty shard list panics earlier ('empty shard set given to sharder'), not here. In practice this expect has no normal trigger; it fires only on an implementation bug or memory corruption of the shards Vec.","commonSituations":"Almost never seen; if reported, it follows a dependency upgrade that changed integer/float casting behavior in the jump-hash loop, a fork that altered the shard list after construction, or a corrupted Vec. InfluxDB Enterprise router/sharding code paths (sharding data by table+namespace across nodes) would be the place it surfaces.","solutions":["Treat it as a bug: capture the key, shards.len(), and crate versions and file an issue against the sharder crate — do not try to 'fix' it in calling code.","Verify the JumpHash instance is not constructed once and then have its shard list changed underneath (immutability guarantees shards order); rebuild the sharder when topology changes.","Ensure the constructor path ran (the 'empty shard set given to sharder' assert) so an empty shard set fails fast at startup instead of at first hash.","Pin dependency versions (siphasher) so hash/cast behavior matches the tested build; the repo keeps test_key_bucket_fixture for exactly this mapping stability."],"exampleFix":"// No caller-side fix: b is guaranteed < shards.len() by the loop.\n// If hit, add a reproducing test:\n\n#[test]\nfn repro_out_of_bounds_bucket() {\n    let hasher = JumpHash::new((0..1_000).map(Arc::new));\n    for k in 0..1_000_000u64 {\n        let _ = hasher.hash(k); // panics here => jump-hash math bug, report upstream\n    }\n}","handlingStrategy":"validation","validationCode":"// The constructor already asserts non-empty; validate before building:\nlet shards: Vec<_> = nodes.collect();\nif shards.is_empty() {\n    return Err(\"refusing to start with zero shards\");\n}\nlet sharder = JumpHash::new(shards);","typeGuard":"fn has_shards<T>(v: &[T]) -> bool { !v.is_empty() }","tryCatchPattern":null,"preventionTips":["Fail fast on empty shard sets at topology load time (the JumpHash::new assert exists for this).","Never rebuild or reorder the shard list after handing it to JumpHash — rebuild a new sharder instead.","Pin sharder dependency versions; the repo's test_key_bucket_fixture pins the key->bucket mapping across upgrades."],"tags":["rust","sharding","consistent-hashing","jumphash","invariant","panic","influxdb"],"backgroundTag":"consistent-hashing-invariant","analyzedSha":"d28e26e048401c53cbb98cf2d6ab0cf1e98048ca","analyzedAt":"2026-08-16T19:53:34.623Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}