Skip to content

fix(drizzle): prevent duplicate latest=true version rows under concurrent/same-second writes#17413

Open
gonzoblasco wants to merge 2 commits into
payloadcms:mainfrom
gonzoblasco:fix/17216-version-latest-race
Open

fix(drizzle): prevent duplicate latest=true version rows under concurrent/same-second writes#17413
gonzoblasco wants to merge 2 commits into
payloadcms:mainfrom
gonzoblasco:fix/17216-version-latest-race

Conversation

@gonzoblasco

Copy link
Copy Markdown

The createVersion function in the drizzle adapter clears prior latest flags with a separate UPDATE guarded by updatedAt <. This guard is too strict: two autosave writes within the same second produce identical updatedAt values, so the < operator skips the earlier row and both keep latest = true. On D1/SQLite this routinely produces duplicate rows under normal autosave editing.

The fix: replace updatedAt < with id < result.id. Version IDs are auto-incremented in SQLite/D1, so any prior version always has a smaller id. This is deterministic and not subject to timestamp collisions.

Testing:

  • Added expect(docs).toHaveLength(1) to the existing parallel-writes test (was only checking docs[0] is defined)
  • Added a new test that forces two writes with the same updatedAt timestamp and asserts only one latest = true row
  • All 108 version tests pass on SQLite

Fixes #17216

…rent/same-second writes

The createVersion function clears prior latest flags with a separate UPDATE
guarded by updatedAt < comparison. This guard is too strict: two autosave
writes within the same second produce identical updatedAt values, so the <
operator skips the earlier row and both keep latest=true.

Replace the updatedAt < guard with id < result.id. Version IDs are
auto-incremented in SQLite/D1, so any prior version always has a smaller
id. This is deterministic and not subject to timestamp collisions.

Also adds an assertion to the existing parallel-writes test and a new
same-second-writes test that reproduces the exact failure mode from payloadcms#17216.

Fixes payloadcms#17216
Comment thread packages/drizzle/src/createVersion.ts Outdated

@sviat-barbutsa sviat-barbutsa left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, I left an inline comment, please, check it beause it might be useful. Thank you!

…p race

The cleanup query used id != result.id, which is vulnerable to an
interleaving race: two concurrent writes can insert rows A and B,
then B's cleanup runs first (keeps B), then A's cleanup runs second
and with != it also clears B — leaving no version with latest=true.

Changing to id < result.id ensures each cleanup only touches rows
older than its own insert, so a newer concurrent insert is preserved.

Also adds a regression test that simulates the exact interleaving
pattern described in sviat-barbutsa's review.

Refs payloadcms#17413
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants