Concurrency
Locks, what a save merges, the order a save writes in, and how the registry is repaired.
On this page
Saving
save() runs under $app->lock(), keyed by the record, and merges against what is on disk once the lock is taken. Concurrent saves of different keys on one page all land. A read followed by a write of the same key, such as a counter or a seat count, needs your own lock around both:
use Phlat\Exceptions\ConflictException;
$app->lock("seats:{$page->uuid}", function () use ($page) {
if ($page->registrations->count() >= 40)
throw new ConflictException('Sold out');
$page->registrations->new(['name' => $name]);
});
A lock that can't be taken within the timeout (10 seconds by default) is a 503. Locks are per host.
A page or user is created through Record::create() under a lock, so two creators of the same uuid or slug can't both win. A collection item is not under $app->lock(): each create, save and delete is one transaction in the collection's database, which takes the write lock as it begins.
Sessions
A request reads the session once and releases its lock straight away, so a slow request never holds the visitor's others. A write through Ses, $page->session() or $app->session->scope() reopens the session, changes one key and closes it, merging what concurrent requests wrote in between. Write nothing to $_SESSION directly: it is a snapshot and the change is lost. Recording activity is the one write most requests would make, so it happens at most once a minute.
Write order
A page save writes _index.json, then _data.json, then the registry row and its field and search rows in one transaction. There are no multi-page transactions. _index.json goes first because its updated is how drift is found: a crash before _data.json leaves updated ahead of the registry, so the next visit re-indexes the page.
Repairing the registry
The registry is an index of files that are the truth. Visiting a page compares its updated with the registry row and re-indexes a stale one. For the whole site:
phlat registry:check # report pages that drifted
phlat registry:check --heal # re-index only those
phlat registry:rebuild # redo everything
A full rebuild holds the registry:rebuild lock and marks the registry unfinished until it ends. Every other request gets a 503 with Retry-After, and a rebuild that dies is retried by the next request.