Design a Reactive Database (Live Queries), stage 5 of 9: break it
Seven tasks in a column of five
The move mutation counts the tasks in the target column, refuses if the count is at the limit, and otherwise updates the task's column. Each mutation runs in a transaction at snapshot isolation: it sees the database as of the moment it started.
System so far· 5 parts
Select a component to see what it is responsible for and which state it owns.
- 1Web and mobile apps → Sync servers: Subscribe and mutate
- 2Sync servers → Function runners: Run a function
- 3Function runners → Versioned store: Read at a snapshot
- 4Versioned store → Subscription tracker: Committed writes, in order
- 5Subscription tracker → Sync servers: These subscriptions changed
- 6Sync servers → Web and mobile apps: New results
- Request / response
- Asynchronous
- Server push
What you need to know
Under snapshot isolation, each transaction reads the database as of when it started, and only conflicts if two transactions write the same row.
A rule that spans several rows (at most five tasks in a column) isn't protected. Two transactions can each read the rows, each decide the rule holds, and each write a different row. Both commit. This is write skew. See Concurrency control.
Think first
The column has 4 tasks, limit 5. Three moves start at once, each counting 4 in its snapshot. Each updates a different task's column. What happens?Optimistic concurrency control checks reads as well as writes: at commit, if anything the transaction read has been written since its snapshot, abort and re-run it. The first move commits; the other two find their read range changed, re-run, count five, and refuse.
The same read sets that power subscriptions make this check possible, which gives serializable mutations without locks.
Check
What's the cost of validating reads at commit?