Service Discovery Pattern A Solution
A service registry is a live, in-memory key–value store keyed by logical service name: each instance writes its own host:port on startup and keeps that entry alive by renewing a time-bounded lease with periodic heartbeats, so a caller resolves "where is payment-service?" against the current set of live addresses at request time instead of against a config file baked in at deploy time.
That one move — replacing a static binding with a runtime lookup — is what lets instances come, go, move, and scale without anyone re-configuring their callers. Everything else (load balancing, decoupled config, self-healing) falls out of it.
The two topologies
Discovery splits on who does the lookup and picks the instance. This choice drives almost every trade-off later.
- Client-side discovery — the caller queries the registry itself, gets the full list of live instances, and runs its own load-balancing algorithm (round-robin, least-connections, zone-aware). Netflix Eureka + Ribbon is the canonical example. Smart client, dumb infrastructure.
- Server-side discovery — the caller sends to one stable virtual address (a load balancer, router, or Kubernetes
ServiceIP). That intermediary consults the registry and picks the instance. Kubernetes (kube-proxy + CoreDNS), AWS ALB, and Consul-with-a-proxy work this way. Dumb client, smart infrastructure.
The lifecycle: register, heartbeat, expire
The registry never trusts an entry forever — that would make crashed instances immortal. Each registration carries a lease with a TTL. The instance must renew (heartbeat) before the lease expires, or the registry evicts it. This heartbeat loop is the heart of the pattern and the source of its hardest failure mode.
Worked trace: order-service → payment-service
Two payment-service instances, an Eureka-style registry with TTL = 30s and a 10s heartbeat. Instance A crashes mid-stream. Follow the registry contents and what the caller sees.
| t | Actor / event | Registry state for payment-service | What order-service observes |
|---|---|---|---|
| 0s | A starts, POST /register {A: 10.0.1.7:8443}, lease→30s | [A@10.0.1.7:8443] | — |
| 2s | B starts, registers, lease→30s | [A@10.0.1.7, B@10.0.1.9] | — |
| 10s,20s | A & B heartbeat; leases reset to 30s | [A, B] both fresh | — |
| 25s | order-svc looks up, gets both, round-robins → picks A | [A, B] | calls A → 200 OK |
| 40s | A & B heartbeat (A's last successful renew) | [A, B] lease-A expires at 70s | — |
| 42s | A crashes — process dead, no de-register sent | [A(zombie), B] | — |
| 45s | order-svc looks up, still sees A, round-robins → picks A | [A(zombie), B] | connect timeout → error |
| 50s | order-svc retries, picks B | [A(zombie), B] | calls B → 200 OK |
| 70s | lease-A expires; registry evicts A | [B] | — |
| 75s+ | lookups return only B | [B] | always hits live B |
The lesson is in the 28-second window (42s→70s): a crashed instance is still advertised until its lease lapses. Graceful shutdown (explicit de-register) closes this window; a hard crash does not. This is why callers must pair discovery with retries, timeouts, and circuit breakers — discovery alone cannot guarantee the address it hands you is alive right now.
Pitfalls
- The registry is a SPOF. If discovery goes down, no one can find anyone and the whole mesh can grey out. Mitigate by clustering the registry (Eureka peer replication, Consul/etcd Raft quorum) and by having clients cache the last-known list and keep using it if the registry is unreachable — stale routing beats no routing.
- TTL is a tuning trap. Long TTL = long stale window routing traffic to dead instances (the trace above). Short TTL = heartbeat storms and flapping: a brief GC pause or network blip misses one heartbeat and a healthy instance gets evicted and re-added, churning every caller's routing table. Eureka's default 30s renew / 90s expiry is deliberately conservative.
- Self-preservation / split-brain. When many instances stop heartbeating at once, is it 50 crashes or one dead network link to the registry? Eureka's self-preservation mode chooses to stop evicting and keep serving possibly-stale entries (AP: it would rather over-report live than wrongly evict healthy nodes). A CP registry (Consul, etcd) would instead refuse writes without quorum. Neither is "correct" — it is a CAP choice you inherit.
- Zombie entries from hard crashes. Health checks (Consul actively probes
/health; Eureka only trusts heartbeats) catch instances that are up-but-unhealthy — e.g. running but with a dead DB pool. Heartbeat-only registries will happily route to a process that is alive but broken. - Stale client cache in client-side discovery. Ribbon-style clients refresh their instance list on an interval (often 30s). A newly scaled-up instance may get zero traffic for that interval; a scaled-down one may still get traffic. Server-side discovery centralizes this and shrinks the staleness window.
- DNS TTL caching bites. If you use plain DNS as the registry, resolvers and JVMs cache A-records past their usefulness (the JVM's infamous
networkaddress.cache.ttlhistorically defaulted to "forever"), and DNS gives you no health awareness and no port unless you use SRV records.
When to use it — and when not
Reach for explicit service discovery when instance addresses are ephemeral (autoscaling, container reschedules, spot instances), you have many services calling many services, and you want deploys/scaling to happen without touching caller config. These are the signals: dynamic IPs, horizontal scaling, and blue-green/rolling deploys.
Skip it when you have a handful of long-lived services on fixed hosts — a static config file or a single well-known load balancer VIP is simpler and has no SPOF to operate. Do not build a registry to solve a problem you do not have.
Choosing the mechanism
| Option | Model | You gain | It costs |
|---|---|---|---|
| Kubernetes Service + CoreDNS | Server-side, DNS-fronted | Free with the platform; no client library; endpoints controller tracks pod health automatically | Locked to k8s networking; kube-proxy adds a hop; DNS caching quirks |
| Netflix Eureka (+ Ribbon) | Client-side, AP | No extra network hop; client-side LB with zone-awareness; survives registry partitions (self-preservation) | Fat client library per language; stale reads possible; you operate the Eureka cluster; effectively JVM-centric |
| Consul / etcd | CP (Raft), active health checks | Strong consistency; real health probing; DNS and HTTP APIs; also does config + KV | Loses availability under quorum loss; more moving parts; heavier ops |
| Plain DNS (SRV records) | Server-side, dumb | Zero new infrastructure; every language already speaks DNS | No health awareness; TTL-driven staleness; no rich metadata; slow to converge |
Choose Kubernetes-native discovery when you already run on k8s — rolling your own Eureka there is redundant. Choose Eureka when you are on a JVM/Spring Cloud stack outside k8s and value availability over consistency (routing to a slightly stale list is acceptable). Choose Consul/etcd when you need genuine health checks, cross-language support, and can tolerate a CP registry that stops accepting writes rather than serve stale data. Choose plain DNS only for coarse, slow-changing topologies where operating a registry is not worth it.
Takeaways
- Discovery replaces a static address binding with a runtime lookup keyed by logical name; the lease + heartbeat loop is what keeps the registry's view current.
- The client-side vs server-side split is the primary design axis: smart client + no hop (Eureka) vs dumb client + centralized routing (k8s Service, ALB).
- A registry is a SPOF and its TTL is a staleness-vs-flapping dial — always cache the last-known list on the client and pair discovery with retries, timeouts, and circuit breakers.
- The registry's CAP posture (Eureka = AP/self-preservation, Consul/etcd = CP/quorum) is a decision you inherit, so choose it deliberately for your failure tolerance.
Sources: Chris Richardson, Microservices Patterns (Manning) and microservices.io — Service Registry, Client-side / Server-side Discovery patterns; Sam Newman, Building Microservices, 2nd ed. (O'Reilly) on service discovery and DNS trade-offs; the Netflix Eureka and HashiCorp Consul documentation for heartbeat/TTL, self-preservation, and health-check behavior; the Kubernetes documentation on Services, EndpointSlices, and CoreDNS. Re-authored / Deepened for this guide.
🤖 Don't fully get this? Learn it with Claude
Stuck on Service Discovery Pattern A Solution? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.
Progressively stronger hints — you still solve it.
I'm working on the problem **Service Discovery Pattern A Solution** (System Design). Give me a HINT LADDER: start with the tiniest nudge, then wait. Only reveal the next, stronger hint when I ask. Do NOT show the full solution unless I type 'show solution'. Keep me doing the thinking. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
See the technique, not just code.
Explain the optimal approach to **Service Discovery Pattern A Solution** with a VISUAL walkthrough: trace it on a small concrete example using ASCII art / a step-by-step diagram, narrate what changes each step, then give time & space complexity with a one-line derivation. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
Catch bugs, edge cases, sub-optimality.
I'll paste my solution to **Service Discovery Pattern A Solution**. Review it for correctness, missed edge cases, and time/space complexity, then coach me toward the optimal — don't just rewrite it. Ask me to paste my code now. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
Lock in recognition with look-alikes.
Give me 2 problems that use the SAME underlying pattern as **Service Discovery Pattern A Solution**. For each, let me attempt first, then review my answer and name the trigger signal that reveals the pattern. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.