Architecture

etcd Architecture: How the Codebase Actually Works

etcd-io/etcd Apache-2.0 1 diagram
How to read this page

Symvanta parsed this repository into a code graph: every function, class, and method is a node, and every call or import between them is an edge. Everything on this page is computed from that graph at the commit shown above. The terms:

Module (or cluster)
A group of symbols that call each other far more than they call anything else. An algorithm called Louvain community detection finds these groups from the call traffic alone; nobody draws them by hand.
Modularity (the Q number)
A 0-to-1 score of how cleanly those groups separate. Higher means more call traffic stays inside its own group; scores around 0.7 and above read as clean boundaries.
Hub
The most depended-upon symbol inside one module.
Load-bearing symbols
PageRank, the algorithm Google originally used to rank web pages, run over the call graph instead: it surfaces the functions the rest of the codebase leans on hardest.
Arrows and their numbers
How many calls cross from one module into another. A heavier arrow means tighter coupling between those two parts.
Dependency cycle
File A imports B, which imports A again, sometimes through a longer loop. Cycles are not bugs, but a change inside one tends to ripple around the whole loop.
Mutually recursive symbols
Functions that call each other, usually the natural shape of parsers and tree-walking code.

etcd is a distributed, reliable key-value store built on the Raft consensus protocol: the coordination store that Kubernetes and a long list of other distributed systems rely on to hold cluster state consistently across machines. Clients talk to it over gRPC (Range, Put, Delete, Txn, Watch, LeaseGrant, and friends), and every write that changes the store's state has to be proposed to and committed by Raft before it lands in the underlying storage engine. Symvanta's Louvain community detection organized the codebase's indexed symbols into 136 functional clusters (modularity Q=0.79, a clean separation of concerns for a codebase this size, with zero circular dependencies between clusters). The largest cluster on the map, Etcd Cluster Operations (506 symbols, hub rpctypes.EtcdError.Code), is the client-facing surface: the clientv3 request and option types (RangeRequest, WatchRequest, LeaseGrantRequest, WithRev, WithSerializable) and the gRPC error-code translation almost every one of them eventually routes through. Underneath it sit the pieces that do the actual work: backend and storage configuration, the accessor the server uses to identify itself to the rest of the cluster, and the mvcc, WAL, and snapshot data structures that give etcd its durability guarantees.

Module map

The diagram below shows the 10 largest of the 136 detected modules, sized by symbol count, with arrows weighted by how many calls cross between them. End-to-end and integration test clusters, several of them larger than any module shown here, are excluded: etcd's own e2e, integration, and testutil packages form some of the biggest Louvain communities in the codebase (one of them, at 667 symbols, is the end-to-end test harness), but they exercise the server rather than form part of it.

etcd-io/etcd module map: the 10 largest of 136 detected modules with call-weighted edges, generated by Symvanta
Module map of etcd-io/etcd, generated by Symvanta. Link to this diagram Open full size

Where to start reading

In a 136-module codebase, finding the handful of symbols everything else depends on is exactly the kind of task that trips up AI coding agents working from a file tree and grep instead of a call graph. These 10 are blended from the global PageRank ranking and the hub of each product-shaped module in the diagram above, so a load-bearing symbol from a smaller module isn't crowded out by a generic utility that simply gets called more often: start here to orient in the call graph.

A few of these are worth calling out individually. rpctypes.EtcdError.Code (api/v3rpc/rpctypes/error.go) sits on the boundary every client-facing RPC crosses: it converts etcd's internal error values into the gRPC status codes clients actually see. Op is clientv3.Op (client/v3/op.go), the struct every Get, Put, Delete, and Txn call builds before it goes out over the wire, and the same type transactions compose from. wal.NewDecoder and wal.NewDecoderAdvanced are the two entry points into decoding etcd's write-ahead log, used both by a restarting server replaying its log and by etcdutl's offline inspection tools. cobrautl.ExitWithError is the shared CLI-error path every etcdctl command falls back to. version.LessThan is the version-comparison helper cluster-upgrade and cluster-downgrade code calls before deciding whether a feature is safe to use across a mixed-version cluster. MemberID is a small interface method (server/etcdserver/corrupt.go) that returns the local raft member's own identity; corruption-checking and membership code call it constantly, which is also why its Louvain cluster (below) picked up some unexpected company.

Key subsystems

Etcd Cluster Operations

The largest cluster on the map at 506 symbols. Its hub, rpctypes.EtcdError.Code, is the error-translation boundary between etcd's internal error values and the gRPC status codes returned to clients, and the rest of the cluster is dominated by the clientv3 request and option types built around it: RangeRequest, WatchRequest, LeaseGrantRequest, OpOption, WithRev, WithSerializable. This is the vocabulary every etcd client, including etcd's own etcdctl and the in-repo integration tests, uses to talk to a cluster.

Etcd Cluster Management

Hub: MemberID, the same local-member-identity accessor called out above. Its home module is genuinely mixed: alongside MemberID sit real membership and consensus symbols, but a large share of the cluster's 328 symbols (EtcdRequest, EtcdResponse, EtcdState, MaybeEtcdResponse) belong to etcd's own linearizability test model (tests/robustness/model), not production cluster-management code. The model's request and response types mirror the real etcd request and response shapes closely enough that Louvain's call-graph clustering grouped them with the production code they model.

Data Structure Management

314 symbols. The cluster's highest-traffic symbol is a logger method (etcdserver.zapRaftLogger.Panic, called from nearly everywhere as an error path), but its thematic center is the durability layer: types.URLs.Sort and types.NewURLs for peer-URL handling, mvcc.newTreeIndex and treeIndex for the mvcc key-space index, and snap.Snapshotter.snapNames for naming and locating on-disk snapshots.

Client Operations

307 symbols, hub clientv3.Op. This is the CRUD layer of the client library: OpPut, OpGet, OpDelete, and Cmp (etcd's compare-and-swap comparator, used by Txn) all build or compose Op values, which is why this cluster and Etcd Cluster Operations share the heaviest cross-module edge on the map.

Backend Configuration Management

482 symbols, hub traceutil.TODO (a placeholder trace constructor called from code paths that don't yet thread a real trace context through). The cluster's real subject is backend configuration: backend.DefaultBackendConfig, BackendConfig, and StoreConfig define how etcd's bolt-backed storage engine is set up, and betesting.NewTmpBackend and its variants, the harness tests use to spin up a throwaway backend, cluster alongside them because so much of the test suite calls straight into the same configuration constructors.

Store Configuration Utilities

286 symbols, hub Set (a small TTLOptionSet-style configuration type). The cluster also carries concurrency.WithContext and etcdserver.EtcdServer.getCommittedIndex, the same committed-index accessor processInternalRaftRequestOnce calls in the request flow below.

Shared infrastructure

Two smaller clusters round out the shared infrastructure the modules above call into. Command Execution Utilities (hub: cobrautl.ExitWithError) is the CLI plumbing etcdctl and etcdutl share for parsing commands, resolving client config, and reporting errors. Data Parsing Utilities (hub: wal.NewDecoderAdvanced) is the WAL and snapshot decoding layer both the running server and the offline etcdutl tools read through.

Canonical request flow

etcd has no plain HTTP surface of its own (the client API is gRPC), so the flow traced here is a client Put reaching consensus, as far as the graph's static call edges follow it:

  1. etcdserver.EtcdServer.Put (server/etcdserver/v3_server.go:295) is the gRPC handler for a client's Put request. It wraps the request in an InternalRaftRequest and hands it to raftRequest.
  2. etcdserver.EtcdServer.raftRequest (server/etcdserver/v3_server.go:1012) brackets the call with a traceutil.Trace (SetStartTime, GetStartTime, InsertStep, LogIfLong) so a slow proposal gets logged, then calls processInternalRaftRequestOnce.
  3. etcdserver.EtcdServer.processInternalRaftRequestOnce (server/etcdserver/v3_server.go:1058-1133) assigns the request a unique ID (idutil.Generator.Next), classifies it (getRequestType), and checks exceedsRequestLimit, which compares the server's committed and applied Raft indexes so a client can't propose faster than the server can keep up. Any proposal-context error is translated by parseProposeCtxErr before it reaches the caller.

The traced chain stops there. Submitting the wrapped request to Raft, and the eventual write into etcd's mvcc storage once that entry commits, cross a channel boundary (etcd's own raft.Node.Propose and the apply loop that reads committed entries off the Ready channel) rather than a direct function call, so the static call graph has no edge to follow across it. That gap is independently visible in the graph itself: apply.applierV3backend.Put (server/etcdserver/apply/backend.go:50), the function that actually writes the key into mvcc once Raft has committed the entry, has zero callers on record, exactly what you'd expect from a handler the apply loop dispatches dynamically rather than calls directly.

Health signals

Symvanta detected 0 dependency cycles across 136 modules (modularity Q=0.79): every cluster's call traffic resolves without a circular dependency between clusters. Symvanta also detected 7 sets of mutually recursive symbols, all two-symbol pairs. Two are worth naming: apply.checkTxnPermission and apply.checkTxnReqsPermission, in the request-authorization path, check a transaction's operations by recursing into its nested sub-transactions, and clientv3.Op.toTxnRequest/clientv3.Op.toRequestOp convert an Op tree into its wire representation the same way. The remaining four pairs sit in gRPC-proxy request translation (grpcproxy), key-prefix namespacing (namespace), request validation (v3rpc), and the discovery service's retry logic (v3discovery), each a small, self-contained two-symbol pair.

See your own codebase mapped like this.

Book a demo →

Auto-generated by Symvanta from the public repo etcd-io/etcd at commit 6006f40 , licensed Apache-2.0 .

Get this for your codebase →