How does the Watch call in gRPC's grpc.health.v1 Health service differ from Check, and what does Watch give a caller?
answer
- one answer now versus answers over time
- unary against server-streaming
- the request is sent exactly once
- messages arrive on change, not on a timer
- a fourth status value the stream alone uses
basics
~20 sCheck is unary: one request, one response, the status now. Watch is server-streaming: the caller sends one request, the server answers with the current status immediately and sends a further message on every change for the life of the call.
solid answer
~50 sBoth calls take the same `HealthCheckRequest` with its single `service` field, and both answer with a `HealthCheckResponse` carrying a `ServingStatus`. The difference is the call shape. `Check` is unary — ask, get one answer, done — so a caller that wants to keep up must poll, and its picture of the server is stale by up to one poll interval. `Watch` is server-streaming: the request is sent exactly once, the server replies with the current status straight away, then sends a new message each time that status changes, until the call ends. The stream also has a value `Check` never returns: `SERVICE_UNKNOWN`, for a name the server has not registered yet, so the call can survive until that service appears. The cost is that each watcher holds an open call for as long as it watches.
code
pseudocode · 8 linescall = open a server-streaming call to grpc.health.v1.Health/Watch
send HealthCheckRequest { service: "" } // sent exactly once
for each HealthCheckResponse received on call:
record response.status // SERVING, NOT_SERVING, UNKNOWN, SERVICE_UNKNOWN
// the call ends when the server ends it, the caller cancels it,
// or the deadline the caller set on it expiresgo deeper
Remember that the standard gRPC health service offers two calls: one that answers once, and one that keeps answering as things change. Being able to name which is which is enough at this stage.
Explain the call shapes — unary against server-streaming — say that the request goes up exactly once, and know that the streaming call reports an unregistered name in band instead of failing.
Talk about what a watch costs and how it dies: one open call per watcher, a deadline that eventually fires, an intermediary that cuts an idle connection, and the need to treat the end of a stream as stale data rather than silence meaning health.
The trade-off worth owning is staleness against connection budget across a whole estate. Watching scales with watchers times names, and an organisation that defaults to watching everything buys freshness it rarely acts on.
## Two calls, two shapes The standard health service declares two methods over the same request and response messages: - `Check` — **unary**. One `HealthCheckRequest` goes up, one `HealthCheckResponse` comes back, the call ends. It answers the question "what is the status right now?" - `Watch` — **server-streaming**. One `HealthCheckRequest` goes up and the response side stays open: the server sends a message immediately with the current status, and another one every time that status changes. Both take a `service` name, including the empty string for the server's overall status. Nothing about *what* is being asked differs; only *how often the answer arrives*. ## What Watch actually sends, and what it does not Three properties of the stream are worth stating precisely, because each has a common misreading: - The request is sent **once**. The caller has nothing further to say, which is exactly why the method is server-streaming rather than bidirectional. - The first message arrives **immediately**, carrying the current status. A watcher is never left guessing during the gap before the first change. - Subsequent messages are sent **on change**, not on a timer. A server whose status holds steady for an hour sends nothing for an hour, and that silence is not a fault. The call ends when the server ends it, when the caller cancels, or when a deadline the caller set expires — the ordinary life cycle of any gRPC call. ## The status value only the stream can send `ServingStatus` has four values, and one of them is reserved for `Watch`: | Situation | What `Check` does | What `Watch` does | |---|---|---| | Name registered and able to work | Response with `SERVING` | Message with `SERVING` | | Name registered, cannot work now | Response with `NOT_SERVING` | Message with `NOT_SERVING` | | Name the server never registered | Fails the call with `NOT_FOUND` | Message with `SERVICE_UNKNOWN` | | No considered answer yet | Response with `UNKNOWN` | Message with `UNKNOWN` | The third row is the interesting one and the reason `SERVICE_UNKNOWN = 3` exists at all. Failing the call is a reasonable answer to a one-shot question about a name nobody has heard of. It is a terrible answer to a long-lived watch, because a caller may legitimately start watching a service *before* it registers — during a rollout, or while a slow component is still coming up. So the unregistered case has to be reportable **in band**, as a normal response, with a later message switching to `SERVING` once the service appears. That is a real design decision, not a quirk, and it is the detail interviewers use to see whether you have read the contract or only used it. ## What a Watch costs Watching is not free, and the trade has two sides: 1. **What you gain.** No poll interval, so no staleness window and no wasted calls against a server whose status never changes. A caller learns of a transition close to when it happened. 2. **What you pay.** Every watcher holds an open call for as long as it watches, and the server must account for those the way it accounts for any other open call. A hundred callers watching a hundred service names is ten thousand open calls. 3. **What you inherit.** A long-lived call meets every long-lived-call problem: a deadline that eventually fires, a connection that an intermediary decides has been idle too long, and the need to re-establish the watch and re-read the current status after any of that. ## Choosing between them The rough rule that survives contact with production: - Use `Check` when the caller already has a natural cadence — something that runs periodically anyway and wants a fresh fact each time it runs. It is stateless, cheap to reason about, and fails in obvious ways. - Use `Watch` when the caller keeps a model of the target and wants it to track reality, and when the number of watchers is bounded by something you control. And whichever you use, handle the outcomes that are not a `ServingStatus`: a server that never registered the health service fails the call with `UNIMPLEMENTED`, and a call that never gets answered ends in `DEADLINE_EXCEEDED`. A watch that silently died and was never re-established is worse than a poll, because its silence looks exactly like good news.
- Why does SERVICE_UNKNOWN exist for Watch when Check already has an answer for that situation?A `Check` on a name the server never registered fails the call with `NOT_FOUND`, which ends the exchange. A `Watch` is supposed to survive exactly that case, because a caller may start watching a service before it registers. So the unregistered case has to be reportable inside a normal response — as `SERVICE_UNKNOWN` — with a later message switching to `SERVING` once the service appears.
- What happens to a Watch when the caller sets a deadline on it?The same as for any gRPC call: when the deadline passes the call ends with `DEADLINE_EXCEEDED`, whether or not the status ever changed. A watch is meant to be long-lived, so either give it a deadline far longer than the polling interval it replaces, or leave it open and rely on cancellation plus a deliberate re-establish. Either way, treat the end of the stream as "my picture is now stale", not as "nothing changed".
- Does one Watch call cover every service the server hosts?No. The request names one service, exactly as a `Check` does, and the empty name covers the server's overall status rather than enumerating its parts. A caller that wants per-service tracking opens one watch per name, which is precisely where the cost of watching starts to show.
saying these in an interview costs you the question
- Thinks Watch is bidirectional and the caller keeps sending requests on it.
- Expects Watch to emit on a fixed interval rather than on a change of status.
- Believes Check can return SERVICE_UNKNOWN.
- Assumes one Watch call reports on every service the server hosts.
- Treats a watch as free, ignoring that each watcher holds an open call.