genesis-compiler 1.10.22 → 1.10.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -86,10 +86,10 @@ Read the engineering approach's rules on query counts and realistic tests, and
|
|
|
86
86
|
these sections of the sanity checks guide
|
|
87
87
|
(`.agents/skills/genesis-project/references/sanity-checks.md`): "Never do
|
|
88
88
|
these", "Data: one owner per table", "Data: one source for each fact", "Data:
|
|
89
|
-
loading a dataset", "Data: each read asks for the rows it uses", "Data:
|
|
90
|
-
|
|
91
|
-
looks like; this pass applies them across the whole scope
|
|
92
|
-
find.
|
|
89
|
+
loading a dataset", "Data: each read asks for the rows it uses", "Data: batches
|
|
90
|
+
and background work", "Data: two changes at once" and "Inputs and access". They
|
|
91
|
+
describe what good looks like; this pass applies them across the whole scope
|
|
92
|
+
and fixes what they find.
|
|
93
93
|
|
|
94
94
|
Then list, for the scope: each table, collection or store with its one owner
|
|
95
95
|
(`genesis/subsystems.md` records data ownership), who reads and writes it, its
|
|
@@ -173,35 +173,75 @@ one matching, and see it found.
|
|
|
173
173
|
|
|
174
174
|
**Outer limit:** a capped read narrowed in code is broken at any size.
|
|
175
175
|
|
|
176
|
-
## Data:
|
|
176
|
+
## Data: batches and background work
|
|
177
177
|
|
|
178
178
|
**Good:** a batch writes each table in a few statements inside the owner's
|
|
179
179
|
transaction, not one full create per row. Work nobody waits for runs as a
|
|
180
180
|
scheduled or queued job in bounded chunks: it loops over units of work (a
|
|
181
181
|
tenant, a user), one transaction each, never one around the whole run and
|
|
182
|
-
never one per row. A read request takes no lock and writes nothing.
|
|
183
|
-
takes its locks in one order, parents before children, so two transactions
|
|
184
|
-
never wait on each other. Two writers on the same records never produce a
|
|
185
|
-
server error.
|
|
182
|
+
never one per row. A read request takes no lock and writes nothing.
|
|
186
183
|
|
|
187
184
|
**Check:** count a batch's statements at 10 items and at 500. Look for due work
|
|
188
185
|
run inside whichever request comes first, and one long transaction around it.
|
|
189
186
|
Find any lock or write a read path takes for one-time setup: read first, lock
|
|
190
|
-
only on the rare unset path.
|
|
191
|
-
Run two writers on the same records; a retried transaction must be safe to
|
|
192
|
-
repeat, so HTTP calls, email and uploads go before or after it, or into an
|
|
193
|
-
outbox row written inside it.
|
|
187
|
+
only on the rare unset path.
|
|
194
188
|
|
|
195
189
|
**Examples:**
|
|
196
190
|
- An import created each row through the full single-record create path, with
|
|
197
191
|
its lookups and read-back, one row at a time.
|
|
198
192
|
- Overdue work ran inside the first user request after a quiet period, so that
|
|
199
193
|
request's time depended on how long nobody had used the app.
|
|
200
|
-
- Concurrent saves surfaced database deadlocks as 500s.
|
|
201
194
|
|
|
202
195
|
**Outer limit:** thousands of queries or tens of seconds for one action, or a
|
|
203
196
|
request whose time depends on how long since anyone used the app.
|
|
204
197
|
|
|
198
|
+
## Data: two changes at once
|
|
199
|
+
|
|
200
|
+
**Good:** each write uses the lightest tool that keeps the stored data right.
|
|
201
|
+
Most need nothing beyond the transaction they already run in:
|
|
202
|
+
- An ordinary edit (a form saving a record, a name or a description, settings,
|
|
203
|
+
a saved game) is a plain write, and the last save wins: when two people edit
|
|
204
|
+
the same thing at the same moment, one change replaces the other. That is
|
|
205
|
+
usually acceptable, and it needs no lock and no concurrency test. Where
|
|
206
|
+
people would care, the save can carry the version it read and refuse when
|
|
207
|
+
the record has moved on, saying that someone else changed it and to reload.
|
|
208
|
+
- A change allowed only while a value still holds (open to booked, a reward
|
|
209
|
+
claimed once) is a conditional update, a compare-and-set: of two writers one
|
|
210
|
+
wins and the other gets a clear refusal, with no lock.
|
|
211
|
+
- A value that must be unique gets a unique index or constraint, and an
|
|
212
|
+
insert that loses that race is retried where the tools offer it, or refused
|
|
213
|
+
clearly.
|
|
214
|
+
- An explicit lock is for a race that would silently leave wrong data that
|
|
215
|
+
matters: stock or quantities, money or balances, sequential document
|
|
216
|
+
numbers, a capacity limit (two bookings of the last place), or a
|
|
217
|
+
check-then-act on a shared resource (is the drier empty? then load it).
|
|
218
|
+
|
|
219
|
+
A lock makes other writers wait, can deadlock and needs a test with two
|
|
220
|
+
writers, so it usually pays only where you can name the wrong data a race
|
|
221
|
+
would leave. Whatever the tool, a lost race never becomes a server error: the
|
|
222
|
+
loser gets a refusal it can act on, or its work runs again. Take locks in one
|
|
223
|
+
order on every path, parents before children, so two transactions never wait
|
|
224
|
+
on each other. Work that may run again must be safe to repeat, so HTTP calls,
|
|
225
|
+
email and uploads go before or after the transaction, or into an outbox row
|
|
226
|
+
written inside it.
|
|
227
|
+
|
|
228
|
+
**Check:** for each write path, name its tool and why: for a lock, the wrong
|
|
229
|
+
data a race would leave; for a plain write, why the last save winning is
|
|
230
|
+
acceptable. Run two writers at once on each path that locks, writes
|
|
231
|
+
conditionally or relies on a unique key, and see one win and the other refused
|
|
232
|
+
or run again, never a server error and never both applied. Compare the order in
|
|
233
|
+
which each path locks rows. An ordinary edit needs neither a lock nor a
|
|
234
|
+
two-writer test.
|
|
235
|
+
|
|
236
|
+
**Examples:**
|
|
237
|
+
- Concurrent saves surfaced database deadlocks as 500s.
|
|
238
|
+
- A business app locked the rows of every save, names and settings included,
|
|
239
|
+
and spent hours on lock order, deadlocks and two-writer tests for edits
|
|
240
|
+
where the last save winning was fine.
|
|
241
|
+
|
|
242
|
+
**Outer limit:** a lost race that ends in a server error, or that leaves stock,
|
|
243
|
+
money, numbering or a capacity limit silently wrong.
|
|
244
|
+
|
|
205
245
|
## Work that repeats or lingers
|
|
206
246
|
|
|
207
247
|
**Good:** work whose answer does not change between calls runs once and is
|
|
@@ -440,8 +480,9 @@ hundreds of requests on a first screen.
|
|
|
440
480
|
|
|
441
481
|
**Good:** the same state twice gives the same result. Each fact has one
|
|
442
482
|
resolver. Each status names what clears it, and each gate has a case that must
|
|
443
|
-
fail it.
|
|
444
|
-
|
|
483
|
+
fail it. A step that moves a status writes only while the status it read
|
|
484
|
+
still holds, so of two at once one wins and the other is refused. A delete
|
|
485
|
+
removes what it created elsewhere. Readiness is checked directly.
|
|
445
486
|
|
|
446
487
|
**Check:** find every place that resolves the same fact. Check that what a
|
|
447
488
|
delete created outside its table is gone. When a rule's result breaks another
|