Skip to content

PATTERN Cited by 1 source

Postgres extension over fork

Pattern

When building a database product on top of Postgres — even one that replaces core subsystems (replication, concurrency control, durability, storage, session management) — extend Postgres through its public extension API rather than forking the codebase.

This works because Postgres was designed from the outset as an extensible database system. Extensions:

  • Run in the Postgres process.
  • Live in separate files and packages, not mixed into core.
  • Are part of Postgres's documented public API surface.

Consequence: extension code can change behavior without altering core, and the host product benefits from upstream Postgres releases / bugfixes / performance improvements for free.

The anti-pattern: hard fork

Forking a 1986-vintage, >1M-line, thousands-of-contributors, continuously-developed codebase has a predictable trajectory:

  • Starts with best intentions.
  • Initial merges from upstream stay cheap.
  • Every custom change raises the merge cost of the next upstream sync.
  • Eventually, upstream sync is so painful the fork stops syncing.
  • Now the fork is missing new features and security fixes, and the team owns a second full DB engine.

"Forks that start with the best intentions but slowly drift into maintenance nightmares." — (sources/2025-05-27-allthingsdistributed-aurora-dsql-rust-journey)

Case study: Aurora DSQL

systems/aurora-dsql replaces almost all of the interesting parts of Postgres (replication, concurrency control, durability, storage, session management) and keeps only the query processor (parser, planner). Yet DSQL is built via extensions, not a fork. This lets DSQL:

  • Continue to track upstream Postgres query-optimizer and planner improvements.
  • Keep the extension code in its own repos / packages rather than interleaved with Postgres internals.
  • Separate language concerns: core stays C, extensions are Rust (for concepts/memory-safety on new code).

Language choice for extensions

Initial instinct: write extensions in C, for lowest impedance mismatch with Postgres's C API. DSQL team found this was the wrong choice:

  • Postgres's C core is battle-tested — not the source of new bugs.
  • New extension code would be the overwhelming source of new memory-safety bugs (per Android research — see concepts/memory-safety).
  • Rust extensions let the team encode Postgres's implicit invariants (e.g. a char*+len pair documented to be used together) into the type system instead of into header-comment conventions.

So the refined pattern is: extend Postgres, don't fork; and write the extensions in Rust, not C, for memory-safety gains on new code.

When not to use this pattern

  • If your changes require modifying parts of Postgres that have no extension hook, extensions won't suffice. In that case, the choice is: contribute the hook upstream, maintain a minimal patch on top of upstream, or accept a fork.
  • If your product ships a custom on-disk format and you don't want to carry Postgres's on-disk format as a vestigial dependency, you may still fork or diverge — DSQL sidesteps this by replacing storage behind the extension API rather than changing Postgres's on-disk concept.

Seen in

Last updated · 200 distilled / 1,178 read