Skip to content

docs(etl): correct why the users insert needs ON CONFLICT DO NOTHING - #429

Merged
rickyrombo merged 1 commit into
mainfrom
fix/drop-unneeded-user-insert-conflict
Aug 4, 2026
Merged

docs(etl): correct why the users insert needs ON CONFLICT DO NOTHING#429
rickyrombo merged 1 commit into
mainfrom
fix/drop-unneeded-user-insert-conflict

Conversation

@rickyrombo

@rickyrombo rickyrombo commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Follow-up to #425, correcting a claim I made there. Comments only — no behaviour change.

What was wrong

#425 justified ON CONFLICT DO NOTHING on the users insert with "nothing validates that a user is new."

That is false. validateUserCreate has called userExists since #148, and migratedUserCreateHandler does the same on the genesis replay path.

Why the clause is still right

My first pass at this PR removed the clause as dead code. That was also wrong, and for an interesting reason: userExistsINSERT is check-then-act, which is atomic only within a transaction.

  • One writer — check and insert share a transaction, so the conflict is unreachable. This is the case I was reasoning about.
  • Two writers — a concurrent writer can pass its own userExists before this insert commits, and both insert. Nothing in the code prevents it.

The second case is not hypothetical: it is the most plausible account of the five duplicate current rows 0035 cleans up, three of which pair a bare-hex txhash with a 0x-prefixed one.

So users_current_uniq_idx is what actually closes the race. DO NOTHING only decides what the loser does about it — and a no-op is both correct (the row it would write already exists) and better than the alternative: a hard error here hits sp.Rollback + return false in indexer.go, dropping the tx and its audit row, silent apart from a log line. That is the same path that silently lost 30 txs during the genesis replay.

Changes

  • user_create.go — rewrite the comment above insertUserWithState.
  • 0035_users_one_current_row.up.sql — rewrite the paragraph asserting the missing check.

The constraint, the backfill, and the fifteen genesis-writer joins are all untouched.

🤖 Generated with Claude Code

#425 justified the clause with the claim that nothing validates that a user
is new. That is wrong: validateUserCreate has called userExists since #148,
and migratedUserCreateHandler does the same on the genesis replay path.

The clause is still needed, for a different reason. userExists followed by
INSERT is check-then-act, and that is atomic only within one transaction. A
single writer therefore cannot reach the conflict, but a second writer can,
by passing its own check before this insert commits — which is the most
plausible account of the five duplicate current rows 0035 cleans up, three
of which pair a bare-hex txhash with a 0x-prefixed one.

users_current_uniq_idx is what closes that race. DO NOTHING decides what the
loser does about it, and no-op is both correct (the row it would write
already exists) and better than the alternative: a hard error here rolls
back the savepoint and drops the tx along with its audit row, which is
silent apart from a log line.

Comments only — no behaviour change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@rickyrombo
rickyrombo force-pushed the fix/drop-unneeded-user-insert-conflict branch from 7d96170 to 994007e Compare August 4, 2026 21:28
@rickyrombo rickyrombo changed the title fix(etl): drop the unneeded ON CONFLICT on the users insert docs(etl): correct why the users insert needs ON CONFLICT DO NOTHING Aug 4, 2026
@rickyrombo
rickyrombo merged commit cc26f69 into main Aug 4, 2026
6 checks passed
@rickyrombo
rickyrombo deleted the fix/drop-unneeded-user-insert-conflict branch August 4, 2026 23:42
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