advanced25 min studyLesson 352 in Engineering

API concurrency: treat resourceVersion as an opaque token

Handle update conflicts by rereading and recomputing intent without turning API version tokens into timestamps or cross-object clocks.

On this page

What you’ll understand

  • Trace an optimistic update conflict between two writers.
  • Distinguish object UID, resourceVersion, and generation.
  • Choose a conditional update strategy that preserves unrelated fields.

Two controllers can read the same object and independently decide to change it. Without a concurrency check, the second write could overwrite the first writer’s work. Kubernetes resourceVersion supports the documented optimistic concurrency protocol. In the v1.34 API contract used here, clients treat it as an opaque server-issued string: carry it back where required, but do not parse it as elapsed time, increment it yourself, or compare tokens from different objects to invent a global clock.

Identity and version answer different questions

A namespace/name locates an object, while its UID distinguishes that incarnation from a later object recreated with the same name. resourceVersion identifies a persisted version for API operations that use it. generation, where defined by a resource API, tracks specified changes according to that resource’s semantics. These fields are not interchangeable. An external resource associated only with a name may be mistaken for a new incarnation’s property after delete-and-recreate, even though the new object has a different UID.

For a normal version-checked object replacement, send the resourceVersion from the object you read. If another accepted update changed the current version first, the API server rejects the stale replacement with a conflict rather than silently losing that update. The retry should fetch current state and recompute the desired change. Replacing only the version token on an otherwise stale object would preserve the stale overwrite and defeat the purpose of the check. Bounded retries and appropriate backoff protect the control plane under contention.

accept⁡⇒RVsubmitted=RVcurrent\operatorname{accept}\Rightarrow RV_{\rm submitted}=RV_{\rm current}
This equality sketches the optimistic precondition for the normal version-checked replacement case, not every patch or API operation. RV values are opaque tokens; equality does not grant numeric ordering, timing, or cross-object comparability.↔ Scroll wide equations horizontally.

Select field ownership and patch semantics deliberately

A read-modify-write operation should preserve fields owned by other actors. Different patch formats have different merge and precondition semantics; an unconditional patch that overwrites a shared field can still lose logical updates. Server-Side Apply uses field-management semantics and conflict handling, which is a different mechanism from replacing an entire object with resourceVersion. Do not turn force ownership into a default retry policy. Decide which actor is responsible for each field, especially when a human and a controller both edit the same object.

A status subresource can separate supported status writes from desired-spec writes, but the resource definition determines what is exposed and validated. Do not assume that every status write can ignore concurrency or that any resourceVersion change means the desired configuration changed. Cache reads may lag a recent successful write, so a tight reread-and-overwrite loop can conflict unnecessarily. Use the client’s documented consistency behavior and observe the exact condition you need, rather than guessing freshness from a token that merely looks like a decimal number.

p^conflict=NconflictNattempts\widehat{p}_{\rm conflict}=\frac{N_{\rm conflict}}{N_{\rm attempts}}
This is an observed fraction of conflict responses in a defined set of update attempts. Counts are dimensionless. It measures contention in that experiment; it is not computed from resourceVersion differences and does not estimate elapsed time.↔ Scroll wide equations horizontally.

Make conflict handling preserve intent

A conflict is often useful evidence that somebody else made progress. Distinguish it from authorization failure, invalid data, transport uncertainty, or deletion of the object. On NotFound, determine whether the desired external resource should be cleaned up or whether a new incarnation now occupies the same name; a UID check prevents confusing those cases. Preserve cancellation and maximum-attempt bounds, and expose persistent contention rather than silently spinning forever. A retry that changes the desired behavior is a semantic decision, not a mechanical networking trick.

The modular lab provides a warning against assuming numeric-looking labels encode unlimited progress. Reset, set m=8 and a=1, predict residues 0 through 7 and then zero again, and Step eight times. The lab’s labels are explicitly modular integers; Kubernetes resourceVersion is not modeled by them and is not claimed to wrap this way. The point is to read the actual contract before inferring order from labels. For real concurrency, replay the equality-token trace and verify the server’s conflict response.

Two writers preserve both intended changes

Writers A and B read one object with the same opaque token, symbolically called α. Its labels are empty. A wants team=blue and B wants zone=west. Use version-checked replacements and preserve other labels.

  1. A submits its replacement with token α and team=blue. It succeeds and returns a different opaque token, symbolically β. These symbols stand for server-provided values, not client-generated API strings.

  2. B’s stale replacement with token α conflicts. It must not merely attach β to its original empty-label copy. B rereads the current object containing team=blue and recomputes adding zone=west.

  3. B’s second attempt uses the freshly read token and preserves team=blue. If that succeeds, there were three update attempts and one conflict, an observed conflict fraction of 1/3. Both intended labels remain, without numeric token arithmetic.

The result

Rereading and recomputing preserves concurrent intent; substituting a fresh token into stale content does not.

Explore the model

Interactive lab

Interactive lab · modular

Labels need an ordering contract

Reset, set m=8 and a=1, predict a return to residue zero after eight additions, then Step eight times. This modular-label counterexample does not model resourceVersion or imply Kubernetes tokens wrap.

Labels need an ordering contractAddition modulo m starts at zero: rₙ = na mod m. Playback advances two additions per second. The orbit has period m/gcd(m,a), including period one for a = 0. This is not a cryptographic security test or a primality algorithm. Current readings: Residue r0: 0; Orbit period · m/gcd(m,a): 12; Distinct residues visited: 1 of 12. All parameters are adjustable with the labeled controls below.Repeated addition modulo 12: the current residue is 0, and the orbit period is 12.01234567891011+ 5mod 12An additive orbitn = 0rₙ = 0Period = 12Cycle starts at 0:0 → 5 → 10 → 3 →8 → 1 → 6 → 11 →4 → 9 → 2 → 7 → 0Exact residues · two additions per second
Additive orbitCurrent residue / next edge
Residue r0
0
Orbit period · m/gcd(m,a)
12
Distinct residues visited
1 of 12
t = 0 s

Addition modulo m starts at zero: rₙ = na mod m. Playback advances two additions per second. The orbit has period m/gcd(m,a), including period one for a = 0. This is not a cryptographic security test or a primality algorithm. Parameter changes pause playback and recompute the model at the current elapsed time. Reset restores both parameters and returns time to zero. Playback starts paused. Adjust either parameter, then choose Play or Step.

Look forOnly the documented API contract determines which comparisons a version token supports.

Check your understanding

A stale object replacement receives HTTP 409 Conflict. What should the controller generally do?

Try it yourself

A test makes ten normal version-checked update attempts, of which two conflict. Compute the conflict fraction. Can subtracting two objects’ resourceVersion strings tell you their age difference?

Take a moment to make a prediction or work it out on paper. Open a hint when you need one.

Give me a hint

Use response counts for the fraction and the opaque-token contract for the second question.

Compare with the solution

The observed fraction is 2/10=0.2, or 20 percent. Subtracting resourceVersion strings is not a valid age calculation under this API contract; they are opaque and not a cross-object physical clock. Measure elapsed time with a suitable timer and documented observation scope.

Take it with you

  • UID identifies an incarnation; version tokens and generation have different roles.
  • A conflict retry must reread and recompute rather than bless stale content.
  • Field ownership and patch preconditions determine what concurrent work is protected.

Read further

Original sources and deeper explanations to follow at your own pace.

Optional progress, saved in this browser when available. Revisit any lesson at any time.

Lesson not marked complete.