skip to content

In an RPC system, what distinguishes maybe, at-most-once and at-least-once invocation semantics, and what does each need from client and server?

level: middleimportance: must knowfreq 30%

answer

  1. how many executions on failure
  2. who retransmits, who remembers
  3. request identifier plus saved reply
  4. notification with no response

basics

~20 s

Maybe sends once and never resends, so the call ran zero or one times. At-least-once resends until a reply arrives, so it may run repeatedly. At-most-once also resends, but the server spots duplicates by request identifier and replays its saved reply.

solid answer

~50 s

These semantics say how many times the remote procedure executes when messages or machines fail. **Maybe** (best-effort) sends the request once with no retransmission: it ran zero or one times and the caller may never learn which — a JSON-RPC 2.0 notification, which gets no response by definition, has this shape. **At-least-once** has the client retransmit after each timeout until some reply arrives: a reply proves at least one execution, but every lost reply causes another, so it suits idempotent procedures. **At-most-once** keeps the client retransmitting but makes the server recognise a retransmission: each call carries a request identifier, the server remembers identifiers it has executed together with their replies, and answers a duplicate from that memory instead of running it again. Even then, a call that never got any reply ran once or not at all.

go deeper

for a junior

Recall the three names and what each says about the number of executions: zero or one, one or more, and at most one.

for a middle

Explain the mechanics: who retransmits, why at-most-once needs a request identifier reused on retransmission, and why the server stores the reply rather than a flag.

for a senior

Pick the semantics per operation and defend it: idempotent reads tolerate at-least-once, transfers need at-most-once, and how long the server must remember identifiers.

for a principal

Weigh the server-side memory and coordination cost of at-most-once across a fleet against redesigning operations to be idempotent, and say which default an organisation should set.

## The question these semantics answer An **RPC system** lets a client invoke a procedure on a server as if it were local. Over a network, messages get lost and machines crash, so the system has to decide what to do when a reply does not arrive — and that decision fixes how many times the procedure can **execute**. That count is the system's **invocation semantics**. It is a promise about execution on the server, which is a different thing from whether a message was delivered: a message can be delivered once and executed zero times (the server crashed mid-call), or delivered twice and executed once (the server filtered the copy). ## The three guarantees | Semantics | Client on timeout | Server on a repeat | Executions if replies are lost | Fits | |---|---|---|---|---| | **Maybe** | gives up, no resend | nothing special | 0 or 1, never known | fire-and-forget hints, telemetry | | **At-least-once** | resends until a reply | executes again | 1 or more (if a reply arrives) | idempotent procedures | | **At-most-once** | resends with the same identifier | replays the saved reply | 0 or 1 | non-idempotent procedures | - **Maybe** is the cheapest: one message, no state. A **JSON-RPC 2.0 notification** — a request object with no `id` member — is an example. The server MUST NOT reply to it, and the specification says notifications "are not confirmable by definition", so the client never hears about an invalid-params or internal error. - **At-least-once** is what you get from the simplest retry loop. If any reply arrives, the procedure ran at least once; if replies keep getting lost, it may have run many times. - **At-most-once** is at-least-once's resending plus duplicate filtering on the server, so a lost reply does not cause re-execution. ## How at-most-once is built 1. Every call carries a **request identifier** chosen by the client. 2. A client that retransmits **reuses the same identifier**; a new identifier would look like a new call. 3. The server keeps a table of executed identifiers and the **reply** each produced — often called a duplicate-request cache or reply cache. 4. On a repeat, the server returns the stored reply without running the procedure again. 5. The server must keep each entry at least as long as the client may still retransmit. RFC 5531, the ONC RPC specification, describes exactly this: a client "may choose to reuse its previous transaction ID when retransmitting a call", and the server "may choose to remember this ID after executing a call and not execute calls with the same ID, in order to achieve some degree of execute-at-most-once semantics". It adds that the server may use the identifier only as a test for equality — it is not a sequence number. Note the phrase "some degree": a table kept in memory is lost when the server restarts. The reply is stored, not just a "seen" flag, because a duplicate usually arrives *because* the first reply was lost. Returning the original reply gives the client the answer it missed. ## Where the guarantee comes from RFC 5531 is explicit that the RPC message protocol "does not attach specific semantics to the remote procedures or their execution requirements"; the semantics come from the transport and from what client and server choose to do. Over an unreliable datagram transport, an application that retransmits and never gets a reply "cannot infer anything about the number of times the procedure was executed"; if it gets a reply, it knows the procedure ran **at least once**. So the semantics are a property of the whole system — client retry behaviour plus server memory — not of a wire format. The same procedure can be offered at-least-once to one client library and at-most-once to another. ## Choosing per operation - A **read** or an absolute **set** can live with at-least-once: repeats do no harm. - A **transfer**, **create** or **send** needs at-most-once (or an idempotent redesign), or a lost reply turns into a second effect. - A **hint** whose loss is acceptable — a cache-warm request, a progress ping — can use maybe and save the round trip. None of the three tells a client whose every attempt timed out whether the call ran. At-most-once only promises it did not run *more than once*.

  • Why must an at-most-once server store the reply, not just the request identifier?
    Because a duplicate usually arrives because the first reply was lost. A server that only remembered 'already done' could drop the duplicate, leaving the client resending forever, or answer with a bare 'duplicate' that hides the real result. Storing the reply lets it resend the original outcome.
  • Does at-most-once tell a client whose every attempt timed out whether the call ran?
    No. It only promises the procedure did not run more than once. With no reply at all, the call ran once or not at all, and the client still has to query the server or reconcile before acting again.
  • Which semantics does a JSON-RPC 2.0 notification get, and why?
    Maybe. A notification has no id member, the server MUST NOT reply to it, and the specification calls notifications not confirmable, so the client never learns whether it ran or failed. Nothing in JSON-RPC 2.0 itself resends it.

saying these in an interview costs you the question

  • At-least-once means the procedure runs exactly once in practice.
  • The server can spot duplicates by comparing request bodies.
  • A retransmission should get a fresh request identifier each time.
  • Running over TCP gives at-most-once even when the client resends on a new connection.
  • At-most-once tells the caller whether a timed-out call ran.