Compare client-side and server-side service discovery: who queries the registry, who performs load balancing, and what does the calling service need to know about in each pattern?
answer
- caller does lookup vs LB does lookup
- Ribbon+Eureka = client-side
- Kubernetes Service/kube-proxy = server-side
- extra hop vs extra client complexity
- service mesh = hybrid via sidecar
basics
~10 sClient-side: the calling app looks up healthy instances itself and picks one. Server-side: the caller just calls a fixed address, and a separate component (load balancer/proxy) looks up instances and forwards the request.
solid answer
~40 sIn client-side discovery, the calling service (often via a library like Netflix Ribbon or a sidecar) queries the registry directly, gets a list of healthy instances, and applies its own load-balancing algorithm to pick one before calling it directly. This gives fine-grained control and cuts out a network hop, but couples every client to the registry's API and requires the discovery logic to be implemented in every language/service. In server-side discovery, the client just sends the request to a well-known address - a load balancer, API gateway, or Kubernetes Service ClusterIP - which itself queries the registry and forwards the call. This centralizes discovery logic and keeps clients simple and language-agnostic, at the cost of an extra hop and a potential bottleneck/single point of failure in the LB tier unless it's made highly available.
go deeper
Can restate which side does the lookup in each pattern given a diagram or example, but may need prompting to name real tools.
Names Eureka/Ribbon and Kubernetes Services as concrete examples and explains the coupling vs simplicity trade-off.
Discusses the extra-hop latency cost, LB HA requirements, and where service meshes fit as a hybrid.
Weighs the choice against organizational constraints (polyglot fleets, in-house platform maturity) and explains why most orgs converge on server-side/mesh patterns as they scale.
## The question that splits the two patterns Service discovery patterns split along one key question: which component is responsible for resolving 'a healthy instance of service X' into a concrete address - the calling service itself, or something sitting between the caller and callee? ## Client-side discovery Client-side discovery pushes that responsibility into the caller. The calling service embeds a discovery client - either a library linked into the process (classic examples: Netflix `Ribbon` paired with `Eureka`, or a hand-rolled client using Consul's HTTP API) - that periodically fetches or watches the registry for the current list of healthy instances of the target service. When a request needs to go out, the client applies a load-balancing algorithm locally (round robin, weighted round robin, least-connections, or something registry-aware like health-weighted selection) and calls the chosen instance directly, with no extra network hop. This pattern gives the caller precise control: - it can factor in its own view of latency; - it can retry a different instance on failure without another round trip through a shared load balancer; - it can make routing decisions (e.g. canary weighting, zone-aware routing to minimize cross-AZ traffic) that a generic LB might not support. The cost is **coupling**: every service that calls other services needs the discovery library, which means it has to exist in every language your organization uses, be kept in version lockstep, and its bugs/outages (e.g. a bad cache of stale instances) are now distributed across every caller rather than centralized in one place to fix. ## Server-side discovery Server-side discovery keeps the caller ignorant of the registry entirely. The caller sends its request to a fixed, well-known endpoint - a hardware or software load balancer, an API gateway, a sidecar proxy (`Envoy` in an Istio/service-mesh setup), or, in Kubernetes, a Service's stable `ClusterIP` - and that intermediary is the one that queries the registry (or, in Kubernetes' case, watches `Endpoints/EndpointSlices`) and forwards or proxies the request to a concrete healthy instance. The calling code doesn't need any discovery-aware logic at all; it just does a normal HTTP or TCP call to one address. This dramatically simplifies clients and makes the pattern **language-agnostic** - a Python client and a Java client behave identically because neither knows discovery is happening. The trade-off is an added network hop (caller to LB, LB to instance) which adds latency, and the LB/proxy tier itself becomes a piece of critical infrastructure that must be made highly available - if it goes down or falls behind on registry updates, everything behind it is unreachable even if the instances themselves are healthy. ## Two textbook examples, and a hybrid A useful way to see the split concretely: - **Netflix's original OSS stack (`Eureka` + `Ribbon` + `Zuul`)** is the textbook client-side example - services registered with Eureka, and Ribbon-equipped clients pulled the registry and load-balanced locally. - **Kubernetes' native model** is the textbook server-side example - Pods register implicitly by matching a Service's label selector, `kube-proxy` programs iptables/IPVS rules on every node so that traffic to a Service's ClusterIP gets load-balanced to a ready Pod, and the calling Pod just does a plain TCP/HTTP call, unaware that a Service resource, a controller, and a kernel-level NAT rule sit behind it. - **Service meshes (Istio, Linkerd)** are a hybrid: they keep the 'dumb client' ergonomics of server-side discovery from the app's point of view, but implement the smart routing/load-balancing that client-side discovery offers by running a sidecar proxy alongside every instance, so the intelligence is distributed per-hop rather than centralized in one shared LB tier or embedded as a library in app code. ## Choosing between them in practice Choosing between them in practice usually comes down to organizational and operational constraints rather than a technical ideal: - **polyglot shops** without the appetite to maintain a discovery client per language gravitate to server-side discovery or a mesh; - **teams with a single dominant runtime** and strong requirements for custom routing logic (e.g. sophisticated canary/AB splitting done in-process) may prefer client-side. Kubernetes-native shops usually just get server-side discovery for free and only reach for client-side patterns or a mesh when they need capabilities the built-in Service abstraction doesn't provide, like fine-grained traffic shifting or mutual TLS between services.
- Why is a service mesh like Istio often described as getting 'the best of both worlds'?Because from the application's point of view it looks like server-side discovery - the app just calls a local address (localhost or a plain service name) and knows nothing about the registry. But architecturally the actual load-balancing and routing decision happens in a sidecar proxy running right next to each instance, giving the same per-hop intelligence (retries, circuit breaking, zone-aware routing) that client-side libraries provide, without embedding a discovery library into every app's code.
- What happens to request latency and failure modes if the central load balancer in a server-side setup becomes unavailable?Every request depending on that LB fails or times out, even if all the backend instances are perfectly healthy, because the LB is a mandatory hop in the path. This is why production server-side setups run the LB tier redundantly (multiple LB nodes behind a VIP/DNS, or a per-node proxy like kube-proxy) rather than as a single instance.
Client-side discovery is like calling a restaurant yourself after checking a review app for which branch is open nearest you. Server-side discovery is like calling a single reservation hotline that checks branch availability for you and connects your call - you never see the branch list.
saying these in an interview costs you the question
- thinks client-side discovery means the browser/frontend does discovery
- can't name a concrete tool for either pattern
- doesn't mention the extra network hop trade-off in server-side discovery
- believes DNS round-robin is client-side discovery