Keys the chart lock by store - #246
Merged
Merged
Conversation
The per-hash advisory lock's second key now hashes the store's identity with the content hash: the chart schema's table under its prefix, each a quoted identifier (chart_store/1). Two stores in one database no longer wait on each other for a hash they share; two host modules on one physical charts table are one store and keep sharing the key. The first key stays @chart_lock_namespace. The unit is the store and never the tenant, ruled by the operator, 2026-09-29: the charts table's unique index is on content_hash alone, so tenants sharing one table race on one tombstone row. Records the key in a dated Amendment at proposed on ADR-0012, and pins it with two live Postgres race cases. Refs: sp-j6vh
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
The Ecto adapter's per-chart advisory lock on Postgres (a shared lock for a tombstone read, an exclusive one for a retirement) is now keyed by the store as well as the content hash. The second key is
hashtext(store <> " " <> content_hash), where the store is the chart schema's table under its prefix, each written as a double-quoted identifier (chart_store/1inlib/statifier_persistence/storage/ecto.ex, read the waysupports_chart_retirement?/1reads them). The first key stays@chart_lock_namespace, so the upgrading page's first-key sentence still holds.Two stores in one database no longer wait on each other for a hash they share. Two host modules on one physical charts table are one store and keep sharing the key.
The unit is the store, never the tenant, ruled by the operator, 2026-09-29: beside its primary key, the charts table's unique index is on
content_hashalone (V01), so tenants sharing one charts table race on one tombstone row and must keep sharing the per-hash lock.Changes
lib/statifier_persistence/storage/ecto.ex:chart_lock/3passes the store identity with the content hash; new privatechart_store/1andquote_identifier/1.test/statifier_persistence/ecto/retire_chart_race_test.exs: two live Postgres cases. "two stores in one database retire one hash without either waiting" (theScopedandSharedIdScopedfixture stores: one table name under two prefixes); "two host modules on one charts table still share the hash's lock" (DefaultandBigserialon one table: a tombstone read through one waits for a retirement held through the other). The existing cases in the file are unchanged.docs/adr/0012-retention-and-retirement.md: a dated Amendment at proposed, appended at the foot (zero removed lines): the key, its unit, why a per-tenant key is refused, the prefix-freesearch_pathedge. It adds to the 2026-09-29 Note's "The key is database-wide" sentence without removing it.changelog.d/sp-j6vh.md: under Changed.Callers of the lock body (read, not written)
statifier_oban calls none of
fetch_retired_info/2,retire_chart/3orcheck_chart_retired/2. statifier_router reaches the lock only throughStatifierPersistence.Executions.create/4(persistence_create/4in its delivery module), inside one store, where the key changes no wait and no answer.Provenance
Sabotage
chart_lock/3with the content hash alone as the second key's text: red, "two stores in one database retire one hash without either waiting" (Task.yieldanswered nil).chart_store/1with the prefix dropped: red, the same case.chart_store/1appending the schema module's name to a prefix-free identity: red, "two host modules on one charts table still share the hash's lock" (the read did not wait).Each reverted from a copy, byte-equal.
Gate
Full
mix qualityon the committed tree, quoted whole from the gate's own output:Refs: sp-j6vh