Skip to content

fix(adonet): align PostgreSQL persistence script with its migration - #11247

Merged
ReubenBond merged 1 commit into
dotnet:mainfrom
ranma42:fix/postgres-persistence-modifiedon-timestamptz
Sep 15, 2026
Merged

ReubenBond merged 1 commit into
dotnet:mainfrom
ranma42:fix/postgres-persistence-modifiedon-timestamptz

Conversation

@ranma42

@ranma42 ranma42 commented Sep 11, 2026 •

Copy link
Copy Markdown
Contributor

Problem

A provider's ADO.NET setup script should produce the same database as a deployment that was created on an older version and then had every migration applied — the principle behind #8896 and #8811, where Migrations/ is an upgrade path and never a required second step for a fresh install.

For PostgreSQL that holds everywhere except OrleansStorage:

  • PostgreSQL-Persistence.sql declares modifiedon timestamp without time zone
  • Migrations/PostgreSQL-Persistence-3.6.0.sql converts it to timestamptz

So a freshly created database and a migrated one end up with different column types.

How it happened: the PostgreSQL migrations were added in f39c0b0 (#7490) to mirror a9bf72a (#7402, "Support Npgsql 6.0"). #7402 only touched the clustering and reminders scripts — persistence was never updated to match, so the persistence migration made a change with no counterpart on the fresh-install path.

The mismatch also leaves migrated databases writing shifted timestamps. After migrating, modifiedon is timestamptz, but the UPDATE branch of writetostorage still writes now() at time zone 'utc' — a bare timestamp that PostgreSQL re-interprets in the session time zone — while the INSERT branch uses a plain now(). On any server whose session time zone isn't UTC, the two branches disagree by the UTC offset. The fresh-install script is internally consistent, so this affects migrated deployments only.

Solution

Move the setup script forward to timestamptz and use now() in both branches, and apply the same now() fix to the migration.

Verification

Tested against PostgreSQL 16 in Docker with the session time zone set to Europe/Rome (UTC+2), building four databases: fresh and migrated, before and after this change.

Schema equality — today's setup script + the fixed migration vs. the new setup script:

  • information_schema.columns for orleansstorage: identical
  • pg_get_functiondef for writetostorage: identical
  • orleansquery rows (key + md5(querytext)): identical

Timestamp drift, measured as stored instant minus true instant across an insert and then an update:

database modifiedon INSERT UPDATE
status quo, fresh timestamp 0s 0s
status quo, migrated timestamptz 0s −7200s
this branch, fresh timestamptz 0s 0s
this branch, migrated timestamptz 0s 0s

−7200s is exactly one CEST offset, reproducing the bug and confirming the fix.

Notes

  • OrleansStorage.modifiedon is never read back by Orleans — no query projects it and no C# accessor reads it (the ModifiedOn in RelationalOrleansQueries belongs to the streaming table). Impact is limited to operator-visible data; this is not a behavioural change for running silos.
  • Existing deployments are unaffected. New installs get timestamptz, which is the shape a migrated database already has.
  • The (now() at time zone 'utc') inside the ReadFromStorageKey query text is deliberately left alone — it is a constant "current UTC time" projected into the SELECT list, not the modifiedon column.
  • Separate pre-existing gap, not addressed here: deployments predating Migrate AdoGrainStorage to the new grain serializer #8081 still carry payloadxml/payloadjson columns, which that PR removed from the setup script without a corresponding migration. Since those columns are nullable and unused, leaving them in place is harmless and dropping them would be destructive — but it does mean "setup script == old + migrations" is not yet exactly true for pre-Migrate AdoGrainStorage to the new grain serializer #8081 databases.

🤖 Generated with Claude Code

Microsoft Reviewers: Open in CodeFlow

@ranma42
ranma42 requested a review from ReubenBond as a code owner September 11, 2026 12:19
The PostgreSQL setup script should produce the same database as a
deployment created on an older version that then had every migration
applied, so that a fresh install never requires a second script (see
dotnet#8896 and dotnet#8811).

For PostgreSQL that already held everywhere except OrleansStorage:
PostgreSQL-Persistence.sql declared `modifiedon timestamp without time
zone`, while the 3.6.0 migration converts it to `timestamptz`, so fresh
and migrated databases ended up with different column types.

The migrations were added in f39c0b0 to mirror a9bf72a ("Support
Npgsql 6.0"), but that change only touched the clustering and reminders
scripts - persistence was never updated to match.

The mismatch also left the migrated function writing shifted values: with
`modifiedon` converted to `timestamptz`, the UPDATE branch of
writetostorage still wrote `now() at time zone 'utc'`, a bare timestamp
that PostgreSQL re-interprets in the session time zone, while the INSERT
branch used a plain `now()`. On any non-UTC server the two branches
disagreed.

Move the setup script forward to `timestamptz` and use `now()` in both
branches, and apply the same `now()` fix to the migration. The two
writetostorage bodies are now identical apart from CREATE OR REPLACE,
matching the clustering and reminders scripts.

`OrleansStorage.modifiedon` is never read back by Orleans, so this
affects operator-visible data only.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@ReubenBond
ReubenBond force-pushed the fix/postgres-persistence-modifiedon-timestamptz branch from e50f035 to d7cde5b Compare September 13, 2026 14:58
@ReubenBond
ReubenBond requested a balanced review from Copilot September 14, 2026 14:12

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

A new migration is needed to repair databases that already applied the released 3.6.0 migration.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Aligns fresh PostgreSQL persistence installations with the migration schema and corrects timezone-shifted modifiedon writes.

Changes:

  • Changes modifiedon to timestamptz.
  • Uses now() consistently for inserts and updates.
  • Updates the existing 3.6.0 migration function.
File summaries
File Description
PostgreSQL-Persistence.sql Aligns fresh-install schema and timestamp writes.
Migrations/PostgreSQL-Persistence-3.6.0.sql Corrects the migrated write function.
Review details
  • Files reviewed: 2/2 changed files
  • Comments generated: 1
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@ReubenBond
ReubenBond merged commit 5a63e15 into dotnet:main Sep 15, 2026
66 of 67 checks passed
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.

3 participants