Skip to content

Instantly share code, notes, and snippets.

@dims
Last active February 11, 2026 18:05
Show Gist options
  • Select an option

  • Save dims/20fa8ab4fb1bbeef470d96228753677f to your computer and use it in GitHub Desktop.

Select an option

Save dims/20fa8ab4fb1bbeef470d96228753677f to your computer and use it in GitHub Desktop.
etcd Dependency Analysis Report (depstat)
Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

Etcd Dependency Analysis Report

Project: etcd (main branch, commit a5649248231) Modules: 10 Go modules analyzed (13 total; 3 tools modules auto-excluded by depstat) Analysis date: February 11, 2026 Tool: depstat (built from source)


1. Executive Summary

Etcd is a CNCF graduated distributed key-value store with a 13-module monorepo architecture. This analysis uses depstat to profile dependency scope, depth, cycles, archived dependencies, and change trends across the 10 non-tools modules (tools modules are excluded by default as they pull in linters and GCP SDK).

Key Findings

Metric All 10 Modules Core 8 (excl. tests)
Direct dependencies 87 81
Transitive dependencies 113 113
Total dependencies 141 139
Max dependency depth 12 12
Metric Value
Non-test dependencies 123
Test-only dependencies 18
Dependency cycles 13 (7 two-node)
Archived dependencies 8

Headlines

  • Lean dependency footprint. 141 total dependencies across 10 modules with a max depth of 12 — a well-managed graph for a project of this scope.
  • Cycles are minimal and external. Only 13 cycles detected, all originating from external ecosystems (OTel, protobuf, prometheus, x/crypto). No etcd-internal cycles.
  • Large dependency diet from v3.5.0 to v3.6.0. Net reduction of 51 deps (92 removed, 41 added), driven by removal of the HashiCorp stack and legacy gRPC/gateway v1 patterns.
  • 8 archived dependencies should be tracked for replacement — notably opentracing-go, pkg/errors, json-iterator/go, and google/btree.

2. Module Architecture

Etcd uses a 13-module monorepo (via go.work):

go.etcd.io/etcd/
├── go.mod              -> go.etcd.io/etcd/v3
├── api/go.mod          -> go.etcd.io/etcd/api/v3
├── cache/go.mod        -> go.etcd.io/etcd/cache/v3
├── client/pkg/go.mod   -> go.etcd.io/etcd/client/pkg/v3
├── client/v3/go.mod    -> go.etcd.io/etcd/client/v3
├── etcdctl/go.mod      -> go.etcd.io/etcd/etcdctl/v3
├── etcdutl/go.mod      -> go.etcd.io/etcd/etcdutl/v3
├── pkg/go.mod          -> go.etcd.io/etcd/pkg/v3
├── server/go.mod       -> go.etcd.io/etcd/server/v3
├── tests/go.mod        -> go.etcd.io/etcd/tests/v3
├── tools/mod/go.mod    -> go.etcd.io/etcd/tools/v3          (excluded)
├── tools/rw-heatmaps/  -> go.etcd.io/etcd/tools/rw-heatmaps/v3  (excluded)
└── tools/testgrid/     -> go.etcd.io/etcd/tools/testgrid-analysis/v3 (excluded)

Module Dependency Layering

v3 (main)
├── tests/v3
│   ├── etcdctl/v3
│   ├── etcdutl/v3 -> server/v3
│   └── server/v3
├── etcdctl/v3 -> client/v3
├── etcdutl/v3 -> server/v3
├── server/v3
│   ├── client/v3 -> api/v3, client/pkg/v3
│   ├── pkg/v3 -> client/pkg/v3
│   └── api/v3
├── cache/v3 -> client/v3
└── tools/v3 (independent, pulls in linters + GCP SDK)

3. Dependency Statistics

All 10 Modules (excl. tools)

Metric Value
Direct Dependencies 87
Transitive Dependencies 113
Total (deduplicated) 141
Max Dependency Depth 12

Core 8 Modules (excl. tools + tests)

Metric Value
Direct Dependencies 81
Transitive Dependencies 113
Total (deduplicated) 139
Max Dependency Depth 12

Test vs Non-Test Split

Category Count
Non-test 123
Test-only 18
Combined 141

4. Graph Characteristics

Top In-Degree Dependencies (most depended upon)

Rank Module In-Degree Out-Degree
1 go (stdlib) 33 0
2 golang.org/x/sys 30 1
3 gopkg.in/yaml.v3 29 1
4 github.com/davecgh/go-spew 27 0
5 github.com/stretchr/testify 27 4
6 github.com/pmezard/go-difflib 26 0
7 google.golang.org/protobuf 23 4
8 golang.org/x/net 22 5
9 golang.org/x/text 20 4
10 github.com/google/go-cmp 18 1

Top Out-Degree Modules (most fan-out)

Rank Module Out-Degree In-Degree
1 go.etcd.io/etcd/tests/v3 88 1
2 go.etcd.io/etcd/v3 83 0
3 go.etcd.io/etcd/etcdutl/v3 70 2
4 go.etcd.io/etcd/server/v3 69 3
5 go.etcd.io/etcd/etcdctl/v3 37 2
6 go.etcd.io/etcd/client/v3 33 5
7 google.golang.org/grpc 32 17
8 github.com/prometheus/common 26 7
9 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc 25 4
10 github.com/grpc-ecosystem/go-grpc-middleware/v2 22 6

Figure 1. Full dependency graph (10 modules):

Full dependency graph

Figure 2. Core dependency graph (8 modules, excl. tools + tests):

Core dependency graph


5. Cycles

Metric Value
Total cycles 13
2-node cycles 7

Two-Node Cycles

Cycle Ecosystem
github.com/golang/protobufgoogle.golang.org/protobuf Protobuf
github.com/prometheus/client_golanggithub.com/prometheus/common Prometheus
go.opentelemetry.io/auto/sdkgo.opentelemetry.io/otel OTel
go.opentelemetry.io/otelgo.opentelemetry.io/otel/metric OTel
go.opentelemetry.io/otelgo.opentelemetry.io/otel/trace OTel
go.opentelemetry.io/otel/sdkgo.opentelemetry.io/otel/sdk/metric OTel
golang.org/x/cryptogolang.org/x/net Go stdlib extended

Top Cycle Participants

Module Cycle Count
go.opentelemetry.io/otel 7
go.opentelemetry.io/auto/sdk 4
go.opentelemetry.io/otel/metric 4
go.opentelemetry.io/otel/trace 4
golang.org/x/net 3

All cycles originate from external ecosystems. No etcd-internal cycles.


6. Archived Dependencies

8 archived dependencies detected:

Module Version Repository
census-instrumentation/opencensus-proto v0.2.1 GitHub
client9/misspell v0.3.4 GitHub
golang/mock v1.1.1 GitHub
google/btree v1.1.3 GitHub
json-iterator/go v1.1.12 GitHub
opentracing/opentracing-go v1.1.0 GitHub
pkg/errors v0.8.1 GitHub
golang/lint v0.0.0-2019... GitHub

Priority replacements:

  • opentracing-go → already superseded by OTel (likely transitive only)
  • pkg/errors → use fmt.Errorf with %w or stdlib errors
  • json-iterator/go → use encoding/json or go-json
  • golang/mock → use go.uber.org/mock (maintained fork)
  • google/btree → check if still needed or switch to stdlib generics

7. Release Diff: v3.5.0 → v3.6.0

Summary

Metric v3.5.0 v3.6.0 Delta
Direct Deps 51 87 +36
Transitive Deps 178 113 -65
Total Deps 192 141 -51
Max Depth 12 12 ±0

Module graph: +41 added, -92 removed, ~70 version changes

Major Removals (92 deps removed)

  • HashiCorp stack removedconsul, serf, memberlist, hcl, viper, and 15+ related modules
  • Legacy gRPC ecosystemgrpc-gateway v1, go-grpc-prometheus, coreos-era deps (coreos/bbolt, coreos/etcd, coreos/go-systemd, coreos/pkg)
  • GCP SDK mass removalcloud.google.com/go/* (bigquery, datastore, firestore, pubsub, storage)
  • Deprecated JWTdgrijalva/jwt-go, form3tech-oss/jwt-go
  • Misc cleanupshurcooL, smartystreets, mitchellh/*, urfave/cli

Major Additions (41 deps added)

  • OTel stackgo.opentelemetry.io/auto/sdk, contrib/detectors/gcp, otel/exporters/otlp/otlptrace/otlptracegrpc
  • CEL + protovalidategithub.com/google/cel-go, buf.build/gen/go/bufbuild/protovalidate
  • Raft extractiongo.etcd.io/raft/v3 (moved to separate module)
  • Modern gRPC middlewarego-grpc-middleware/v2, grpc-gateway/v2
  • Modern JWTgolang-jwt/jwt/v5

Figure 3. Diff graph (v3.5.0 → v3.6.0):

Diff v3.5.0 to v3.6.0


8. Why-Traces (Key Dependencies)

gRPC (google.golang.org/grpc)

Directly depended on by 17 modules (8 etcd modules + 9 external).

Core RPC transport for etcd's client-server communication. Every etcd module from api/v3 through tests/v3 directly depends on gRPC.

Key etcd modules: api/v3, client/v3, etcdctl/v3, etcdutl/v3, pkg/v3, server/v3, tests/v3, v3

Figure 4. Why-trace for gRPC:

Why gRPC

OpenTelemetry (go.opentelemetry.io/otel)

Directly depended on by 12 modules (4 etcd modules + 8 OTel/external).

Tracing and metrics across server/client flows. Etcd modules etcdutl/v3, server/v3, tests/v3, v3 directly depend on OTel.

Figure 5. OTel dependency subgraph:

Why OTel

bbolt (go.etcd.io/bbolt)

Directly depended on by 4 etcd modules: etcdutl/v3, server/v3, tests/v3, v3.

Embedded B+tree storage engine for etcd's persistence layer. All paths are short (1-2 hops from main modules).

Figure 6. Why-trace for bbolt:

Why bbolt

Prometheus (github.com/prometheus/client_golang)

Directly depended on by 7 modules (5 etcd modules + 2 external).

Metrics instrumentation used throughout etcd. Enters the graph via client/v3 and propagates to etcdctl/v3, etcdutl/v3, server/v3, tests/v3, v3.

Figure 7. Why-trace for Prometheus client:

Why Prometheus

zap (go.uber.org/zap)

Directly depended on by 9 modules (8 etcd modules + 1 external).

Logging infrastructure rooted in client/pkg/v3 and propagated to nearly every etcd module.

Key etcd modules: client/pkg/v3, client/v3, etcdctl/v3, etcdutl/v3, pkg/v3, server/v3, tests/v3, v3

Figure 8. Why-trace for zap:

Why zap


9. Recommendations

  1. Replace archived dependencies — prioritize opentracing-go, pkg/errors, json-iterator/go, and golang/mock (see Section 6).
  2. Monitor OTel cycle complexity — OTel accounts for 7 of 13 cycles. As the OTel ecosystem matures, these may resolve upstream.
  3. Track tools module separately — the 3 tools modules are excluded from this analysis by design; consider periodic audits to prevent transitive bloat from linters.
  4. Continue dependency diet — the v3.5→v3.6 reduction of 51 deps was significant. Consider further pruning of indirect GCP/cloud deps that may no longer be needed.

10. Reproducibility

All commands run from the etcd repo root:

# Stats
depstat stats --json
depstat stats --json --exclude-modules "go.etcd.io/etcd/tools/*" --exclude-modules "go.etcd.io/etcd/tests/*"
depstat stats --split-test-only --json

# Graph
depstat graph --svg > etcd-full-graph.svg
depstat graph --svg --exclude-modules "go.etcd.io/etcd/tools/*" --exclude-modules "go.etcd.io/etcd/tests/*" > etcd-core-graph.svg
depstat graph --top both

# Cycles
depstat cycles --summary --json

# Archived
GITHUB_TOKEN=$(gh auth token) depstat archived --json

# Diff
depstat diff v3.5.0 v3.6.0 --verbose
depstat diff v3.5.0 v3.6.0 --svg > etcd-diff-v35-v36.svg

# Why-traces
depstat why google.golang.org/grpc --svg > etcd-why-grpc.svg
depstat graph -p go.opentelemetry.io/otel --svg > etcd-why-otel.svg
depstat why go.etcd.io/bbolt --svg > etcd-why-bbolt.svg
depstat why github.com/prometheus/client_golang --svg --max-paths 50 > etcd-why-client_golang.svg
depstat why go.uber.org/zap --svg > etcd-why-zap.svg
Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Display the source blob
Display the rendered blob
Raw
<svg xmlns="http://www.w3.org/2000/svg" width="506" height="714" viewBox="0 0 506 714" font-family="system-ui,-apple-system,sans-serif">
<defs>
<marker id="a" viewBox="0 0 10 6" refX="10" refY="3" markerWidth="8" markerHeight="5" orient="auto-start-reverse">
<path d="M0 0L10 3L0 6z" fill="#888"/>
</marker>
<marker id="ar" viewBox="0 0 10 6" refX="10" refY="3" markerWidth="8" markerHeight="5" orient="auto-start-reverse">
<path d="M0 0L10 3L0 6z" fill="#D32F2F"/>
</marker>
</defs>
<text x="252.8" y="28" text-anchor="middle" font-size="14" font-weight="600" fill="#333">Why is github.com/prometheus/client_golang included?</text>
<text x="252.8" y="46" text-anchor="middle" font-size="11" fill="#888">26 paths, 7 direct dependent(s)</text>
<rect x="16" y="60" width="12" height="12" rx="3" fill="#E8F5E9" stroke="#388E3C" stroke-width="1"/><text x="32" y="66" font-size="11" dominant-baseline="central" fill="#555">Main module</text><rect x="126" y="60" width="12" height="12" rx="3" fill="#E3F2FD" stroke="#1976D2" stroke-width="1"/><text x="142" y="66" font-size="11" dominant-baseline="central" fill="#555">Same org</text><rect x="236" y="60" width="12" height="12" rx="3" fill="#FFF3E0" stroke="#F57C00" stroke-width="1"/><text x="252" y="66" font-size="11" dominant-baseline="central" fill="#555">External</text><rect x="346" y="60" width="12" height="12" rx="3" fill="#FFE0E0" stroke="#D32F2F" stroke-width="1"/><text x="362" y="66" font-size="11" dominant-baseline="central" fill="#555">Target</text>
<path d="M365.2 124.0Q309.0 382.0 252.8 640.0" fill="none" stroke="#D32F2F" stroke-width="2.2" marker-end="url(#ar)"/>
<path d="M252.8 344.0Q252.8 437.0 252.8 530.0" fill="none" stroke="#888" stroke-width="1.3" marker-end="url(#a)" stroke-dasharray="5,3"/>
<path d="M365.2 124.0Q309.0 272.0 252.8 420.0" fill="none" stroke="#888" stroke-width="1.3" marker-end="url(#a)" stroke-dasharray="5,3"/>
<path d="M365.2 124.0Q309.0 217.0 252.8 310.0" fill="none" stroke="#888" stroke-width="1.3" marker-end="url(#a)" stroke-dasharray="5,3"/>
<path d="M252.8 234.0Q252.8 272.0 252.8 310.0" fill="none" stroke="#888" stroke-width="1.3" marker-end="url(#a)"/>
<path d="M252.8 344.0Q252.8 382.0 252.8 420.0" fill="none" stroke="#888" stroke-width="1.3" marker-end="url(#a)"/>
<path d="M252.8 454.0Q252.8 547.0 252.8 640.0" fill="none" stroke="#D32F2F" stroke-width="2.2" marker-end="url(#ar)"/>
<path d="M252.8 564.0Q252.8 602.0 252.8 640.0" fill="none" stroke="#D32F2F" stroke-width="2.2" marker-end="url(#ar)"/>
<path d="M365.2 124.0Q309.0 162.0 252.8 200.0" fill="none" stroke="#888" stroke-width="1.3" marker-end="url(#a)"/>
<path d="M252.8 234.0Q252.8 437.0 252.8 640.0" fill="none" stroke="#D32F2F" stroke-width="2.2" marker-end="url(#ar)"/>
<path d="M252.8 234.0Q252.8 327.0 252.8 420.0" fill="none" stroke="#888" stroke-width="1.3" marker-end="url(#a)" stroke-dasharray="5,3"/>
<path d="M140.4 124.0Q196.6 217.0 252.8 310.0" fill="none" stroke="#888" stroke-width="1.3" marker-end="url(#a)" stroke-dasharray="5,3"/>
<path d="M252.8 454.0Q252.8 492.0 252.8 530.0" fill="none" stroke="#888" stroke-width="1.3" marker-end="url(#a)"/>
<path d="M252.8 344.0Q252.8 492.0 252.8 640.0" fill="none" stroke="#D32F2F" stroke-width="2.2" marker-end="url(#ar)"/>
<path d="M365.2 124.0Q309.0 327.0 252.8 530.0" fill="none" stroke="#888" stroke-width="1.3" marker-end="url(#a)" stroke-dasharray="5,3"/>
<path d="M252.8 234.0Q252.8 382.0 252.8 530.0" fill="none" stroke="#888" stroke-width="1.3" marker-end="url(#a)" stroke-dasharray="5,3"/>
<g><title>github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus</title><rect x="115.0" y="420.0" width="275.6" height="34.0" rx="6" fill="#FFF3E0" stroke="#F57C00" stroke-width="1.5"/><text x="252.8" y="437.0" text-anchor="middle" dominant-baseline="central" font-size="11" fill="#E65100">github.com/grpc-e...viders/prometheus</text></g>
<g><title>github.com/prometheus/client_golang</title><rect x="121.8" y="640.0" width="262.0" height="34.0" rx="6" fill="#FFE0E0" stroke="#D32F2F" stroke-width="2"/><text x="252.8" y="657.0" text-anchor="middle" dominant-baseline="central" font-size="11" fill="#B71C1C">github.com/prometheus/client_golang</text></g>
<g><title>github.com/prometheus/common</title><rect x="145.6" y="530.0" width="214.4" height="34.0" rx="6" fill="#FFF3E0" stroke="#F57C00" stroke-width="1.5"/><text x="252.8" y="547.0" text-anchor="middle" dominant-baseline="central" font-size="11" fill="#E65100">github.com/prometheus/common</text></g>
<g><title>go.etcd.io/etcd/client/v3</title><rect x="155.8" y="310.0" width="194.0" height="34.0" rx="6" fill="#E8F5E9" stroke="#388E3C" stroke-width="2"/><text x="252.8" y="327.0" text-anchor="middle" dominant-baseline="central" font-size="11" fill="#1B5E20">go.etcd.io/etcd/client/v3</text></g>
<g><title>go.etcd.io/etcd/etcdctl/v3</title><rect x="40.0" y="90.0" width="200.8" height="34.0" rx="6" fill="#E8F5E9" stroke="#388E3C" stroke-width="2"/><text x="140.4" y="107.0" text-anchor="middle" dominant-baseline="central" font-size="11" fill="#1B5E20">go.etcd.io/etcd/etcdctl/v3</text></g>
<g><title>go.etcd.io/etcd/etcdutl/v3</title><rect x="264.8" y="90.0" width="200.8" height="34.0" rx="6" fill="#E8F5E9" stroke="#388E3C" stroke-width="2"/><text x="365.2" y="107.0" text-anchor="middle" dominant-baseline="central" font-size="11" fill="#1B5E20">go.etcd.io/etcd/etcdutl/v3</text></g>
<g><title>go.etcd.io/etcd/server/v3</title><rect x="155.8" y="200.0" width="194.0" height="34.0" rx="6" fill="#E8F5E9" stroke="#388E3C" stroke-width="2"/><text x="252.8" y="217.0" text-anchor="middle" dominant-baseline="central" font-size="11" fill="#1B5E20">go.etcd.io/etcd/server/v3</text></g>
<text x="252.8" y="702" text-anchor="middle" font-size="10" fill="#aaa">generated by depstat</text>
</svg>
Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment