Limits
Process model
Section titled “Process model”PostgreSQL runs as it does on a server: a postmaster starts a backend process for every connection, plus its auxiliary processes, and they share memory. Every process is a WebAssembly instance on its own goroutine. Sessions are therefore real: locks between connections wait, deadlocks are detected (SQLSTATE 40P01 after deadlock_timeout), pg_stat_activity and pg_locks show the other sessions, and session state (SET, temporary tables, advisory locks) belongs to one connection.
snapshotandresetwait for open transactions. Both stop the cluster and start it again; client connections keep their sockets and get a new backend on their next message, with named prepared statements andLISTENregistrations re-created. Commit or close transactions first; the wrappers fail withbusyafter their timeout.- A backend PostgreSQL itself ends (
pg_terminate_backend,DROP DATABASE ... WITH (FORCE), a crash) ends the connection with its FATAL message, as on a server. - Cancelling a statement that makes no system call waits until it does.
Databases
Section titled “Databases”Every database of a server can be connected to, and CREATE DATABASE works. A database that does not exist is refused with SQLSTATE 3D000.
Closing a server
Section titled “Closing a server”Closing a server or fork does not drop client connections. Each stays open until its client closes it, sends a message, which is answered with SQLSTATE 57P01, or 30 seconds pass. Pools such as node-postgres’s raise an unhandled error when an idle connection is dropped under them.
Background processes
Section titled “Background processes”- Parallel query and parallel index builds work; the workers are processes of the cluster.
- Extensions that need their own background worker are not bundled. See extensions.
io_methoddefaults tosync(PostgreSQL 18’sworkerwould add three I/O worker processes for no gain).
Resources
Section titled “Resources”- A server is about 150 MB resident with the default
shared_buffers=32MB, plus a few MB per process (five auxiliary processes and one per connection). Each live fork adds its own copy. Raiseshared_buffersthrough the server parameters when a test needs a larger cache. - The server is 32-bit WebAssembly and addresses at most 4 GiB.
statement_timeoutfires between protocol messages and whenever the backend reads the clock or sleeps, but not inside a CPU loop that does neither.
Fork capacity and waiting
Section titled “Fork capacity and waiting”Each snapshot has its own pool of fork slots. MaxForks limits live forks from that snapshot; the template server does not use a slot. The default comes from memory: a quarter of the process’s memory limit (GOMEMLIMIT, the cgroup limit or the physical memory, whichever is smallest) divided by the cost of one fork (shared_buffers plus about 32 MB), which is 28 on a 7 GB CI runner with the default shared_buffers=32MB. A slot costs nothing until a fork occupies it. When the memory cannot be determined, the default is the number of CPUs. Each fork owns another data-directory copy and buffer cache, so the cap also limits memory use. When all slots are occupied, another fork request waits until a fork closes and releases its slot.
| API | Set the limit | Limit a wait for a free slot |
|---|---|---|
| Go | SnapshotOptions.MaxForks or pgmemtest.Options.MaxForks |
Snapshot.Fork(ctx) waits until its context is cancelled or reaches its deadline. If the server logger is enabled, a wait longer than 5 seconds is logged. |
| Python | server.snapshot(max_forks=n) |
snapshot.fork(timeout=seconds); omitted or None waits indefinitely. Expiration raises ProtocolError with code pool_timeout. |
| Java | Server.snapshot(maxForks) or JUnit’s .maxForks(n) |
Snapshot.fork(Duration) or JUnit’s .forkTimeout(Duration); no timeout is the default. Expiration raises pool_timeout. |
| Node.js | PgmemServer.start({ maxForks: n }) |
fork({ timeoutMs }) or withFork(fn, { timeoutMs }); omitting it waits indefinitely. Expiration raises PgmemError with code pool_timeout. |
The JUnit extension applies its cap to each registered template. A snapshot’s close() refuses new forks but leaves live forks running; Go’s Snapshot.Wait() waits for those forks to close.
Automatic test fixtures have their own acquisition defaults: Go and Python use 30 seconds; Node registration/useFork/Jest also use 30 seconds. Java JUnit acquisition remains unbounded unless .forkTimeout(Duration) is set. See the lifecycle guide for each setting.
Timeouts govern different waits
Section titled “Timeouts govern different waits”The timeout for a fork slot does not limit snapshot creation or server start-up. Those are separate waits with separate settings:
| Wait | Setting | What happens at the deadline |
|---|---|---|
| Snapshot creation waits for open transactions to finish | Go: the Snapshot context. Python: server.snapshot(timeout=30.0). Java: Server.snapshot(maxForks, timeout) (30 seconds by default). Node.js: snapshotTimeoutMs (30,000 ms by default). |
Snapshot creation fails with busy; commit or close connections to the template first. |
| A server process starts | Python: pgmem.start(timeout=30). Java: .readyTimeout(Duration) (30 seconds by default). Node.js: startupTimeoutMs (30,000 ms by default). Go: the Start context. |
Startup fails if the process does not become ready in time. |
| A fork is reset to a snapshot | Go: Server.Reset(ctx) or Restore(ctx). Python: server.reset(timeout=5.0). Java: server.reset() (5 seconds). Node.js: fork.reset({ timeoutMs }) (5,000 ms by default). |
Reset fails with busy if an open transaction does not finish. |
Security
Section titled “Security”There is no TLS and no password authentication. TCP listeners bind only to 127.0.0.1, and URLs carry sslmode=disable. Explicit Unix socket mode uses private local socket directories instead. ICU collations are not available.