Per-cluster data directories

Version 0.6.3 Updated Sep 30, 2026

Layout change in releases after 0.6.0. When the data directory is derived from a root, PG_EMBED_ROOT or the per-user default, each cluster gets a data directory of its own under the root. This lets concurrent processes, such as cargo nextest running one process per test, each bootstrap a working cluster in one root. Up to 0.6.0 they shared one directory, and every process but the first failed with pg_ctl: another server might be running.

0.6.0 and earlier Later releases
Data directory <root>/data <root>/data/<name>/
Password file <root>/install/.pgpass <root>/data/<name>.pgpass
Liveness lock none <root>/data/<name>.lock

Table: Data layout under a derived root.

<name> is <pid>-<nanoseconds>-<counter>, unique to each cluster. The install tree, <root>/install, is still shared. A cluster left directly in <root>/data by an earlier release is no longer used, and the bootstrap logs a warning naming it; it can be deleted.

  • Explicit PG_DATA_DIR is unchanged. Setting it keeps a single data directory at that path, with its password file in the install tree, and skips everything below.
  • Clean-up. Each process holds a kernel lock on its cluster's .lock file until it exits. The next bootstrap in the root removes every cluster whose lock it can take, which are those whose process has gone, however it ended. A reused process ID cannot fool this, because the held lock decides, not the ID. A cluster whose data directory is removed while its process lives, for example by TestCluster's drop, leaves its .lock and .pgpass files until the process exits.
  • Orphaned servers. If a test process dies abruptly, its postgres server can outlive it. Before removing a dead cluster's directory, the bootstrap reads its postmaster.pid. A live process counts as that cluster's server only if it is a postgres serving that directory (its working directory is the cluster's, read from /proc on Linux and with lsof elsewhere), so a recycled process ID that now belongs to another database is left alone. The bootstrap stops the server the way pg_ctl stop -m immediate does, and removes the directory only after the server has exited. A server it cannot stop, or cannot confirm, keeps its directory, and a warning is logged.
  • run and the binary keep one directory. The setup-only run function and the pg_embedded_setup_unpriv binary initialize a cluster to be used after they exit, so they keep the derived <root>/data directory as in 0.6.0 rather than taking a slot that the next bootstrap would sweep. A TestCluster in the same root claims a slot of its own and does not reuse it; set PG_DATA_DIR to share one directory.
  • CleanupMode::Full shares the install tree. Clusters run side by side from one install tree, so a full clean-up removes it only when no other cluster in the root is running from it. Otherwise it removes just its own data directory. The check and the removal hold a guard file, .claim-guard, that new clusters wait on when claiming a directory.
  • Setup turns. Clusters sharing an install tree take turns populating it, under a lock on <root>/install/.pg-embed-setup.lock, so a cold root is set up once rather than raced. Once the tree is populated, clusters initialize and start in parallel; only the brief copy of cached binaries is serialized. Clusters that declare extensions retake the lock while the extensions are installed, so that step is also serialized.

ADR 005 records the design.