Skip to content

Spread service endpoints trust producer health reports that never expire #1099

Closed

Description

@privateip

Summary

With the spread distribution from #1094, each node reports the health of the producers it hosts, and every other node routes on those reports. A report stays until the node that wrote it replaces it, so a node that goes down, or whose router stops, leaves its producers reported as they last were.

If its attachments still read as ready, a lost node's producers that were last Healthy keep receiving other nodes' consumers. A producer reported Unhealthy just before its node's router stopped stays out of other nodes' rotation after it recovers.

Acceptance criteria

  • Other nodes stop sending traffic to producers on a node that has gone down, within a documented bound
  • A producer is never drained indefinitely by a report written before its node's router stopped
  • Operators can tell from the endpoint's status which health reports are current

Related to #1086

Activity

  1. added
    enhancementNew feature or request
    needs triageIssues that need to be triaged before implementing
    on Oct 25, 2026
  2. self-assigned this
    on Oct 27, 2026
  3. privateip commented on Oct 27, 2026

    @privateip
    CollaboratorAuthor

    Confirmed on main (509c9c9).

    Entries in status.producers carry no freshness. serviceroute.Compile and serviceroute.ProgrammingPlan both read every entry for the current attachment, whenever it was written (planner.go#L389-L412). The writer, ServiceProducerHealthReconciler, rewrites an entry only when its decision changes. Nothing marks an entry as no longer current, so both symptoms reproduce:

    • Lost node: with a PreferNodeLocal Spread endpoint, a remote consumer stays on the first producer whose entry says Healthy, even after that entry's node stopped writing.
    • Stuck drain: in a node's local set, an Unhealthy entry written before its router stopped keeps the producer draining with no time limit.

    Two planner tests show both. Each sets an entry's last renewal to older than any plausible lifetime. Both fail on the unchanged planner:

    producers with an expired Unhealthy decision = producer-a(draining),producer-z, want all eligible
    remote producer with a-remote's report expired = a-remote, want z-remote
    

    Plan

    • API: add an optional renewTime to each status.producers entry. Each node renews its entries every 15 seconds, even when no decision changed. An entry is current for 60 seconds after its renewTime. It is expired after that, or when renewTime is unset.
    • Writer: ServiceProducerHealthReconciler sets renewTime on every apply. It skips the write while its entries are unchanged and younger than the renew interval, and requeues for the next renewal.
    • Planner (Compile and ProgrammingPlan):
      • An expired entry never drains a local producer.
      • A remote consumer prefers, in order: a producer with a current Healthy or Unknown entry (or no entry), then one with an expired entry, then the first producer.
      • A lost node's producers therefore stop receiving other nodes' consumers within 60 seconds of their node's last renewal, whenever a currently reported producer exists. If none exists, availability wins, as it does today when every producer is Unhealthy.
    • Policy controller:
      • Requeues for the next entry expiry, so a node that has no intents yet picks up a producer that moves onto it.
      • Ignores endpoint updates that only renew entries that are still current, so the periodic renewal does not trigger a reconcile on every node.
    • Operators: renewTime shows which reports are current. The private services guide documents the bound, the ordering and the upgrade behavior: routers without renewal write no renewTime, so upgraded nodes treat those entries as expired until the rollout finishes.
    • Tests: planner tests for both symptoms and for the expiry helper, an envtest check of renewal and its write suppression, and a unit test of the watch filter.
  4. privateip commented on Oct 27, 2026

    @privateip
    CollaboratorAuthor

    The fix is up for review in #1139.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

enhancementNew feature or requestgalactic-routerRelated to galactic-routerneeds triageIssues that need to be triaged before implementing

Type

No type

Fields

Priority

None yet

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions