Postgres
Context
- INGRES (1970s, Berkeley) was Stonebraker’s first system
- proved the relational model could be implemented
- Postgres:
- mid-1980s: INGRES was “hacked up enough” that big new features would not fit
- so they built a new system: POSTGRES = POST inGRES
- Motivating applications were not payroll
- CAD, GIS, graphics, etc.
- polygons, rectangles, text, nested objects — not just ints and strings
- Hellerstein: this is Stonebraker’s most ambitious project
- a “second system” that succeeded (Brooks usually predicts the opposite)
- today’s PostgreSQL is still recognizably that architecture
Design goals (1986 paper)
- Complex objects
- User-extendible types, operators, and access methods
- Active databases: alerters, triggers, inferencing
- Simpler crash recovery
- New hardware: optical disks, multiprocessor workstations, custom VLSI
- Change the relational model as little as possible
Why a conventional RDBMS fails on “objects”
Example from the paper: paint a map.
- polygons, circles, lines each in their own relation
- a DISPLAY relation holds color / position / scale, plus
(obj-type, object-id) - to draw the screen you issue one query per object type
- too many round trips; you cannot paint in 1–2 seconds
What they wanted:
- store the object in a field of DISPLAY
- one query fetches the whole thing
This is the object-relational bet:
- keep tables as the outermost type
- let columns be nested / opaque / user-defined
- “have your cake and eat it too” vs flattening everything into 1NF
Complex objects and ADTs
Nested data
- a column can hold nested tuples or tables
- also “QUEL as a data type”: a field whose value is a query
- later: XML, JSON — same idea, new syntax
- Hellerstein: every “NoSQL then add queries” cycle ends here
Opaque ADTs + UDFs
- store a type the core engine does not interpret
- Codd already allowed any atomic type with predicates
- the software problem is registering operators
- User-defined functions (UDFs) and aggregates (UDAs)
- put in the catalog, call them from queries
- Why in the DBMS instead of the application?
- push code to data, not data to code
- modest catalog + foreign-code invocation
- query language and architecture stay relational
Security was not a 1980s concern.
- Illustra / Informix DataBlades ran unprotected C on the server
- Oracle’s marketing used that
- today: UDFs exist, with sandboxes
MapReduce / Big Data UDFs are the same architecture with parallelism bolted on.
Extensible access methods
- B-trees: equality + 1-D range
- GIS / CAD want 2-D range → R-trees (Guttman, Stonebraker group)
- inventing an index is not enough
- can you plug it in?
- can the optimizer recognize when to use it?
- concurrency / recovery for the new structure?
- Postgres: register an access method + the operators it supports
- B-tree works for any operator set that obeys the usual order axioms
- optimizer gets: pages touched, tuples examined, join method legality
- Survived: B-tree, GiST, SP-GiST, GIN, PostGIS
- GiST = templated “generalized search tree” (later Hellerstein work)
Active databases: alerters, triggers, rules
- Alerters / triggers (
always): run forever- alerter: retrieve that fires when matching data changes
- trigger: append / replace / delete that fires on a condition
- example:
delete always DEPT where count(employees in that dept) = 0
Implementation idea (1986): special locks
- I-locks (“invalidate me”): cached plan / answer; a write invalidates
- T-locks: sleeping
alwayscommand; a write wakes it - D-locks:
demandrule; a read rewrites the query (query modification)
Two implementation styles (Hellerstein / PRS2):
- query rewrite (Ingres-style view modification)
- row-level triggers (locks that run an action instead of waiting)
Neither “won.” PostgreSQL still has statement-level and row-level triggers. The AI-winter problem remains: a pile of rules becomes unintelligible. Fast deployments often just avoid triggers.
Descendants: materialized views, CEP, streaming.
Process structure
- DBMS must be a separate process from the app (protection)
- two models:
- process-per-user: one backend per client — simpler, worse sharing
- server: one DBMS for all clients — better, needs a mini-OS
- they chose process-per-user on 4.3BSD (“limited programming resources”)
- POSTMASTER: one per machine, started at boot
- lock manager (no shared memory segments on 4.3BSD)
- demons (compile queries in the background, vacuum, …)
- POSTGRES backend: executes commands for one app
- request/answer messages; frontend pulls a bounded number of tuples
- unlike INGRES, the backend does not flood the channel
This process model is still how PostgreSQL looks: postmaster + one backend per connection.
Storage: log as data (no overwrite)
Stonebraker’s “missionary zeal to do something different.” Commercial systems used WAL. INGRES already had a WAL-ish manager. They did not want to write another one — especially if users can add access methods (recovery code must stay simple).
Tuple format
Every tuple carries:
- immutable 64-bit tuple id
tmin,BXID— when / which xact created ittmax,EXID— when / which xact killed it- version pointer, descriptor (null bitmap / field offsets)
Update = insert a new tuple + stamp tmax on the old one.
No in-place overwrite.
To read as of time T, find tuples with tmin < T < tmax whose creating xact committed
(and whose deleter has not committed, depending on the case).
The “log”
- xact ids assigned sequentially
- commit: force dirty pages, then write one bit (committed / aborted / in-progress)
- assumed a little non-volatile “secure main memory” for the tail of this bit log
- vacuum:
- drop aborted versions
- migrate old committed tuples to optical disk
- after that, archive needs no log — everything there committed
Why this was counter-cultural
- TPC winners were competing on fancy WAL
- log shipping / replication is natural with WAL, awkward here
- Postgres storage never won on OLTP speed
What PostgreSQL actually does today (Hellerstein)
- no-overwrite time travel was removed
- replaced by WAL
- but they kept versioned tuples for MVCC / snapshot isolation
- that was not a Berkeley Postgres goal
- result: WAL complexity plus version overhead
- echoes elsewhere:
- NoSQL: replication instead of fancy WAL
- in-memory DBs: multi-version + a compressed commit log
- cheap storage + streaming: time travel is due for a comeback
What survived vs what died
Survived (in PostgreSQL or the industry):
- object-relational: UDFs, nested types, JSON/XML
- extensible indexes (GiST, PostGIS)
- postmaster / process-per-user
- SQL (added by the pickup team, not the 1986 design)
- triggers (rewritten, both granularities)
- extension / DataBlade ecosystem (MADlib, Citus, …)
- a pile of companies
Died or was replaced:
- POSTQUEL
- no-overwrite + time travel as primary storage
- optical-disk vacuum
- the original rules implementations
- expensive-UDF optimizer (temporarily)
Hellerstein’s number (2019): PostgreSQL among the most popular independent open-source DBMSs; Postgres-based companies totaling > $2.6B in acquisitions.
Lessons (Hellerstein)
- extensibility let the second system carry too many ideas without collapsing
- try many extensions; the strong ones stick
- “one size fits many” can beat “one size fits all is dead”
- later “MIT Stonebraker” argued specialization; Postgres is the counterexample
- open-sourcing a research prototype can outlive the lab
- Stonebraker: a pickup team, none of them Berkeley, shepherded it after 1995
- “do something important and set it free”