Skip to content

fix: split temporal periods without FOR PORTION OF, on PostgreSQL 18 - #879

Closed
zachdaniel wants to merge 1 commit into
mainfrom
temporal-without-fpo
Closed

zachdaniel wants to merge 1 commit into
mainfrom
temporal-without-fpo

Conversation

@zachdaniel

Copy link
Copy Markdown
Contributor

PostgreSQL reverted UPDATE/DELETE ... FOR PORTION OF from 19 before release (a4b26b8f7cd, discussion): concurrent writes under READ COMMITTED could silently lose part of an update. Temporal writes relied on it.

Splitting. Each temporal write is one statement that writes the slice it covers and puts back the rest of the version as new rows, using PG18's RETURNING old. Temporal resources now need PostgreSQL 18, not 19. Once PostgreSQL ships native FOR PORTION OF, we'll use it.

Concurrency. Each statement locks the versions it's about to split and compares them with what its snapshot matched. If a concurrent write changed one, the statement writes nothing and reruns, inside a transaction so the locks it took are kept. Upserts that race to insert the same record skip on the WITHOUT OVERLAPS primary key (ON CONFLICT ON CONSTRAINT <pkey> DO NOTHING) and retry just that key. Identity violations still raise. After 25 conflicts, AshPostgres.Temporal.WriteConflict. No savepoints.

Migrations. Temporal DDL is emitted unconditionally instead of behind server_version_num >= 190000 guards with plain-key fallbacks. VerifyTemporal requires the repo's min_pg_version/0 to be at least 18.

Also:

  • temporal upsert errors now go through handle_raised_error (identity violations were Ash.Error.Unknown)
  • CI on main has failed since the temporal commit: the test repo port was hardcoded to 5433. It's PG_PORT now, default 5432. Plus format fixes.
  • temporal test resources moved to AshPostgres.TemporalTestRepo (min_pg_version 18), only migrated and started on PG18+, so the PG14-17 jobs still compile
  • new docs topic: Temporal Resources

Tests: new test/temporal_concurrency_test.exs runs real concurrent transactions (split, end, bulk, upsert race, partial bulk-upsert race). All fail without the conflict gate. Locally: PG18 full suite and a PG17 run pass, except 3 unrelated sort tests that also fail on main. Not re-run on PG19 beta since the test-repo restructure.

Upgrade note: migrations generated before this created plain keys on PostgreSQL 18. The new docs page explains how to check and fix.

PostgreSQL reverted UPDATE/DELETE FOR PORTION OF from 19 (a4b26b8f7cd),
because concurrent writes under READ COMMITTED could lose updates. Temporal
writes now split versions themselves in one statement, using PG18's
RETURNING old, and lock the versions they split first: if a concurrent
write changed one, the statement writes nothing and reruns. Upserts that
race to insert the same record skip on the WITHOUT OVERLAPS primary key and
retry that key. After 25 conflicts, AshPostgres.Temporal.WriteConflict.

- migrations emit temporal DDL unconditionally; VerifyTemporal requires the
  repo's min_pg_version/0 to be at least 18
- temporal upsert errors go through handle_raised_error
- test suite: temporal resources on AshPostgres.TemporalTestRepo, only
  migrated on PG18+; test repo port is PG_PORT (CI broke on 5433)
- docs: temporal resources topic
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant