Skip to content

Retry a colliding node_id instead of 500ing - #302

Merged
Sbussiso merged 2 commits into
masterfrom
fix/node-id-collision
Sep 14, 2026
Merged

Sbussiso merged 2 commits into
masterfrom
fix/node-id-collision

Conversation

@Sbussiso

Copy link
Copy Markdown
Contributor

node_id is the first 8 characters of a uuid432 bits — and the column
is unique=True across every org, not per-org. So a collision isn't a
per-tenant curiosity: it's one customer's new node landing on an id another
customer already holds.

The code drew once and committed, so the unique constraint surfaced as an
unhandled IntegrityError → 500, at the worst possible moment: someone
adding their first node.

Odds

fleet size chance a single creation collides
1,000 nodes 1 in 4,294,967
10,000 nodes 1 in 429,496
100,000 nodes 1 in 42,949

Rare per request, certain enough in aggregate, and free to handle.

Why retry rather than widen the id

8 hex characters is a deliberate UX choice — the operator types it into the
installer, and CameraNode validates exactly that shape
(setup/mod.rs:199: 8 chars, all hex). Widening it would push that cost onto
every install to avoid a once-in-a-fleet event.

Three draws, then a clean 503. Three collisions in a row isn't bad luck at
any plausible fleet size — it means something else is wrong (a duplicated uuid
source, a constraint firing on a different column) — and a 503 says "try
again"
where a 500 says "we crashed".

The unique constraint stays the arbiter rather than a pre-check SELECT, which
would race two concurrent creates.

Tests

Two, both verified to fail without the retry:

$ # with the retry removed:
FAILED test_create_node_survives_a_node_id_collision
FAILED test_create_node_gives_up_cleanly_if_every_draw_collides
2 failed, 38 passed

One forces a single collision and asserts the second draw succeeds with a
different id; the other collides forever and asserts 503 rather than an
unhandled error.

The uuid shim replaces nodes.py's own module reference rather than
mutating the real uuid module — request_context.py also calls uuid4() and
wants a genuine UUID with .hex, which is how my first version of the test
broke.

Verification

  • 874 backend tests pass, ruff clean

🤖 Generated with Claude Code

Sbussiso and others added 2 commits September 13, 2026 20:50
node_id is the first 8 characters of a uuid4 — 32 bits — and the column is
unique across EVERY org, not per-org. So a collision is not a per-tenant
curiosity: it is one customer's new node landing on an id another customer
already holds. The code drew once and committed, so the unique constraint
surfaced as an unhandled IntegrityError, i.e. a 500 at the worst possible
moment — someone adding their first node.

Odds of a single creation colliding, by fleet size:

    1k nodes    1 in 4,294,967
   10k nodes    1 in   429,496
  100k nodes    1 in    42,949

Rare per request, certain enough in aggregate, and free to handle.

Retries rather than widening the id, because 8 hex characters is a
deliberate UX choice: the operator types it into the installer. Three
draws, then a clean 503 — three collisions in a row is not bad luck at any
plausible fleet size, it means something else is wrong (a duplicated uuid
source, or a constraint firing on a different column), and a 503 says
"try again" where a 500 says "we crashed".

Lets the unique constraint be the arbiter rather than a pre-check SELECT,
which would race two concurrent creates.

Two tests, both verified to fail without the retry: one forces a single
collision and asserts the second draw succeeds with a different id, the
other collides forever and asserts 503 rather than an unhandled error.

The uuid shim in those tests replaces nodes.py's OWN module reference
rather than mutating the real uuid module — request_context.py also calls
uuid4() and wants a genuine UUID with .hex, which is how the first version
of the test broke.

874 backend tests pass, ruff clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@Sbussiso
Sbussiso merged commit 92fde9c into master Sep 14, 2026
11 checks passed
@Sbussiso
Sbussiso deleted the fix/node-id-collision branch September 14, 2026 04:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant