CMD Guide
HomeSystem DesignMicroservices Patterns

What is Service Discovery Pattern

What is the Service Discovery Pattern

In a monolith, one function calls another by name and the linker resolves the address at compile time. In a microservices system that guarantee disappears. Your orders service needs to call payments, but payments is not a fixed machine — it is a herd of ephemeral instances that autoscaling spins up and kills, that crash and restart, that get rescheduled onto different hosts by Kubernetes, each landing on a fresh IP and port. Hard-coding 10.0.3.14:8080 into orders is a trap: the instance behind that address will be gone in an hour.

Service discovery is the mechanism that answers one question at runtime: "I want to talk to the payments service — which live instances exist right now, and where are they?" It decouples the logical name of a service from the physical network locations of its instances, so callers work with stable names while the underlying addresses churn freely underneath them.

How it works, precisely

Every service discovery setup has three moving parts. The service registry is a highly-available database of (service-name → list of instance endpoints), plus health metadata. This is the source of truth — Consul, etcd, ZooKeeper, Eureka, or the built-in registry inside Kubernetes (etcd behind the API server).

Registration puts instances into the registry. Two styles exist. In self-registration, the instance calls the registry on startup ("I am payments, I am at 10.0.3.14:8080") and sends periodic heartbeats; miss a few and the registry evicts it. In third-party registration, a separate registrar (the platform — e.g. Kubernetes watching pod readiness probes) registers and deregisters instances on the app's behalf, so application code stays clean.

Discovery is how a caller resolves the name. Again two styles:

Two properties make the registry usable at scale: health checking (only healthy instances are returned, via heartbeats, TTLs, or active probes) and caching (clients cache results for seconds to avoid hammering the registry, trading a little staleness for huge QPS reduction).

A worked scenario with numbers

Picture a checkout flow. The orders service handles 5,000 QPS and calls payments on every request. payments runs 20 instances behind autoscaling; during a flash sale it scales to 60 and back down within minutes, and roughly 3 instances per minute are being rotated (crashes, deploys, rescheduling).

If orders hit the registry on every call, that is 5,000 registry QPS from just one caller — the registry becomes a bottleneck and a single point of failure. Instead, each orders instance caches the instance list for ~30 seconds and refreshes in the background. Registry load drops to a trickle. The cost is bounded staleness: for up to 30 seconds after an instance dies, some clients still hold its address. Health checks with a 10-second heartbeat and a 30-second eviction TTL plus client-side retry-on-another-instance keep the blast radius tiny — a failed call to a dead .14 just retries .27, adding maybe 5–20 ms of retry latency to a fraction of requests rather than failing the checkout.

In Kubernetes this is mostly invisible: orders calls http://payments, DNS resolves the Service ClusterIP, and kube-proxy (or a mesh sidecar) load-balances across the current healthy pods, which the control plane keeps in sync with readiness probes. The pattern is the same — the platform just runs the registry and the server-side balancer for you.

Trade-offs: when to use, when not, versus the alternatives

Client-side vs server-side discovery. Client-side (Eureka/Ribbon) gives you smart, application-aware balancing (zone affinity, weighted routing) and removes an extra network hop and a component to run — but it couples every client to registry-specific logic and needs a discovery library per language, which hurts in a polyglot fleet. Server-side (K8s Service, load balancer, mesh) keeps clients trivial and language-agnostic and centralizes routing policy, at the cost of an extra hop and a piece of infrastructure that must itself be highly available.

Versus DNS alone. Plain DNS is a form of server-side discovery, but classic DNS is weak here: TTL caching by resolvers means stale records linger, and vanilla A-records carry no health or load info. It works when instance sets change slowly; it struggles with fast-churning ephemeral instances unless you add short TTLs and health-aware DNS (as SkyDNS/CoreDNS do).

Versus hard-coded config / a static load balancer VIP. Perfectly fine when you have a small, stable set of long-lived hosts. Reach for full service discovery only when instances are dynamic and numerous — autoscaling, frequent deploys, container orchestration. When NOT to use it: a monolith or a handful of fixed VMs behind one load balancer — a registry there is complexity with no payoff.

Versus a service mesh. A mesh (Istio, Linkerd) is service discovery plus mTLS, retries, circuit breaking, and observability, implemented with sidecars. Adopt it when you need those cross-cutting concerns fleet-wide; skip it if plain discovery already meets your needs, because the sidecar tax (latency, memory, operational surface) is real.

Pitfalls an interviewer probes

Client-side vs server-side discovery decision table

DimensionClient-side (Eureka + Ribbon / Consul SDK)Server-side (K8s Service / LB / Envoy)
Who picks the instance?The caller, using app-aware rules (zone, weight)The intermediary (kube-proxy / sidecar / LB)
Network hopsDirect to instance; no extra hopOne extra hop through proxy/sidecar
Language supportNeeds registry library per languageLanguage-agnostic; HTTP/DNS only
Routing intelligenceRich: canary, zone affinity, custom weightsCentralized policy; uniform for all callers
Operational burdenSDK versions, caching bugs, retry logic in every clientProxy/sidecar fleet to operate and debug
Typical homeNetflix-style Java fleets, custom platformsKubernetes, service mesh, cloud load balancers

Health-check + TTL expiry trace

TimeInstanceRegistry stateClient cacheWhat happens
t0payments-14 healthypayments-14 registeredList: [payments-14, payments-27]Traffic splits across both
t0+5 spayments-14 crashes (silent, no deregister)Still registeredStill cachedSome calls hit dead instance
t0+30 s3 missed 10 s heartbeats → lease (30 s) expiresRegistry evicts payments-14Still cached until client TTLClient retries to other instance
t0+60 s-Registry no longer lists itCache (30 s) expires, refresh fetches [payments-27, payments-31]Dead instance fully drained

These ticks match the Eureka config below: a 10 s heartbeat, a 30 s eviction TTL (three missed beats), plus a ~30 s client-cache refresh. The bounded staleness window is the sum of the eviction TTL and the client cache TTL — here up to ~60 s — so tune them, and lean on retries, to cover the gap without hammering the registry.

Config snippets: Consul, Eureka, Envoy

These are representative fragments, not production manifests, but they show the shape each system expects (note Consul's agent API uses PUT for registration — a common trip-up).

Consul service registration (HTTP API)

PUT /v1/agent/service/register
{
  "ID": "payments-14",
  "Name": "payments",
  "Tags": ["v1", "us-east"],
  "Port": 8080,
  "Check": {
    "HTTP": "http://10.0.3.14:8080/health",
    "Interval": "10s",
    "Timeout": "5s"
  }
}

Spring Eureka client (application.yml)

eureka:
  client:
    serviceUrl:
      defaultZone: http://eureka:8761/eureka/
  instance:
    leaseRenewalIntervalInSeconds: 10
    leaseExpirationDurationInSeconds: 30

Envoy cluster with EDS

clusters:
  - name: payments
    connect_timeout: 0.25s
    type: EDS
    eds_cluster_config:
      eds_config:
        path: /etc/envoy/endpoints.yaml

The common theme: register, heartbeat, and let callers resolve names to live endpoints.

Trap: split-brain and stale registry

A network partition can split the registry cluster itself. In a CP registry (etcd, ZooKeeper, Consul with consistency mode), a minority partition stops accepting writes so the instance list stays consistent but may become unavailable for updates. In an AP registry (Eureka), each partition keeps accepting registrations, so after healing the two sides may disagree on which instances are alive.

The practical defense is the same for both: clients cache last-known-good endpoints, use short TTLs, and retry on a different instance. A stale registry entry should cause a retry, not an outage. Never trust the registry to be perfectly consistent; design for bounded staleness.

Key takeaways

🤖 Don't fully get this? Learn it with Claude

Stuck on What is Service Discovery Pattern? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.

🎨 Explain it visually

Build the mental picture, not memorization.

I just read a lesson on **What is Service Discovery Pattern** (System Design) and want to truly understand it. Explain What is Service Discovery Pattern from first principles using ONE vivid real-world analogy and a visual mental model — draw it as ASCII art or a clear step-by-step diagram — with a concrete example using real numbers. Then ask me one question to check I got the mental picture, and wait for my reply. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🤔 Walk me through it (interactive)

Socratic — adapts to where you're stuck.

Teach me **What is Service Discovery Pattern** interactively. Ask me ONE guiding question at a time, wait for my answer, and adapt to my confusion — build the idea with me step by step instead of explaining it all at once. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧪 Quiz me & fix my gaps

Active recall exposes what you missed.

Quiz me on **What is Service Discovery Pattern** with 5 questions, easy to tricky, ONE at a time. Tell me if each answer is right; at the end, explain clearly what I got wrong and why. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧠 Make it stick

Intuition + hook + flashcards for long-term memory.

Help me remember **What is Service Discovery Pattern** for the long term: give the one-sentence intuition, a memorable hook/mnemonic, a tiny worked example, and 3 active-recall flashcards (Q -> A). If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.

📝 My notes