Repository navigation
Route keys with XXH3, matching the server and other clients - #45
Merged
Merged
Conversation
The client hashed keys with the classic XXH64 algorithm, while the server and all the other clients (Go, Java, Node, Rust) use XXH3-64 truncated to 32 bits, which is also what the ShardKeyRouter.XXHASH3 assignments announce. In namespaces with more than one shard, the Python client read and wrote keys (and partition keys) on different shards than everyone else, so records were invisible across clients. The server does not check that a key belongs to the shard it is written to, so the misrouted writes succeeded silently. Hash with XXH3-64 instead. The unit test pins the hash to the Go test vectors, and the integration test checks that records written by this client are readable with the Go CLI shipped in the server image, and vice versa, including routing by partition key. Keys already written by the Python client to a multi-shard namespace were stored on the shard picked by XXH64. After this change they are looked up on the shard picked by XXH3, so they can appear to be missing. Signed-off-by: Matteo Merli <mmerli@apache.org>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
ServiceDiscovery.get_shardhashed keys withxxhash.xxh64, which is the classic XXH64 algorithm. The server and every other client use XXH3-64 truncated to 32 bits:common/hash/hash.go:uint32(xxh3.HashString(key))LongHashFunction.xx3()xxh3.xxh64from@node-rs/xxhashxxhash_rust::xxh3::xxh3_64The shard assignments even announce
ShardKeyRouter.XXHASH3, and this client checks for it. Results on the Go test vectors (common/hash/hash_test.go):foobarbazget_shardis the only place keys are hashed, and every routed operation goes through it:put,delete, andgetby keypartition_keyget_sequence_updatesWith N evenly sized shards, a key lands on the correct shard only about 1/N of the time. So in a multi-shard namespace, most records written by the Python client are on a different shard than the one the other clients and the server's split logic expect. Every release so far has this bug (0.1.0 through 0.2.1).
Why it went unnoticed
common/hash.Xxh332is only used by the Go client's routing (oxia/internal/shard_strategy_impl.go) and by the shard-split filter (oxiad/dataserver/database/split_filter.go), so a misrouted write just succeeds. Againstoxia/oxia:latest(0.16.10) with 10 shards, the unfixed client's writes to the wrong shard returned OK.test_floor_ceiling_geton 10 shards) only read back what the Python client wrote itself, which works with any hash.Fix
Hash with
xxhash.xxh3_64_intdigest(key)masked to 32 bits, in a smallxxh332()helper named after the Go function. Nothing else changes, and the shard-assignment thread is untouched.Tests
tests/service_discovery_test.py::test_key_hash_matches_gopins the hash to the Go vectors.tests/client_test.py::test_shard_routing_matches_go_clientruns on a 10-shard standalone container:oxia client get)Without the fix (the helper still computing XXH64), both tests fail:
Each section of the integration test catches the bug on its own: 10 of 11 cross-client reads miss. The one that passes,
bar, happens to land on the same shard under both hashes. With the fix, the full suite passes (68 tests).Proposed migration path (not part of this PR)
A one-off script, built on the fixed client, that scans every shard and moves misplaced records:
range_scanit directly (the per-shard stubs from service discovery) and flag every record whose routing key hashes (XXH3) outside S's range.--apply, for each flagged record:putit on the correct shard withEXPECTED_RECORD_DOES_NOT_EXISTKnown limits, because the client read API doesn't return everything the server stores:
partition_keyare routed by it, but reads don't return it (the server keeps it inStorageEntry.partition_key). The script needs a routing-key callback from the application (key -> partition_key). It defaults to the record key.version_id, modification count, and timestamps, so a cached version fails CAS once.If partition keys or secondary indexes are widely used, a server-side relocation tool would be the full-fidelity option, since the server stores both. That would be a server feature, though.