Repository navigation
feat: opt-in PostgreSQL metastore with import, export and verify tooling - #148
Merged
Merged
Conversation
Adds a PostgreSQL implementation of the Metastore trait, selected with P_METASTORE_POSTGRES_URL. Object storage remains the default metastore and needs no new configuration. In PostgreSQL mode metadata documents live in a parseable_documents table; parquet data stays in the bucket, paired to the database by a marker object. - Shared document layer for both backends; native row versions and atomic conditional writes; embedded migrations under an advisory lock. - Stream lifecycle in PostgreSQL mode: durable deleting marker, crash-safe delete ordering, creation refused while a deletion is pending. - Client-side deadlines on every SQL call, tenant-scoped key-only listings, C-collated keys so prefix reads use the index, batched manifest reads. - Offline --metadata-migration import|verify|export with abort, import-time digests, and startup guards for partially migrated state. - Contract suite run against both backends, fault and restart tests, a stronger metastore boundary guard, end-to-end scripts and a CI workflow. Also fixes four defects that exist in object-store mode: target tenant lost on reload, alert listing including MTTR history, local-filesystem listing ignoring the selected instance, and startup tenant cleanup deleting before discovery had fully succeeded. Squashed from local development branches. Machine-specific paths and host names in the generated evidence files were redacted. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
The probe retried only refused connections. While MinIO is still starting, the published port accepts and then resets the connection, which curl reports as error 56 and does not retry without --retry-all-errors. The first hosted run of this workflow that got past setup failed there. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
7 tasks
…dary' into feat/postgres-metastore
…tore The cache branch made the object-store stream listing probe datasets sixteen at a time. That code now lives in the shared document metastore macro, so the bounded probe and its helper move there, and the two tests the cache branch added move into the object-store metastore tests. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
VibeSWE
changed the base branch from
refactor/metastore-metadata-boundary
to
main
November 11, 2026 20:00
VibeSWE
marked this pull request as ready for review
November 11, 2026 20:16
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.
Stacked on #146, which this PR's base branch contains. Closes #144. Refs #141, #142, #143, #145.
Problem
All of Parseable's metadata (
stream.jsonwith its snapshot, manifests, schemas, RBAC, alerts, filters, dashboards, the node registry) lives as JSON objects in the object-store bucket. Two consequences:Change
An opt-in PostgreSQL implementation of the
Metastoretrait. Object storage remains the default metastore and needs no new configuration. WithP_METASTORE_POSTGRES_URLset, metadata documents live in aparseable_documentstable and Parquet data stays in the bucket. A marker object,.parseable/postgres-metastore-id, pairs a bucket with a database.Backend
mainand is identical apart from the wrapper.COLLATE "C"with explicit range predicates, so prefix reads use the primary key. Listings are tenant-scoped and key-only. Manifests for a query are fetched in batched statements bounded by count and bytes, through the manifest cache.Stream lifecycle in PostgreSQL mode
Offline migration
--overwrite-bucket-changes-since-import, and every conflict is listed first.export --dry-runruns all of these checks read-only, so a rollback can be verified right after an import.--abortexists for both, refused once something irreversible has happened. A transfer and an abort share a session-level advisory lock, so a second invocation fails fast. Database and pairing checks run before any bucket inventory, and--dry-runperforms them read-only.P_ALLOW_PAIRED_BUCKETis set to the pairing id.Settings
All prefixed
P_METASTORE_POSTGRES_:URLPOOL_SIZECONNECT_TIMEOUT_MSSTATEMENT_TIMEOUT_MSCAIDMIGRATION_TIMEOUT_MSALLOW_UNPAIREDTLS is required unless the URL says
sslmode=disable.Behaviour changes in default (object-store) mode
Four defects found by the new contract tests exist on
mainand are fixed here:tenantfield but read back expectingtenantId, so the tenant was lost on reload. They now writetenantId; documents with the old field still load.mttr.json.Further default-mode changes from the second review round: tenant normalisation now covers metadata paths (a
DEFAULT_TENANTalias and no tenant resolve to the same keys), default alert-state enumeration is corrected, and explicit-stream ingest propagates a lookup error instead of reporting 404.Also in default mode: two extra GETs at startup (the pairing and migration marker probes), and the PostgreSQL client is compiled into every build (no cargo feature).
Effect on the query path
On local emulators, a repeated query makes one object-store request in PostgreSQL mode where the default mode makes four to nine, depending on topology: the metadata reads are gone and the Parquet read remains. This has been counted in requests, not yet measured as latency against a distant bucket.
Not in this PR
Tests
docs/validation/holds their generated evidence; machine-specific paths and host names in those files were redacted.docs/validation/postgres-review-explain.txt..github/workflows/postgres-metastore.yaml, runs the PostgreSQL tests and both scripts and fails if they were skipped. This PR is its first run.Review status
Draft. The first backend commits were read by four independent reviewers and the import tool by one; their findings were addressed in the first commit of this PR. That commit was then reviewed again by two reviewers (three major and three minor findings on the backend; three major and six minor on the import and export tool; verdict that
importis safe on a real deployment andexportwas not yet safe as a rollback). The six commits on top address all of those. They have not had an independent review, hosted CI has not yet produced a Rust result for this branch, and the tool's throughput at production scale is unmeasured.Operator documentation, including the step-by-step migration and rollback procedure:
docs/postgres-metastore.md.🤖 Generated with Claude Code