Topic 76

Extensions Worth Knowing

Extensions

CREATE EXTENSION adds types, functions, operators, index methods and background workers to a running database. That single statement is why Postgres became a vector store, a job scheduler, a time-series engine and a geospatial platform without anyone forking the server. The first topic of this book called extensibility a catalogue property; this is where the property is spent.

Each entry on the list is also a dependency that your backups, your replicas, your restore host, your next major version and, if you ever move, your managed provider all have to agree with. Cartwheel found this out during the upgrade planning in the previous topic: four extensions were installed on pg-primary, one of them with no record of who added it or why, and each had to be confirmed as packaged for 18 before the window could be booked. Nothing in the catalogue records the reason an extension is there.

What CREATE EXTENSION Actually Does

An extension is a packaged set of SQL objects with a version number and a script that creates them. Installing it is per database, not per cluster: running the statement in cartwheel does nothing for any other database on pg-primary, which is the same lesson the template1 discussion in Chapter 1 arrived at from the other side. The objects land in one schema, chosen with the SCHEMA clause when the extension permits relocation, and CASCADE pulls in any extensions it depends on rather than failing with a message about a missing prerequisite.

What is installed, and moving one forward after a package upgrade
SELECT extname, extversion, extnamespace::regnamespace
  FROM pg_extension ORDER BY 1;

ALTER EXTENSION pg_stat_statements UPDATE;   -- SQL objects only

Two facts hide behind that pair of statements. The first is that installing a new package on the operating system does not update the extension inside the database. The files on disk change, the catalogue does not, and ALTER EXTENSION … UPDATE is what runs the migration script that brings the SQL objects to the new version. Skipping it leaves a database whose extension version and library version disagree, which usually surfaces as a function that exists but behaves like the old one. The second is that some extensions are not just SQL. Those carrying a shared library that has to be loaded at server start (pg_stat_statements, auto_explain, pg_cron, citus) must be listed in shared_preload_libraries, and that is a restart rather than a statement. It is the difference between "install it now" and "install it at the next window".

The Operational Set

The extensions that make the rest of this book measurable are all in the standard contrib package. pg_stat_statements aggregates execution counts and timings per normalized query, which is what turns "the site is slow" into a ranked list. auto_explain logs the plan of any statement over a threshold, so the plan that was actually chosen at 09:14 on Saturday exists rather than being reconstructed later from a query run at a quiet moment. pg_buffercache reports what is currently in shared_buffers and which relation each page belongs to. pgstattuple counts live and dead tuples and free space by scanning the relation, which is the honest bloat number rather than an estimate. postgres_fdw queries another Postgres server as if its tables were local. hypopg creates an index that does not exist so that EXPLAIN can be asked whether it would use one, which tests an index on a 40-million-row table without building it.

One more belongs beside them without being contrib. pg_repack rebuilds a bloated table or index online, holding only brief exclusive locks at the start and end rather than the ACCESS EXCLUSIVE lock a VACUUM FULL holds for the whole rewrite. On a table that checkout reads continuously, that difference is the difference between a maintenance window and a Tuesday afternoon.

The Capability Set

The second group replaces systems rather than instrumenting one. pgvector adds a vector type with similarity operators and approximate-nearest-neighbour indexes, which is the reason a large number of teams that were about to run a dedicated vector database did not. PostGIS adds geometry and geography types with a full spatial function library and spatial indexing, and is old enough and complete enough that "Postgres for geospatial" needs no qualification. pg_trgm supports similarity and fuzzy matching on text and backs index-supported LIKE patterns that a B-tree cannot serve.

The heavier three change how the database is operated. TimescaleDB layers hypertables, columnar compression and continuous aggregates over ordinary tables for time-series work. Citus distributes tables across a cluster of nodes, which is the answer when one server genuinely is not enough. pg_cron runs scheduled jobs inside the database, and demonstrates the restart rule perfectly, since it needs a preloaded library and by default keeps its metadata in a single named database for the whole cluster. pg_partman automates the creation and retention of partitions, which is the tooling most teams eventually build by hand around a partitioned delivery_events.

pgcrypto, and Where the Key Lives

Column encryption is the extension people install for the wrong reason, so it is worth being precise about what it does. pgcrypto provides hashing, password hashing with crypt() and gen_salt(), symmetric and public-key PGP functions, and raw ciphers. All of them execute inside the server process, so the plaintext and the key both pass through the database. The manual states it directly: the data and passwords move between the extension and the client in clear text, so you must connect locally or over TLS and must trust the system and database administrators.

That single sentence prices the feature. A key stored in a table in the same database protects against nothing an attacker who reached the database cannot also reach. A key passed in from the application on every call is better, and it makes a compromised backup file genuinely unreadable, which is a real threat model worth defending. But it does not protect against a compromised superuser or a compromised server, and if that is the threat, the encryption has to happen in the application before the value is ever sent. Combine that with the indexing cost from the authentication topic and the honest recommendation is narrow: a small number of columns, read by primary key, with the key held outside the database.

Trusted Extensions, and Who May Install One

Installing an extension ordinarily requires whatever privileges creating its component objects would require, and for many of them that means superuser. Since 13, an extension whose control file marks it trusted can be installed by any role with CREATE on the database. The extension object itself is owned by the caller while the objects inside it are owned by the bootstrap superuser, so the caller can drop the extension without being able to modify its internals.

That distinction is technical on a self-managed box and structural on a managed one, where nobody has superuser at all. Which extensions a provider supports, at which versions, on which major release, is a hard constraint on the design rather than a detail to look up at deployment. Amazon RDS, Cloud SQL, Azure Database and the rest each publish their own list, the lists differ, and they differ again per major version.

What an Extension Costs Later

Every extension has to exist on every machine the database might ever run on. That is the replica, the restore target, the scratch cluster the upgrade is rehearsed on, and the new major version on the day the window is booked; the previous topic's pre-flight query exists because this is the item that most often stops an upgrade. An extension with a background worker also takes a process slot and shared memory, and one with a preloaded library makes every future install of it a restart.

One CREATE EXTENSION, and every place it has to be true afterwards
Installed per databasenot once per cluster
A preloaded library?then it is a restart, not an install
Every replica and restore hostand the scratch cluster the upgrade is rehearsed on
Packaged for the next majoron the day the window is booked
On the provider's listspecific to the provider, and to the major version

The deeper cost is on the upgrade path. An unmaintained extension pins your major version to whenever its packaging catches up, and five extensions adopted in one quarter can produce a dependency graph that no single Postgres release satisfies. That is the entire argument for reaching for a core feature when one fits, jsonb instead of a document store or built-in full text before a search engine, and for keeping the list in the repository next to the migrations with a version and a named owner beside each line.

An extension vs a second system

The extension — the data, the transaction and the backup stay in one place. pgvector beside products means a similarity search can join to price and stock in the same query, inside the same snapshot, with no synchronization to get wrong.

The separate system — a dedicated vector database, a search cluster, a time-series store. Each wins at extreme scale and at features Postgres does not have, and each costs a second thing to run, secure, back up and keep consistent with the first.

Which way to start — with the extension, and move out when a number you measured says to. A benchmark written by the vendor of the second system is not that number.

Common Mistakes
  • Installing an extension in production without confirming it exists on the restore host and on the next major version — the restore or the upgrade fails, at the worst possible moment for both.
  • Trying to add pg_stat_statements during an incident — it needs a preloaded library and therefore a restart, which is not something to discover while the site is slow.
  • Upgrading the operating system package and never running ALTER EXTENSION … UPDATE — the library and the catalogue disagree, and the symptom is a function that quietly behaves like the old version.
  • Assuming a managed provider supports an extension because it is popular — the supported list is specific to the provider and to the major version, and it is a design constraint.
  • Using pgcrypto with the key stored in the same database — an attacker who reached the data reached the key, so the encryption defends against nothing that has actually happened.
  • Adding five extensions in a quarter — the union of their supported versions becomes an upgrade path nobody can satisfy, and the cluster stays on an old major by default.
Best Practices
  • Keep the list of extensions the application depends on in the repository with a version and an owner beside each one, and treat it as a deployment requirement.
  • Install pg_stat_statements and auto_explain on every cluster from day one, so the restart they need has already happened before you need the data.
  • Check provider support and next-major availability before adopting anything, and check it again for anything that ships a background worker.
  • Run ALTER EXTENSION … UPDATE as part of the same change that upgrades the operating system package, so the catalogue and the library stay in step.
  • Reach for a core feature when one fits (jsonb, built-in full text, declarative partitioning) and make adopting an extension a decision somebody signs.
  • Reserve pgcrypto for a small set of columns read by primary key, with the key supplied by the application rather than stored beside the data.
Comparable toolsMySQL plugins and components, a far narrower surfaceOracle priced options where Postgres has an extensionPGXN and the distribution packages, where extensions come fromManaged providers each publish their own supported list per version

Knowledge Check

Why can pg_stat_statements not be added to a running cluster during an incident?

  • Creating it rewrites the catalogue, which takes hours on a large cluster
  • Its library must be preloaded at server start, so it needs a restart
  • It takes an exclusive lock on every relation while it is being created
  • It can only be created while the database has no other connections

The operating system package for an extension was upgraded. What has to happen inside the database?

  • The extension must be dropped and created again from scratch
  • Nothing, because the catalogue version follows the installed package version
  • ALTER EXTENSION ... UPDATE runs the script that migrates the objects
  • A configuration reload, which reapplies each of the extension's definitions

What did marking an extension as trusted change, and why does it matter on a managed service?

  • A role with CREATE on the database can install it without superuser
  • Its code runs in a sandbox that cannot reach the server's filesystem
  • It has been audited by the project and is guaranteed free of defects
  • It no longer needs its library listed in shared_preload_libraries

Storing a pgcrypto key in a table in the same database defends against which of these?

  • An attacker who has obtained a working connection to the database
  • Nothing meaningful, because the key travels wherever the data goes
  • A stolen logical dump, which excludes tables holding key material
  • A compromised superuser, who cannot read the key table's contents

What is the strongest argument for keeping pgvector beside the products table rather than running a vector database?

  • It is faster at similarity search than any dedicated engine at scale
  • It avoids the approximate indexing that a separate system depends on
  • A similarity search can join to price and stock in the same snapshot
  • It removes an entry from the list checked at restore and upgrade time

You got correct