CockroachDB — Architecture#
Architectural style#
Layered Monolith with Microkernel-style CCL Injection
CockroachDB is a layered monolith: every CockroachDB node runs the same single binary, with responsibilities organized into strict layers that depend only downward. The layers span from PostgreSQL wire protocol at the top to Pebble LSM storage at the bottom, with a transactional distributed KV store in between.
The “microkernel” aspect appears in how enterprise features (CCL) are wired in. The OSS core defines hook points — function variables, init()-registered callbacks, and interface slots — that the commercial pkg/ccl packages fill in via Go’s init() mechanism when imported with a blank import in main.go. This means the core binary compiles and runs with or without CCL; it just lacks commercial capabilities without it.
A more recent architectural concern is multi-tenancy: the serverController manages on-demand instantiation of SQLServerWrapper tenants that share the same KV storage cluster but run isolated SQL processes. This is the foundation for CockroachDB Serverless.
Evidence from code:
pkg/cmd/cockroach/main.go: 5 lines — importspkg/ccl(blank) and callscli.Main()pkg/server/server.go:topLevelServerstruct with ~40 field dependencies, all manually wiredpkg/kv/sender.go:Senderinterface implemented bykv.Txn,TxnCoordSender,Node,Store,Replica— a single interface threading the entire KV call stackpkg/server/server_controller.go:serverControllermanages dynamic tenant server fleet
Component diagram (textual)#
┌─────────────────────────────────────────────────────────────────────────────┐
│ CLIENT APPLICATION (PostgreSQL-compatible driver) │
└───────────────────────────────────┬─────────────────────────────────────────┘
│ PostgreSQL wire protocol (TCP)
┌───────────────────────────────────▼─────────────────────────────────────────┐
│ pkg/sql/pgwire — pgwire.conn │
│ Read/parse/execute loop: net.Conn → parser → stmtBuffer → connExecutor │
└───────────────────────────────────┬─────────────────────────────────────────┘
│ parsed tree.Statement
┌───────────────────────────────────▼─────────────────────────────────────────┐
│ pkg/sql — connExecutor (FSM) + sql.planner │
│ Transaction state machine → makeOptimizerPlan() → dispatchToExecutionEngine│
│ ┌──────────────┐ ┌───────────────────────┐ ┌──────────────────┐ │
│ │ pkg/sql/opt │ │ pkg/sql/distsql │ │ pkg/sql/colexec │ │
│ │ Cascades CBO │ │ DistSQLPlanner │ │ Vectorized engine│ │
│ │ memo, xform │ │ PlanAndRun → FlowSpec │ │ columnar ops │ │
│ └──────────────┘ └───────────────────────┘ └──────────────────┘ │
└───────────────────────────────────┬─────────────────────────────────────────┘
│ kv.Txn (implements kv.Sender)
┌───────────────────────────────────▼─────────────────────────────────────────┐
│ pkg/kv/kvclient/kvcoord — TxnCoordSenderFactory │
│ TxnCoordSender: interceptor chain for retry, intent tracking, heartbeating │
│ → DistSender: routes BatchRequest to owning range via RangeCache │
└─────────────┬───────────────────────────────────────────────┬───────────────┘
│ gRPC/DRPC (to remote nodes) │ (loopback if local)
┌─────────────▼───────────────────────────────────────────── │ ─────────────┐
│ pkg/server — Node (implements kvpb.InternalServer) │ │
│ BatchInternal(): fan-out to local Stores │ │
└─────────────────────────────────────────────────────────────▼───────────────┘
│
┌───────────────────────────────────▼─────────────────────────────────────────┐
│ pkg/kv/kvserver — Store + Replica (both implement kv.Sender) │
│ Store: 40+ subsystems (allocator, queues, intent resolver, raft transport) │
│ Replica: per-range FSM, leaseholder logic, batch evaluation │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ pkg/raft — RawNode │ │
│ │ Custom Raft fork: ProposeConfChange, Step, Ready loop │ │
│ └──────────────────────────────────────────────────────────┘ │
└───────────────────────────────────┬─────────────────────────────────────────┘
│ MVCC reads/writes via Engine interface
┌───────────────────────────────────▼─────────────────────────────────────────┐
│ pkg/storage — Pebble engine │
│ Engine interface: Reader, Writer, MVCCIterator hierarchy │
│ MVCC key encoding, range tombstones, point key versioning │
└─────────────────────────────────────────────────────────────────────────────┘Cross-cutting infrastructure (used by all layers):
pkg/util/hlc— Hybrid Logical Clock for causality trackingpkg/util/tracing— OpenTelemetry-compatible distributed tracingpkg/util/stop—Stopperfor coordinated goroutine lifecyclepkg/settings/cluster—cluster.Settingsfor runtime-reconfigurable cluster settingspkg/gossip— cluster membership and system config propagationpkg/util/admission— multi-resource admission control (CPU, storage I/O)
Core components#
pgwire (PostgreSQL Wire Protocol Handler)#
- Package:
pkg/sql/pgwire - Responsibility: Accepts TCP connections from PostgreSQL-compatible clients. Implements the full PostgreSQL wire protocol: startup/auth handshake, message framing, query parsing, result serialization, cancellation. Each connection is a goroutine running
conn.serveImpl(). - Key types:
pgwire.conn(wrapsnet.Conn),PreServeConnHandler(pre-auth routing for multi-tenant),Server(listener + conn pool management) - Dependencies:
pkg/sql(forconnExecutor),pkg/security(TLS/auth),pkg/util/stop(lifecycle)
connExecutor (SQL Session State Machine)#
- Package:
pkg/sql - Responsibility: The stateful per-connection SQL execution engine. Maintains transaction state as a finite state machine (Open, Aborted, RestartWait, NoTxn). Reads from the statement buffer pushed by
pgwire.conn, executes statements, dispatches to the planner and execution engine. - Key types:
connExecutor(5000+ line struct),connExecutor.execStmtInOpenState(),dispatchToExecutionEngine() - Dependencies:
pkg/sql/opt(optimizer),pkg/sql/distsql(physical planner),pkg/kv(transactions),pkg/sql/pgwire(result writer viaClientComm)
Cascades Cost-Based Optimizer#
- Package:
pkg/sql/opt,pkg/sql/opt/optbuilder,pkg/sql/opt/xform,pkg/sql/opt/memo - Responsibility: Converts SQL AST into an optimized logical query plan using the Cascades framework. Builds a memo (space-efficient representation of all equivalent plan variants), applies normalization and exploration transformation rules (generated via
optgen), then selects the lowest-cost plan. - Key types:
memo.Memo,optbuilder.Builder,xform.Optimizer,opt.RelExpr,opt.ScalarExpr - Dependencies:
pkg/sql/sem/tree(AST),pkg/sql/catalog(schema metadata), statistics (table row counts, histograms)
DistSQLPlanner (Distributed Physical Planner)#
- Package:
pkg/sql(distsql_physical_planner.go) - Responsibility: Converts the logical plan from the optimizer into a distributed physical plan (
PhysicalPlan). Determines which nodes own which ranges and assigns plan fragments (FlowSpec) to them. Coordinates execution across nodes via gRPCSetupFlowcalls. Also manages the local vectorized execution engine path. - Key types:
DistSQLPlanner,PhysicalPlan,execinfrapb.FlowSpec,execinfrapb.ProcessorSpec - Dependencies:
pkg/kv/kvclient/rangecache(for range → node mapping),pkg/sql/execinfra(Processor interface),pkg/sql/colexec(vectorized operators)
TxnCoordSender (Transaction Coordinator)#
- Package:
pkg/kv/kvclient/kvcoord - Responsibility: Implements
kv.TxnSender. Sits betweenkv.Txn(SQL-visible) andDistSender. Manages per-transaction state: tracks write intents, issues heartbeats for long-running transactions, handles automatic retry on serialization conflicts, and aggregates leaf transaction state in DistSQL flows. Uses an interceptor chain pattern for layered concerns. - Key types:
TxnCoordSender,TxnCoordSenderFactory,txnInterceptor(chain:txnHeartbeater,txnSeqNumAllocator,txnPipeliner,txnSpanRefresher,txnCommitter,txnMetricRecorder,txnLockGatekeeper) - Dependencies:
DistSender(downstream),pkg/kv(Txn, Sender interfaces)
DistSender (Range Router)#
- Package:
pkg/kv/kvclient/kvcoord - Responsibility: Routes
BatchRequestoperations to the correct ranges by consulting the range descriptor cache. Splits batches that span multiple ranges, sends each part to the range’s leaseholder, and merges responses. Handles retries for range splits/merges and replica unavailability. - Key types:
DistSender,DistSenderConfig,RangeCache(descriptor + lease cache) - Dependencies:
pkg/rpc/nodedialer(gRPC/DRPC transport),pkg/kv/kvclient/rangecache
Node (KV Request Dispatcher)#
- Package:
pkg/server - Responsibility: Implements
kvpb.InternalServer. Receives incomingBatchRequestRPCs from other nodes (via gRPC/DRPC) and from the local DistSender (via loopback). Fans out to the appropriate localStorebased on the range’s store ID. Also serves as the gossip hub for node-level cluster state. - Key types:
Node,Stores(collection of local stores),perReplicaServer(per-replica RPC handler) - Dependencies:
pkg/kv/kvserver(Store),pkg/gossip,pkg/kv/kvserver/liveness
Store (KV Store Manager)#
- Package:
pkg/kv/kvserver - Responsibility: Manages all Raft replicas (ranges) stored on a single physical disk. Owns all background maintenance work: range splitting/merging decisions, replica placement via the allocator, GC, Raft log truncation, snapshot sending/receiving, and the timestamp cache. The Store is a god-struct with ~40 embedded queues and subsystems.
- Key types:
Store(40+ fields includingallocator,splitQueue,replicateQueue,intentResolver,raftTransport),StoreConfig - Dependencies:
pkg/raft(RawNode per-replica),pkg/storage(Engine),pkg/kv/kvserver/concurrency(lock table),pkg/kv/kvserver/batcheval(command evaluation)
Replica (Per-Range Raft Participant)#
- Package:
pkg/kv/kvserver - Responsibility: Represents one replica of a Raft consensus group (a key range). Manages the Raft state machine for its range via
pkg/raft.RawNode. For the leaseholder replica, evaluates KV batches, applies the Raft log, and serves reads from MVCC storage. Circuit breaker protects against liveness failures. - Key types:
Replica,ReplicaID,replicaMu(large mutex-guarded state includingstate.Desc, raft state, lease) - Dependencies:
pkg/raft(RawNode),pkg/storage(Engine/MVCC),pkg/kv/kvserver/batcheval,pkg/kv/kvserver/concurrency
Storage Engine (Pebble MVCC Layer)#
- Package:
pkg/storage - Responsibility: Wraps the Pebble LSM tree with CockroachDB’s MVCC key encoding. Provides the
Engineinterface (composingReader+Writer) for all KV I/O. Handles MVCC point keys (versioned by HLC timestamp) and range tombstones. All reads/writes below the Raft log go through this layer. - Key types:
Engineinterface,Pebble(concrete implementation),MVCCIterator,Batch(atomic write groups) - Dependencies:
github.com/cockroachdb/pebble(external LSM library),pkg/util/hlc(timestamp encoding)
serverController (Multi-Tenant Fleet Manager)#
- Package:
pkg/server - Responsibility: Manages on-demand instantiation of secondary SQL tenant servers (
SQLServerWrapper) within a single KV node process. Each tenant gets its own SQL server process equivalent, sharing the KV storage layer but isolated in SQL processing. Handles HTTP routing between tenants and graceful drain. - Key types:
serverController,onDemandServerinterface,SQLServerWrapper(tenant SQL server),tenantServerCreator - Dependencies:
pkg/sql/pgwire(SQL protocol for each tenant),pkg/multitenant(tenant capabilities),pkg/kv(shared KV client)
Raft Consensus Module#
- Package:
pkg/raft - Responsibility: CockroachDB’s own Raft implementation (forked from etcd/raft with deep modifications). Provides the
RawNodeAPI: application proposes entries and config changes, Raft producesReadystructs with entries to persist and messages to send. The application loop is owned byReplica. - Key types:
RawNode,Config,LogStorage/Storageinterface (implemented bykvserver),raftpb.Entry,raftpb.Message - Dependencies: None outside the package (pure algorithm implementation);
raftpbfor wire types
Data flow#
A simple SELECT query (local range, single-node)#
1. Client → pgwire.conn.serveImpl():
TCP packet → ReadMsg() → parser.Parse() → stmtBuffer.Push(ExecStmt)
2. connExecutor.execCmd():
Dequeues ExecStmt → execStmtInOpenState() → opens kv.Txn (implicit txn)
→ dispatchToExecutionEngine()
3. sql.planner.makeOptimizerPlan():
AST → optbuilder.Builder.Build() → memo.Memo
→ xform.Optimizer.Optimize() → explore transformation rules, cost each plan
→ select lowest-cost RelExpr as logical plan
4. DistSQLPlanner.PlanAndRun():
Logical plan → PhysicalPlan (assign each operator to a node based on range locality)
For a local single-node query: construct a local flow (no SetupFlow RPCs)
→ instantiate Processors (row-by-row) or colexec operators (vectorized)
5. Processor.Run():
TableReader processor → kv.Txn.Scan() or Get()
6. kv.Txn → TxnCoordSender.Send():
Wraps batch in interceptor chain (sequence numbers, intent tracking, span refresh)
→ DistSender.Send()
7. DistSender.Send():
Look up range descriptor + leaseholder in RangeCache
→ send BatchRequest via nodedialer (loopback for local node)
8. Node.BatchInternal():
Fan-out to Store.Send() for matching range
9. Store → Replica.Send():
Check lease validity → execute read against MVCC storage (no Raft needed for reads)
→ storage.MVCCGet/Scan() on Pebble engine
→ return BatchResponse up the chain
10. Results flow back through Processor → DistSQL flow → connExecutor
→ pgwire serializes rows → sends PostgreSQL DataRow messages to clientA write that goes through Raft consensus#
Steps 1–7 same as above, then:
8. Replica.Send() for a write:
Leaseholder replica evaluates the write batch (batcheval package)
→ Replica.propose() → raft.RawNode.Propose(encoded command)
9. Raft consensus:
Leader appends entry to Raft log → sends AppendEntries to follower replicas
→ followers acknowledge → quorum reached
10. Raft Ready loop (raftScheduler goroutine):
RawNode.Ready() returns committed entries
→ Replica.handleCommittedEntriesRaftMuLocked()
→ apply entries to MVCC storage via storage.Batch.Commit()
→ wake up waiting proposer goroutine
11. Proposer returns BatchResponse up the call chain back to clientDistributed query (multi-node DistSQL)#
DistSQLPlanner detects ranges live on multiple nodes:
→ Generate FlowSpec for each remote node (TableReader on remote, join/aggregation on gateway)
→ Send SetupFlow RPC to each remote node
→ Remote nodes instantiate their Processor chains and start streaming
→ Gateway aggregates results from remote RowSources via network streams
→ connExecutor returns merged results to clientInitialization / Bootstrap#
CockroachDB uses manual dependency injection (no DI framework). NewServer() in pkg/server/server.go is ~1200 lines of explicit construction:
cli.Main() → runStartJoin() → NewServer(cfg, stopper)
1. clock = newClockFromConfig() // HLC clock
2. nodeRegistry/appRegistry/sysRegistry = metric.NewRegistry() × 3
3. engines = cfg.CreateEngines() // Pebble instances per --store flag
4. g = gossip.New() // gossip node
5. rpcContext = rpc.NewContext() // gRPC + DRPC context
6. grpcServer, drpcServer = newGRPCServer() // register gossip, init RPCs
7. kvNodeDialer = nodedialer.New() // range-aware dialer
8. distSender = kvcoord.NewDistSender() // range router
9. tcsFactory = kvcoord.NewTxnCoordSenderFactory()
10. db = kv.NewDB(tcsFactory, distSender) // kv.DB wraps the sender stack
11. node = NewNode(db, stores, ...) // registers as InternalServer
12. sqlServer = newSQLServer(cfg) // SQL engine, pgwire, optimizer
13. serverController = newServerController()// multi-tenant fleet manager
...
server.PreStart():
- Start HTTP listener, RPC server
- Initialize gossip, node liveness
- Bootstrap or join cluster (via initServer)
- Start Stores (opens Pebble, recovers Raft state)
- Start background work (GC, allocator, rebalancing)
server.AcceptClients():
- Start accepting pgwire SQL connections
- Start accepting HTTP/admin API connectionsStopper pattern: Every long-lived goroutine is launched via stopper.RunAsyncTask() or stopper.RunWorker(). On stopper.Stop(), all tasks receive context cancellation and drain gracefully. This is the universal shutdown coordination mechanism.
CCL activation: The main.go blank import _ "github.com/cockroachdb/cockroach/pkg/ccl" causes pkg/ccl/ccl_init.go to run, which transitively imports all CCL packages. Each CCL package’s init() registers hooks into core package function variables (e.g., jobs.MakeChangefeedMetricsHook), replacing nil function vars with real implementations. The OSS binary (cockroach-short) omits this import, so hooks stay nil and CCL features are unavailable.
Configuration#
CockroachDB has a two-tier configuration system:
Static configuration (startup flags / env vars)#
- Package:
pkg/cli/cliflagcfg,pkg/base,pkg/server.Config - Mechanism: Cobra persistent flags (
--join,--listen-addr,--store,--max-sql-memory, etc.) bound tobase.Configandserver.Configstructs - Env var fallback:
pkg/util/envutilprovidesEnvOrDefaultXxx()for overriding flag defaults viaCOCKROACH_*environment variables - Example:
COCKROACH_BLOCK_PROFILE_RATE=100overrides block profiling rate
Dynamic configuration (cluster settings)#
- Package:
pkg/settings,pkg/settings/cluster - Mechanism: Each setting is a typed global variable declared with
settings.RegisterXxxSetting(). Values are stored in thesettings.Valuescontainer (part ofcluster.Settings). Operators change settings withSET CLUSTER SETTINGSQL, which propagates via gossip + internal KV writes to all nodes. - Key type:
cluster.Settings— a single shared instance per node, passed everywhere. ContainsSV settings.Values,Version clusterversion.Handle,Manual atomic.Value. - Example:
kv.rangefeed.enabledis a cluster setting controlling RangeFeed availability; changing it takes effect without restart.
Key design decisions#
1. kv.Sender as the universal KV interface#
The single-method Sender interface — Send(context.Context, *BatchRequest) (*BatchResponse, *Error) — is implemented by every component in the KV stack: kv.Txn, TxnCoordSender, DistSender, Node, Store, Replica. This creates a uniform call chain from SQL down to storage. The simplicity of the interface hides enormous complexity (the code comment in sender.go candidly notes it is “now considered regrettable because it’s too narrow and at times leaky”), but it enables a clean interceptor chain at the TxnCoordSender level.
2. Cascades cost-based optimizer with optgen code generation#
The query optimizer uses the Cascades framework, which explores a space of equivalent query plans using transformation rules. CockroachDB generates these rules from a DSL (optgen) into Go code. This is unusual: most Go databases use hand-written or yacc-based approaches. The generated memo and rule system allows the optimizer to be extended by adding rule files without modifying core data structures. The SQL parser itself is generated by goyacc from a PostgreSQL-derived grammar.
3. Multi-tenancy via process-in-process SQL servers#
The serverController runs multiple SQL server instances (SQLServerWrapper) inside a single KV node process. Each tenant has full SQL isolation (separate connExecutor pool, separate schema catalog, separate admission control) but shares the underlying KV storage. Tenant routing happens at the pgwire.PreServeConnHandler level — connections are dispatched to the correct tenant’s SQL server based on SNI or connection parameters before authentication. This is how CockroachDB Serverless achieves tenant isolation without a separate process per tenant.
4. Distributed SQL execution with leaf transaction state#
DistSQL flows fan out SQL execution across multiple nodes, each running Processor chains. Since a single SQL kv.Txn cannot safely be used concurrently from multiple goroutines, CockroachDB uses RootTxn / LeafTxn split: the gateway node owns the root transaction; remote nodes run leaf transactions that accumulate write intents and return them to the root for consolidation at commit. This allows distributed parallel reads and writes within a single ACID transaction.
5. Admission control as a first-class subsystem#
pkg/util/admission implements multi-resource admission control for CPU and storage I/O (LSM compaction backpressure). Work items have a WorkPriority (e.g., KVWork, SQLKVResponseWork, SQLSQLResponseWork). When a resource is saturated, lower-priority work is queued in admission queues rather than consuming goroutine scheduler slots. This prevents priority inversion and maintains throughput under load — a concern unique to databases, not typical Go services.
6. Hybrid Logical Clock (HLC) for distributed causality#
CockroachDB does not use physical wall-clock time for MVCC timestamps. Instead, pkg/util/hlc provides an HLC: a (walltime, logical) pair that advances monotonically across the cluster. Every node’s clock is kept within a maxOffset (default 500ms) of true time, enforced at RPC boundaries. This enables snapshot reads at a point in time (for AS OF SYSTEM TIME queries) and serializable isolation without a centralized timestamp oracle. Clock skew violations cause node crashes to preserve correctness — a dramatic but necessary safety guarantee.
7. init()-hook CCL injection for OSS/enterprise split#
The open-source binary defines all feature interfaces (empty function variables, nil hooks). The commercial binary activates them through Go’s init() execution chain triggered by a single blank import. This is architecturally elegant — the OSS codebase compiles and ships independently, and no #ifdef-style build tags pepper the core code — but it means that the CCL packages’ init() functions run before main(), making startup ordering implicit rather than explicit. The pattern scales to the ~15 CCL feature packages currently using it.