pgrust
Applicationmalisper/pgrust
A Postgres rewrite in Rust, passing 100% of regression tests.
Overview
pgrust is a rewritten PostgreSQL database system in Rust, targeting compatibility with Postgres 18.3. It passes over 46,000 regression queries and is disk-compatible with existing Postgres data directories. The project aims to make Postgres easier to modify using Rust and AI-assisted development.
README Preview
pgrust\n\n\n A Postgres rewrite in Rust.\n\n\n\n \n \n \n \n \n\n\n\n Browser demo\n | \n Discord\n | \n Get pgrust updates\n | \n Issues\n\n\n\n\npgrust targets compatibility with Postgres 18.3 and matches Postgres's\nexpected output across more than 46,000 regression queries.\n\npgrust is disk compatible with Postgres and can boot from an existing Postgres\n18.3 data directory.\n\nThe goal is to make Postgres easier to change from the inside: keep the behavior\nPostgres-shaped, keep the real Postgres tests as the oracle, and use Rust plus\nAI-assisted programming to explore deeper server changes.\n\nUpdate: We're working on a new not yet published version of pgrust that currently passes 100% of Postgres regression suite, has a thread per connection model instead of process per connection, is 50% faster than Postgres on transaction workloads, and is ~300x faster than Postgres on analytical workloads (2x slower than Clickhouse on clickbench and we think it can get faster than Clickhouse). Follow pgrust or join our Discord for updates!\n\n## Follow pgrust\n\n[Get project updates by email](https://pgrust.com/#updates), including new\nreleases, compatibility milestones, and architecture experiments.\n\n## Status\n\npgrust is not production-ready yet. It is not performance optimized yet.\n\nExisting Postgres extensions and procedural language extensions such as\nPL/Python, PL/Perl, and PL/Tcl are not generally compatible yet. Some bundled\ncontrib modules are already ported, and more compatibility may be possible over\ntime.\n\n## Roadmap\n\n- multithreaded Postgres internals\n- built-in connection pooling\n- better JSON-heavy workload support\n- fast forking and branching workflows\n- storage experiments, including no-vacuum designs\n- runtime guardrails for bad queries and AI-generated SQL\n- fewer sudden bad plan switches\n\n## Try It\n\nTry the WebAssembly demo at https://pgrust.com.
FAQ (5)
TroubleshootingHow to fix DROP TABLE hanging at 100% CPU after crash recovery in pgrust?
This is caused by a leaked buffer pin from WAL redo: the recovery prefetcher's io_uring lane leaves the last prefetched page pinned in the startup thread's ring slot. InvalidateBuffer then spins on that foreign pin and cannot be cancelled.
Fix: upgrade pgrust to a build containing PR #2062, commit 4f84297963e (xlogrecovery: drain prefetch pins after the redo loop). The patch drains the prefetch ring immediately after the redo loop and before the end-of-recovery checkpoint, and makes the foreign-pin wait cancellable. Build and restart with:
git fetch origin
git checkout 4f84297963e # or any later commit containing the fix
cargo build --release --locked --bin postgres
Then restart the server and retry DROP TABLE. After recovery, verify the relation has no leaked pin with pinning_backends = 0 in pg_buffercache.
Temporary mitigation on vulnerable builds: run CHECKPOINT after large inserts before a planned stop, so the pages are not replayed solely from WAL during recovery. This does not protect against an unexpected crash. If a backend is already spinning, pg_cancel_backend will not stop it; restart with the patched build.
TroubleshootingHow do I fix "T_NextValueExpr deparse arm unported" when running EXPLAIN VERBOSE on an INSERT into a PostgreSQL IDENTITY column table in pgrust?
This affects pgrust 0.2 when EXPLAIN VERBOSE deparses the plan for a GENERATED ALWAYS AS IDENTITY column. Immediate workaround: run EXPLAIN without VERBOSE, for example:
EXPLAIN INSERT INTO t (v) VALUES (1);
You can also omit VERBOSE when using other options:
EXPLAIN (ANALYZE, BUFFERS) INSERT INTO t (v) VALUES (1);
The plan will print, but the verbose Output: line containing the identity sequence's nextval(...) expression is omitted.
Permanent fix: upgrade pgrust to a build containing PR #472 (fixed on main, 2026-08-08). The fix deparses NextValueExpr as nextval('seq'), matching PostgreSQL 18.6. serial columns are not affected because they use the normal DEFAULT nextval(...) path.
TroubleshootingHow do I fix "funcapi exprType: node family T_NullIfExpr not ported" when passing NULLIF, GREATEST, IS NULL, or AND to format() in pgrust?
Upgrade pgrust to a current main build containing commit 77ab13c454b. The fix makes funcapi delegate argument type resolution to nodes_core::node_funcs::expr_type, covering T_NullIfExpr, T_MinMaxExpr, T_NullTest, T_BoolExpr, T_DistinctExpr, T_BooleanTest, T_ScalarArrayOpExpr, T_SubPlan, and related expression families. This affects pgrust 0.2 with PostgreSQL 18.3-compatible code and functions using runtime-resolved "any" or VARIADIC "any" arguments, such as format(), concat(), concat_ws(), and json_build_object(). If upgrading immediately is not possible, evaluate the problematic expression in a materialized CTE or subquery and pass the resulting column/Var to the function instead of the raw expression.
TroubleshootingHow to fix the "SERIALIZABLEXACT slots exhausted and the pg_serial summarization path is not ported" panic when starting SERIALIZABLE transactions?
This panic occurs when the fixed SERIALIZABLEXACT pool, sized (MaxBackends + max_prepared_xacts) * 10, is full. In affected pgrust builds, pg_serial SLRU summarization was not implemented, so GetSerializableTransactionSnapshot panicked instead of recycling the oldest committed serializable transaction.
The fix is to upgrade to a current main build containing PR #168. It ports the real pg_serial SLRU and makes GetSerializableTransactionSnapshotInt loop through SummarizeOldestCommittedSxact plus CreatePredXact, matching PostgreSQL behavior. SerialAdd, SerialSetActiveSerXmin, and CheckPointPredicate are also implemented.
Steps:
1. Fetch and build a main commit containing PR #168.
2. Redeploy and restart the database.
3. Optionally verify with scripts/ssi-sxact-exhaustion-e2e.sh; the regression test exhausts the slot pool, runs CHECKPOINT, and confirms there is no panic and the server remains healthy.
Temporary mitigations on older builds:
- Reduce concurrency or commit rate of SERIALIZABLE transactions.
- Avoid long-running SERIALIZABLE sessions that pin SxactGlobalXmin.
- If safe, increase max_prepared_transactions or MaxBackends to raise the pool threshold, then restart.
- Move high-churn transactions to READ COMMITTED only if their correctness requirements allow it; this weakens serializable guarantees.
TroubleshootingHow to fix the dead_items_info.num_items double-count or debug assertion failure in pgrust parallel/morsel VACUUM after a coverage-guard trip?
This bug affects the experimental parallel/morsel VACUUM path enabled with PGRUST_RUNTIME_VACUUM=1. When the coverage guard rewound resume_g, the leader could process deferred-cleanup pages above the resume point before the serial arm re-scanned them. dead_items_add() replaced the block's TIDs but still incremented dead_items_info.num_items again, causing a double count and tripping debug_assert_eq!(tids.len() as i64, vacrel.dead_items_info.num_items) in debug builds.
Fix: update to a pgrust build containing PR #469/current main. The fix makes round_drain_plan clamp leader-deferred cleanup pages to below the rewound resume block, ensuring each page is drained by only one arm. Regression tests include morsel_coverage_trip_counts_each_dead_item_once and morsel_verified_round_drains_all_runs_and_deferred.
Temporary workaround: disable the experimental parallel vacuum path:
unset PGRUST_RUNTIME_VACUUM
# or
PGRUST_RUNTIME_VACUUM=0
Do not enable test fault injection such as PGRUST_RUNTIME_VACUUM_FAULT=lose-local in production.