Repository navigation
The store's TimescaleDB moves to 2.30.1 before the store opens, closing GHSA-hcfx-29v5-2rcw, with nothing ever reverted and its own alert (#3908) - #3948
Merged
Conversation
…rst primitive, quiesced update core, realistic fixture Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SFctxdyLSdZbycGtvwXk8j
…no revert, separate alert, bridge skip, rollback guard, orphan recognition Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SFctxdyLSdZbycGtvwXk8j
…db-store-upgrade # Conflicts: # Darling/Darling.Tests/StartupCommandTimeoutTests.cs
…lizer braces for the member scanner Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SFctxdyLSdZbycGtvwXk8j
…ut back (#3951's note, after #3927) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SFctxdyLSdZbycGtvwXk8j
…r pin, a truly unstamped run, and nine smaller fixes - CaptureDownChunkOrderTests accepts TimescaleDB 2.30's DeferredChunkAppend shape (one visited chunk per arm), which failed CI's PG shard 1 on the new runtime; verified on 18.6+2.30.1 and 18.4+2.28.1. - The same-major test's unstamped run deletes both stamps and asserts the no-stamp branch; the stamped run is the field's legacy-stamp state. - A bootstrap retry keeps the outcome of the attempt that ran the update. - An absent extension is recorded under the runtime version it was read under, so no store pays the extra start and stop on every start, and a fresh install's second start pays none. - A first extraction refuses a package that cannot open the recorded TimescaleDB (clean reinstall), sharing the swap path's check; README says keep pg-runtime when rolling back. - The quiesced step is gated on the data directory's major matching the runtime's, keeps the port inside its never-throws contract, logs a failed start and a failure that landed, and quotes the newest server log. - The pg_upgrade old-cluster start runs quiesced, and the bridge targets only the old runtime's own default. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SFctxdyLSdZbycGtvwXk8j
erikdarlingdata
enabled auto-merge (squash)
September 23, 2026 01:15
This was referenced Sep 23, 2026
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.
Closes #3908. Closes #3951:
CompleteAfterStartAsyncnow returns any outcome that is not a successful major upgrade untouched, so a Failed outcome can no longer becomeExtensionUpdatedand lose its alert (that status is gone).Problem
Every release since 3.2 shipped TimescaleDB 2.28.1, and GHSA-hcfx-29v5-2rcw covers it. Bumping the bundle was blocked because nothing could move an existing store off 2.28.1 safely:
ALTER EXTENSION timescaledb UPDATEinCompleteAfterStartAsyncran after the web host, MCP host, Viewer seats and the job scheduler had all connected. Measured: once the extension moved, every session that had loaded the old library failed its next statement, parallel queries failed, and the update's pre-step killed running jobs, which the scheduler then held in crash backoff for up to an hour.What changed
The bundle moves to TimescaleDB 2.30.1 and carries 2.28.1's two versioned libraries.
fetch-pg-runtime.ps1adds$tsCarriedwith pinned SHA256s and sanity-checks the result. A version counts as carried only when both its DLLs are present, because the TSL one holds compression and continuous aggregates.The quiesced update runs before the store opens. It is
DarlingStoreUpgrade.UpdateTimescaleQuiescedAsync, called fromEnsureRunningAsyncafter the conf append and before the network plan and the start. For each database that allows connections, it:timescaledb.max_background_workers=0andautovacuum=off;timescaledb.disable_load=on);The cluster is then stopped, and the stop is confirmed (fast, then immediate, then
pg_ctl statusmust answer 3). Only the connection is ever retried; the ALTER is sent once.Nothing is ever reverted. Every store this service has shipped is on 2.28.1, which the runtime carries, so a failed update leaves the store opening on its own version. That removes the reviewer's blocking concerns about reverting past a commit point.
The gate is a record in the data directory.
darling-timescaledb.versionholds either the version as last read after start, orpending <from> <to>written before the ALTER. The update runs when the record is missing, pending, or not the runtime's version. There is deliberately no "absent" record: the first start of a new store reads it seconds before the worker creates the extension, and that stale record skipped the update after a runtime change. The gated tests caught it.The post-start ALTER is retired.
CompleteAfterStartAsyncis verify-and-analyze only. The newVerifyTimescaleAfterStartAsyncreads the version, records it, and settles the outcome:Updated,Failed,Behind, or none.A separate alert.
StoreTimescaleReport/EvaluateStoreTimescaleAsyncfires Critical under the sameStore Runtime Upgrademetric with its own key (storetimescale). A start that upgrades the major and then cannot move the extension therefore raises both alerts. An update that landed stays log-only, like the old extension-only outcome, and theExtensionUpdatedstatus is gone. The major-upgrade alert's TimescaleDB versions now say where the extension ended up, and leave it out when it did not move, so the two alerts cannot disagree.The pg_upgrade bridge skips what the new runtime carries.
PlanTimescaleBridgemakes that decision, so every shipped PostgreSQL 17 store is restored at 2.28.1. The quiesced update moves it after the directory swap commits, so a failure can no longer cost the rollback copy; that is the reviewer's link-mode finding. For a version the new runtime does not carry, the bridge targets the newest version both runtimes carry, ALTER-first.Rollback guards:
PinLegacyRuntimeStampkeepspg-runtime.stampat the 3.3-3.8 package hash, so a rollback to those releases keeps this runtime. This release's own stamp is nowpg-runtime.sha256.PackageCannotLoadStorerefuses to swap in a package with no libraries for the recorded version. That is a rollback past this release.Orphan recognition. A server the update left on its private port is stopped before the runtime advance and before the normal start. It is identified by
darling-timescaledb-update.portmatching the port inpostmaster.pid; any other running server is adopted as before.StartClusterAsynctakes a wait and quotes the server log tail on failure.check-runtime-currency.ps1drops the GHSA-hcfx-29v5-2rcw acknowledgement. The check now reports 2.30.1 clear of all four published advisories, and demanded the deletion.Tests
Updated, record written, no marker left.2.28.1--2.30.1script:Failedand the alert report is built;TryReadPostmasterPort, the orphan rule without a server, the rollback guard both ways, the report mapping, the alert text, and a wiring-order pin.Adversarial review round (after the first push)
CaptureDownChunkOrderTestsassumed ChunkAppend's plan. TimescaleDB 2.30 plans the same read as DeferredChunkAppend by default, which lists only the chunks it visited ("Chunks Visited: 1"). The pin now accepts either shape: one visited chunk per arm, or eight or more scans with at most one executed per arm. Verified on throwaway 18.6 + 2.30.1 and 18.4 + 2.28.1 clusters.MissingTimescaleLibrarieswith the swap guard. The README says to keeppg-runtimewhen rolling back.FindFreeLoopbackPortinside its never-throws contract;Guard edits (the kind each guard asks to have named)
StartupCommandTimeoutTests:BridgeTimescaleAsyncis now 1/0/0/0 andCompleteAfterStartAsync2/0/0/0.UpdateTimescaleAsFirstStatementAsync0/0/0/1,ReadExtversionAsync1/0/0/0,ListConnectableDatabasesAsync1/0/0/0.AlertReadFailureSurfaceTests: one exemption, the TimescaleDB self-alert's catch, whose evidence is a parameter. The evaluator's exempt count is now 13 and the tree total 27.Related, found while building this
Field validation
DARLING01, a production-shaped store with 1,024 chunks, 26 continuous aggregates and 148 jobs: 18.4 + 2.28.1 moved to 18.6 + 2.30.1. The extension update took about 4 s on a private port, and collection resumed 31 s after the runtime swap began. Every count is identical, all TimescaleDB jobs succeeded on 2.30.1, and the stamps, record and marker are exactly as designed.
No CHANGELOG edit here; the entry goes through the batched splice.
🤖 Generated with Claude Code
https://claude.ai/code/session_01SFctxdyLSdZbycGtvwXk8j