skip to content

Why does a caller ask a gRPC server's grpc.health.v1 Health service whether it is serving instead of trusting that the connection was accepted?

level: juniorimportance: must knowfreq 64%

answer

  1. an open port proves very little
  2. the server answers about itself
  3. one standard service, one unary call
  4. a status enum, not an HTTP code
  5. empty service name means the whole server

basics

~20 s

Accepting a connection only proves a process bound its port. The grpc.health.v1 Health service's Check call returns a ServingStatus for a named service, so a caller learns whether the server can actually answer work rather than merely that it is listening.

solid answer

~50 s

A connection is accepted the moment the process starts listening, which on a server that must load a large index first happens seconds after start and minutes before it can answer anything. gRPC's standard answer is a service the server exposes about itself: `grpc.health.v1.Health`. Its unary `Check` takes a `HealthCheckRequest` whose only field is a `service` name and returns a `HealthCheckResponse` carrying a `ServingStatus` — `SERVING`, `NOT_SERVING` or `UNKNOWN`. The empty string is the defined key for the server's overall status, so a caller that does not care about individual services sends `""`. Because the answer is keyed by service name, one process can report itself broadly up while one of its services is still warming. The caller should set a deadline on the `Check` and be ready for `UNIMPLEMENTED` from a server that never registered the health service at all.

code

protobuf · 20 lines
protobuf
package grpc.health.v1;

message HealthCheckRequest {
  string service = 1;
}

message HealthCheckResponse {
  enum ServingStatus {
    UNKNOWN = 0;
    SERVING = 1;
    NOT_SERVING = 2;
    SERVICE_UNKNOWN = 3;  // Used only by the Watch method.
  }
  ServingStatus status = 1;
}

service Health {
  rpc Check(HealthCheckRequest) returns (HealthCheckResponse);
  rpc Watch(HealthCheckRequest) returns (stream HealthCheckResponse);
}

go deeper

for a junior

Know that gRPC defines a standard health service and that its unary Check returns a serving status for a named service. Being able to say why an open port is a weaker signal than that answer is most of the mark here.

for a middle

Explain the request's single service field, the empty name as the key for the server's overall status, and the enum values that come back. Make clear that an unhealthy server still answers with a perfectly successful call.

for a senior

Show that you handle the outcomes that are not a status value: a server with no health service, an unregistered name, and a check that never answers. Say what each one licenses a caller to conclude and what it does not.

for a principal

The angle is what a fleet-wide readiness signal is worth and what it costs to keep honest. Per-service granularity buys precision but multiplies the states an operator must reason about, and a health answer nobody trusts is worse than none.

## What an accepted connection actually proves A connection is accepted as soon as a process binds its port and starts listening. Nothing in that moment says the process can do work. Take a germplasm catalogue service that has to read a very large accession index into memory before it can answer a single lookup: it accepts connections about a second after start and stays useless for minutes afterwards. Anything watching it — a caller holding a list of addresses, a supervising process, a curator with a terminal — needs a signal that means **ready to answer**, and a successful connect is not that signal. The same gap appears at the other end of a life cycle. A server that has begun draining is still listening, still accepting, and still entirely unable to take new work. Reachability and readiness are two different facts, and only one of them is free. ## The service a server exposes about itself gRPC's standard answer is a service the server registers alongside its own, in the package `grpc.health.v1`. The service is called `Health`, and it is an ordinary gRPC service — declared in a `.proto`, generated like any other, addressed on the wire like any other. Its only subject is the state of the server serving it. It declares two methods. `Check` is the unary one: one request, one response, the state right now. `Watch` is the streaming one, and belongs to a different question. What matters here is that the state of the server is itself **an RPC you call**, not a side channel and not a guess derived from the transport. ## One field in the request, and the meaning of the empty string `HealthCheckRequest` carries exactly one field, a string named `service`. There are two ways to fill it: - **A fully qualified service name** — the `package.Service` form that addresses that service on the wire — asks about that one service. - **The empty string** is the defined key for the server's **overall** status. A caller that does not care which service is which sends an empty name and gets one answer for the whole process. That granularity is the point of having a name in the request at all. One process commonly serves several services; the catalogue lookups may be ready while a bulk-export service in the same process is still building its cache. A per-name answer lets the server say so instead of flattening everything into one bit. ## Reading the answer The response carries a single `ServingStatus` enum value: | Value | Number | What the server is saying | |---|---|---| | `UNKNOWN` | 0 | The zero value: the name is known but no considered answer is available yet. | | `SERVING` | 1 | That name can take work now. | | `NOT_SERVING` | 2 | That name is registered and currently cannot take work. | | `SERVICE_UNKNOWN` | 3 | Reserved for the streaming `Watch` call; it is not an answer `Check` gives. | Note what this is **not**. It is not an HTTP status code, and it is not the gRPC status of the call — a healthy `Check` of a sick server is a perfectly successful call carrying `NOT_SERVING` inside its response. ## Three answers that are not a ServingStatus A caller that only handles the enum will be surprised. Three outcomes end the call without a response at all: 1. **`UNIMPLEMENTED`** — the server never registered the health service. This says nothing about its health; it says there is no health information to be had. 2. **`NOT_FOUND`** — the server does serve health but has no service registered under the name you sent. That is a statement about the name in your request. 3. **A failed call** — `DEADLINE_EXCEEDED` because nothing came back in time, or `UNAVAILABLE` because the server could not be reached. The absence of an answer is not the same as a bad answer. This is why the check should carry a deadline. The health call is exactly the one that hangs when a server is sick, because its handler may be queued behind the same exhausted resource as everything else. With a deadline, an unanswered check becomes a usable fact in bounded time. ## Why an interviewer asks this one It is a cheap question that separates people who have operated a service from people who have only written one. The candidate who says "the port is open, so it is up" has not watched a fleet come back from a restart. The candidate who reaches for a named service, an explicit status enum and a deadline has. Who calls the health service and what they do with the answer is a separate subject; what the server says about itself is this one.

  • What does a caller get back when the server never registered the health service at all?
    The call fails with `UNIMPLEMENTED`, the status a gRPC server returns for a method it does not serve. That is not the same as an unhealthy server — it says nothing about the target's state. Treat it as "no health information available" and fall back to whatever other signal you have, rather than marking the target down on the strength of it.
  • Why should a health Check carry a deadline when it is meant to be a cheap call?
    The cheap call is exactly the one that hangs when the server is sick, because its handler can be queued behind the same exhausted pool or lock as everything else. Without a deadline the caller waits indefinitely and learns nothing. With one, the call ends in `DEADLINE_EXCEEDED`, which is itself information: the server did not answer within the time you were prepared to wait.

A lit sign in a shop window is not the same as someone standing behind the counter. The accepted connection is the lit sign; the health Check is asking through the door whether anyone can actually serve you.

saying these in an interview costs you the question

  • Thinks a successful connect means the server is ready to take work.
  • Believes the health answer arrives as an HTTP status code.
  • Sends a process or host name in the service field instead of a fully qualified service name.
  • Assumes every gRPC server exposes the health service.
  • Calls Check with no deadline, so a wedged server hangs the caller indefinitely.