Add generate_jittered_key_between for concurrent inserts - #3
Conversation
generate_key_between is deterministic, so two writers concurrently inserting between the same pair of keys generate the same key - and a later insert between two equal keys raises FIError. The new function appends jitter_length (default 3) random digits so simultaneous identical inserts diverge with probability ~1 - 62^-3. Design notes: - Separate function rather than a parameter, so the core parity surface stays signature-identical to the JS reference and deterministic. - The suffix is skipped when the generated key is a prefix of the effective upper bound (e.g. 'a5' between 'a4' and 'a52'), where appending anything could push the key past the bound. Bounds are normalised first since they may be passed in either order. - The suffix's last character is never the zero digit (order keys may not end in zero). - Optional rng (random.Random) for reproducible tests; the default is a module-level Random. Collision avoidance only, not security. - No jittered generate_n_keys_between: its results contain keys that are prefixes of their neighbours, so suffixes could reorder them, and bulk generation is single-writer anyway. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
doistbot
left a comment
There was a problem hiding this comment.
This PR adds generate_jittered_key_between() to produce order keys with random suffixes, preventing concurrent inserts from generating identical keys.
Few things worth tightening:
- The module-level RNG instance is shared across forked processes, so two workers can produce identical suffixes for the same first insert — reseding via
os.register_at_fork(or per-process instantiation) would keep the concurrent-insert protection effective. - The documented collision probability slightly overstates the suffix space because the final digit excludes the zero character; for positive
jitter_length, the formula should account for that excluded trailing digit rather than using62⁻³directly.
I also left one optional follow-up note in the details below.
Optional follow-up note (1)
fractional_indexing.py:450: This exact seeded value is unverified.
test_jittered_key_appends_suffixmakes the identical call —generate_jittered_key_between(None, None, rng=random.Random(42))— but only asserts the prefix and length, and CI runspytest tests.pywithout--doctest-modules, so the doctest never executes. Addingassert key == 'a0e72'to that test would verify the documented example (and surface it immediately if the value is currently off).
|
|
||
| # Default RNG for generate_jittered_key_between(). Jitter is collision | ||
| # avoidance, not security, so a plain seeded-from-OS Random is fine. | ||
| _jitter_rng = random.Random() |
There was a problem hiding this comment.
This generator's state is copied into child processes when an application imports the module before forking. Two prefork workers handling the same insert as their first equivalent RNG use will therefore produce identical suffixes, defeating the concurrent-insert protection deterministically. Reseed this instance in
after_in_child via os.register_at_fork, or create/reseed it per process.
| inserting between the same pair of keys generate the *same* key - and a | ||
| later insert between two equal keys raises FIError. The random suffix makes | ||
| simultaneous identical inserts diverge with probability roughly | ||
| 1 - len(digits) ** -jitter_length, at the cost of `jitter_length` extra |
There was a problem hiding this comment.
The collision formula overstates the suffix space because the final digit excludes
digits[0]. For example, base 2 with jitter_length=3 has four possible suffixes, so its collision probability is 1/4 rather than 1/8. For positive lengths, use 1 - 1 / ((len(digits) - 1) * len(digits) ** (jitter_length - 1)) (with zero handled separately).
What
New
generate_jittered_key_between()— likegenerate_key_between(), but appendsjitter_length(default 3) random digits to the generated key.Why:
generate_key_between()is deterministic, so two devices concurrently inserting between the same pair of keys generate the same key — and a later insert between two equal keys raisesFIError. The random suffix makes simultaneous identical inserts diverge with probability ≈ 1 − 62⁻³ (~1 in 238k per simultaneous identical insert at the default length), at the cost of 3 extra characters per key. This is the conflict-resolution mechanism the filtersorder_keyplan calls for.Design decisions
generate_key_between()'s docstring.'a5'between'a4'and'a52'), any suffix could push it past the bound. In that corner the plain key is returned unchanged. Bounds are normalised first, sincegenerate_key_between()accepts them in either order.digits[1:], since order keys may not end in the zero digit.rng: random.Randomfor reproducible tests; defaults to a module-level instance. Documented as collision avoidance, not security.generate_n_keys_between: its results routinely contain prefix-related neighbours (51,511), so suffixes could reorder them — and bulk generation (e.g. backfill) is single-writer with no collision problem.Testing
11 new tests (98 total, plus doctests): suffix shape/validity, seeded reproducibility, default RNG,
jitter_length=0equivalence, negative length rejected, the prefix corner in both bound orders, base-2 trailing-zero avoidance, and 1000-insert ordering property tests at jitter lengths 1/3/8 asserting every key stays strictly in bounds and the list stays sorted.Release
Please review and merge — I'll hold off on releasing until you give the word. After merge: Actions → Release with
bump: minorpublishes 1.1.0 (this also picks up the unreleased 1.0.1 negative-nguard from #2).🤖 Generated with Claude Code