PlanetScale — Processes and Threads¶
Summary¶
Ben Dicken (PlanetScale, 2025-09-24, re-fetched 2026-04-21) publishes
an interactive-article pedagogical piece on operating-system
process and thread abstractions that lands, in the final third of
the body, on the architectural trade-off between Postgres's
process-per-connection and MySQL's thread-per-connection models,
and canonicalises connection pooling as the universal mitigation for
both. The opening two-thirds is OS fundamentals (CPU + RAM, made-up
instruction set, multitasking via processes +
context switching,
threads + pthread_create,
fork / execve / clone system calls);
the final third is the database architectural pay-off — named MySQL
mysqld-single-process + thread-per-connection model explicitly
positioned as the architectural response to Postgres's
process-per-connection cost. Tutorial-first, but its
production-architecture framing of Postgres vs MySQL concurrency
models + connection pooling is canonical-substrate content the wiki
previously referenced without a definitional home.
Key takeaways¶
-
A process is an instance of a program being executed (concepts/process-os) — the OS's fundamental abstraction for isolating executing code and sharing CPU + RAM. Each process holds its own address space (code + data + stack + heap); the OS multiplexes the CPU across many processes via time slicing, typically milliseconds-scale per slice.
-
Context switch cost is ~5 μs on modern CPUs (concepts/context-switch) — Dicken canonicalises the per-switch cost: "The full time of a context switch takes ~5 microseconds on modern CPUs… it requires executing tens of thousands of instructions, and this happens hundreds of times per second." At billions of instructions/sec, managing switching consumes tens of millions of instructions/sec — the "small performance penalty" of multi-processing. (Source: sources/2025-09-24-planetscale-processes-and-threads)
-
Thread switching is ~5× faster than process switching (~1 μs). Threads share the process's memory + code (except their own stacks), so switching between threads doesn't require page-table-root swaps or full TLB flushes. This is the structural reason threaded application models can sustain more concurrent work than process-per-work models on the same hardware.
-
A process transitions through four states. running (on the CPU), ready (kicked off because its time-slice ended, waiting for the scheduler), waiting (blocked on I/O for a disk / network request), killed (completed). The state machine is OS-assigned, not process-controlled.
-
fork()+execve()are the two canonical process-creation system calls (concepts/fork-execve).fork()clones the calling process into a child;execve()replaces the current program image with a new one loaded from disk. The typical spawn-a-program pattern:fork()then, in the child,execve(path_to_binary, ...). "When a computer boots up, a single process is initiated and all others are descendants of this one."pthread_create()is the POSIX call for threads; bothforkandpthread_createare thin wrappers around the underlyingclone()system call, which takes flags (CLONE_VM,CLONE_FILES, …) controlling what the child / new thread shares with the parent. -
Postgres is process-per-connection; MySQL is thread-per-connection — the canonical database-architecture pay-off of the process-vs-thread distinction. Dicken verbatim: "Postgres is implemented with a process-per-connection architecture. Each time a client makes a connection, a new Postgres process is created on the server's operating system. There is a single 'main' process (PostMaster) that manages Postgres operations, and all new connections create a new Process that coordinates with PostMaster." And: "MySQL is a great contrast, designed to run as a single process (
mysqld). However, it is also capable of handling thousands of queries per-second, hundreds of connections, and utilizing multi-core CPUs. It achieves this via threads." Canonicalised as patterns/process-per-connection-database + patterns/thread-per-connection-database. -
The process-per-connection model has a structural memory + time overhead that the thread-per-connection model avoids — Dicken verbatim: "Processes are heavy: there is memory overhead and a time overhead for managing them." This is the same memory-overhead property canonicalised elsewhere on the wiki as MySQL's
max_connectionsceiling (concepts/max-connections-ceiling) — this post extends the framing backward one abstraction layer to the OS-level process-vs-thread memory cost. -
Connection pooling is the universal mitigation for both models (concepts/connection-pool-exhaustion). Dicken verbatim: "both MySQL and Postgres suffer from performance issues when the connection counts get too high. Even with threads, each connection requires dedicated memory resources to manage connection state. MySQL, Postgres, and many other databases use a technique known as connection pooling to help." The canonical framing: *"Connection poolers sit between clients and the database. All connections from the client are made to the pooler, which is designed to be able to handle thousands at a time. It maintains its own pool of direct connections to the database, typically between 5 and
-
… The pooler then intelligently distributes incoming queries/transactions across the fixed set of connections. It acts as a funnel: pushing the queries from thousands of connections into tens of connections." Canonical datum: typical pooler-to-DB pool 5–50 connections vs arbitrary client fan-in. This is the OS-fundamentals altitude of the same architectural lever canonicalised at production scale by van Dijk's 1M-connections benchmark (patterns/two-tier-connection-pooling): the process-vs-thread memory cost per connection is the why; the two-tier pooler is the how*.
-
Virtual memory is the unacknowledged-in-this-post substrate that makes process context switches affordable. Dicken names it as an advanced topic ("OSs use virtual memory. This is a subject for another day") — the per-process page table + TLB means the "copy all of RAM" picture in the simplified visual isn't what actually happens; only the register state + TLB flush + page-table-root swap is required at context-switch time.
Systems / concepts / patterns extracted¶
Systems¶
- systems/postgresql — canonical process-per-connection database; each client connection spawns a new OS backend that coordinates with the PostMaster supervisor.
- systems/mysql — canonical thread-per-connection database;
mysqldis a single process spawning one thread per connection, utilising multi-core via threads. - systems/pgbouncer — connection pooler referenced by name as the funnel pattern between thousands of client connections and tens of database connections.
- systems/innodb — MySQL's storage engine referenced in the context of MySQL's threading model.
Concepts¶
- concepts/process-os — "a process is an instance of a program being executed by a computer"; the fundamental OS abstraction over CPU + RAM.
- concepts/thread-os — within-process execution context sharing memory with sibling threads, scheduled independently; mount/unmount ~5× faster than process context switch.
- concepts/context-switch — ~5 μs on modern CPUs, tens of thousands of instructions per switch; the load-bearing cost that makes time-slicing "not free".
- concepts/fork-execve —
fork()clones the calling process;execve()replaces its program image; on Linux both (pluspthread_create()) are thin wrappers aroundclone(). - concepts/connection-pool-exhaustion — the natural-threshold symptom of the per-connection memory cost this post canonicalises.
Patterns¶
- patterns/process-per-connection-database — Postgres's architecture; OS isolation and supervision via PostMaster, paid for in memory + context-switch overhead per connection.
- patterns/thread-per-connection-database — MySQL's architecture; lower per-connection overhead via shared process memory + thread-faster context switch, at the cost of losing OS-level isolation between connections.
- patterns/two-tier-connection-pooling — PlanetScale's structural answer to the same memory-cost problem at production scale.
Operational numbers¶
- Context switch cost: ~5 μs on modern CPUs = tens of thousands of instructions per switch.
- Thread switch cost: ~1 μs (~5× faster than process switch).
- Typical connection pool size: 5–50 direct connections to the DB fronting thousands of upstream client connections (Dicken verbatim).
- Context switches per second: hundreds per second; at billions of instructions/sec, bookkeeping consumes tens of millions of instructions/sec.
- CPU instructions per second: >1 billion.
- Time-slice granularity: milliseconds.
Canonical framings¶
- "Processes are heavy" — the structural criticism of process-per-connection architecture that motivates MySQL's contrasting single-process design.
- "A pooler acts as a funnel: pushing the queries from thousands of connections into tens of connections" — the canonical metaphor for connection pooling's role.
- "When a computer boots up, a single process is initiated and all others are descendants of this one" — the canonical fork-ancestry framing.
Caveats¶
- Interactive-article constraints: the post is built around interactive CPU simulations (play button, instruction-set visualiser, process-swap buttons). The scraped markdown captures text but not the interactive widgets; readers of the raw markdown miss the animated context-switch visualisations that carry much of the pedagogical load.
- Single-core assumption: Dicken explicitly assumes a one-core CPU throughout ("we're going to assume you only have one core in your CPU for the rest of this article"). The multi-core regime is where thread-per-connection's advantage over process-per-connection compounds — threads in a single address space can share work across cores without cross-process IPC — but this is not walked through.
- Virtual memory deferred: the simplified visuals show "copy the RAM" at context-switch time which is misleading; real OSes use per-process page tables + TLB so the switch cost is much less than it looks. Dicken acknowledges this ("a subject for another day") but the simplified model stays in readers' heads.
- Postgres connection cost under-quantified: Dicken names "memory overhead and a time overhead" for Postgres processes without numbers. Typical production Postgres backend ~5–10 MB plus shared buffers; this is the load-bearing datum driving PgBouncer adoption, but it's not in the post.
- Connection pooler ecosystem elided: PgBouncer + ProxySQL + PlanetScale's VTTablet two-tier model are not named. The post stays at the generic "connection poolers sit between clients and the database" altitude.
- Thread-per-connection in MySQL is not strictly literal:
MySQL uses a thread pool with thread-caching
(
thread_cache_size, thread-pool plugin in Enterprise / MariaDB). The post's "it achieves this via threads" is directionally correct but simplifies the real concurrency model. clone()flag enumeration deferred: "we wont get into all these details here, but runman cloneon a linux machine for the details."- Pedagogical voice: no production numbers, no customer retrospective, no scaling war-story; the post is in the same genre as Dicken's 2024-09-09 B-trees and database indexes and 2025-03-13 IO devices and latency — OS/database fundamentals with an interactive layer.
- Date: original publication 2025-09-24, re-fetched 2026-04-21; ~7 months old at ingest time.
Cross-source continuity¶
- Fifth altitude of the Reyes / Gangal / van Dijk / Noach / Dicken connection-scaling corpus — (1) names the 16k RDS-MySQL ceiling vs "nearly limitless" PlanetScale (vendor-comparison altitude); (2) benchmarks 1M connections via two-tier pooling (production-scale benchmark altitude); (3) canonicalises VTTablet's three-era pool mechanism (Vitess internals altitude); (4) Noach 2024-08-29 canonicalises connection-pool-exhaustion as natural-threshold throttling signal; (5) this 2025-09-24 post canonicalises the OS-substrate memory-economics (process-per-connection vs thread-per-connection + shared-page-table vs forked-page-table) that makes all four preceding framings load-bearing (OS-fundamentals altitude).
- Complements sources/2026-04-21-planetscale-scaling-postgres-connections-with-pgbouncer — that post canonicalises the PgBouncer configuration surface + deployment topologies at PlanetScale; this post canonicalises the OS-level why (process-per-connection cost) that makes PgBouncer load-bearing on top of Postgres.
- Complements Figma's DBProxy war-story — Figma's Postgres-at-scale corpus canonicalises the custom DBProxy pool tier built for the same per-backend-memory reason; this post canonicalises the OS-substrate dimension of that memory cost.
- Kinship with concepts/virtual-thread — Java 21's virtual threads and OS threads are two different layers of the multitasking hierarchy: OS threads (this post) multiplex onto CPU cores; VTs multiplex onto OS threads via the JVM's continuation machinery.
- Complements concepts/pre-fork-copy-on-write — pre-fork
COW is the specific memory-efficiency exploit that makes
process-per-connection (and other fork-heavy architectures)
viable on Linux; this post canonicalises the underlying
fork()syscall that COW optimises. - No existing-claim contradictions — strictly additive foundational vocabulary.
Source¶
- Original: https://planetscale.com/blog/processes-and-threads
- Raw markdown:
raw/planetscale/2025-09-24-processes-and-threads-f83183fd.md
Related¶
- Companies: companies/planetscale
- Systems: systems/postgresql, systems/mysql, systems/pgbouncer, systems/innodb
- Concepts: concepts/process-os, concepts/thread-os, concepts/context-switch, concepts/fork-execve, concepts/connection-pool-exhaustion
- Patterns: patterns/process-per-connection-database, patterns/thread-per-connection-database, patterns/two-tier-connection-pooling
- Adjacent vocabulary: concepts/virtual-thread, concepts/carrier-thread, concepts/fork-join-pool, concepts/pre-fork-copy-on-write, concepts/max-connections-ceiling