fix(model): stop advertising providers on stale egress-health evidence - #433
Merged
Ryanmello07 merged 1 commit intoAug 12, 2026
Merged
Conversation
The health gate decides whether a provider is published, and it read
provider_egress_health with no age bound at all. GetAllProviderEgressHealthCounts
selected the whole table, loaded measured_at into the struct, and never looked at
it again. A measurement taken once was therefore permanent: passesHealth kept
returning true for a provider that had long since stopped carrying traffic.
Nothing else caught it, because the re-probe schedule keyed off the WRONG table.
Both passes of the due queue are driven by provider_egress_location, so a
provider with a fresh location was never re-offered however old its health tally
was -- and location is trusted for 7 days against health's 1. A provider probed
once, then quietly going dark, kept its passing tally and its place in the list
for days.
Measured on a live deployment: 98.6% of gated providers were advertised on
evidence older than six hours, some of it three days old. Twelve were sampled
from the stalest cohort and probed -- every one answered ok=0/131. Total
blackholes, in the public list, on the strength of a measurement taken days
earlier. They still accept client connections, which is why nothing downstream
noticed.
Two halves, and both are needed:
- GetAllProviderEgressHealthCounts bounds on ProviderEgressHealthMaxAge. A
provider whose measurement aged out is absent from the map and fails closed,
identically to one never measured -- both mean "no current evidence this
provider carries traffic". GetAllProviderEgressCountryCodes has always
bounded its half this way; this is the missing symmetry.
- A third due-queue pass offers providers whose health has gone stale, keyed on
provider_egress_health rather than provider_egress_location. Without it the
bound above would be a slow drain rather than a fix: every gated provider
would age out of the map and never be re-measured, and the list would empty.
24h for health against 7 days for location because they decay differently. Where
a provider egresses from is a property of its network. Whether it still carries
traffic is a property of the moment and changes with no signal at all -- a
provider that stops forwarding stays connected and keeps accepting clients. The
constant is a floor on how bad the list can get: the longest a provider can
blackhole while still being advertised.
Pass 3 is scoped to providers that HAVE a health row which aged out, not to
every provider lacking fresh health. The looser predicate is also true of a
provider never measured, and it made every fixture in
TestGetProviderEgressLocationDue due -- including one probed an hour ago -- by
re-probing on the absence of a row rather than on evidence going stale. A
provider with a location but no health row is left to passes 1 and 2: excluded
from the list meanwhile, and re-offered when its location goes stale, so it is
deferred rather than stranded.
Passes 1 and 2 keep their predicates, ordering and index scans untouched.
Verified against a live database: the six due-queue tests pass, including two
new ones pinning that stale health IS offered and never-measured is NOT. The
first was confirmed to fail when pass 3 is disabled. The health, provider-count,
find-providers and client-score tests pass unchanged.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The bug
GetAllProviderEgressHealthCountsreadsprovider_egress_healthwith no age bound. It selects the whole table, loadsmeasured_atintoProviderEgressHealthCounts— and nothing ever reads that field again.passesHealthchecks only the ratio.So a health measurement, once taken, is permanent. A provider measured healthy months ago still passes the gate today.
The sibling loader has always been bounded.
GetAllProviderEgressCountryCodesfiltersWHERE observed_at >= $1againstProviderEgressLocationMaxAge. Location evidence expires; health evidence never did — and health is the half that detects a blackhole.Why nothing caught it
The re-probe schedule keys off the wrong table. Both passes of
GetProviderEgressLocationDueShardedare driven byprovider_egress_location, so a provider with a fresh location is never re-offered no matter how old its health tally is. Location is trusted for 7 days; health for 1. A provider probed once and then quietly going dark keeps its passing tally and its place in the list until its location ages out, days later.It fails silently by construction: such a provider stays connected and keeps accepting client connections. Nothing downstream can tell it apart from a working one.
Measured, not theorised
On a live deployment:
98.6% were advertised on evidence older than six hours. I took the twelve with the stalest evidence — all currently advertised — and probed them:
Total blackholes, in the public list, on the strength of a measurement taken days earlier.
The fix
1. Bound the evidence.
GetAllProviderEgressHealthCountsfilters on a newProviderEgressHealthMaxAge. A provider whose measurement aged out is absent from the map and fails closed — identically to one never measured, which is the correct reading: both mean no current evidence this provider carries traffic.2. Re-probe on health age. A third due-queue pass offers providers whose health has gone stale, keyed on
provider_egress_health. Without it, half 1 is a slow drain rather than a fix — every gated provider would age out of the map and never be re-measured, and the list would empty.24h for health against 7 days for location, because they decay differently. Where a provider egresses from is a property of its network and rarely changes. Whether it still carries traffic is a property of the moment and changes with no signal at all. The constant is a floor on how bad the list can get: the longest a provider can blackhole while still being advertised.
A defect the tests caught
My first version scoped pass 3 to "no fresh health row". That predicate is also true of a provider never measured, so it made every fixture in
TestGetProviderEgressLocationDuedue, including one probed an hour ago — re-probing on the absence of a row rather than on evidence going stale.Pass 3 is now scoped to providers that have a health row which aged out. A provider with a location but no health row is left to passes 1 and 2: excluded from the list meanwhile (
passesHealthfails closed on a missing row) and re-offered when its location goes stale — deferred, not stranded.Passes 1 and 2 keep their predicates, ordering and index scans untouched.
Verification
Run against a live postgres:
TestGetProviderEgressLocationDueOffersStaleHealthwas confirmed to fail when pass 3 is disabled (due = []), so it has teeth.go build ./model/... ./api/... ./controller/...,go vet ./model/,gofmt -lall clean.Deployed to beta ahead of this PR: 5,942 stale rows dropped out of the gate, the advertised set moved to tracking the fresh-and-passing population, and providers were observed expiring live — 1,207 → 1,186 as a cohort crossed 24h. Under the old code they would have stayed advertised indefinitely.
Related, not in this PR
The deployment where this was found had every provider going dark at exactly 24h —
jwt.expiryDurationis 24h and those clients hold static tokens with no refresh. That's a client-side matter and is separately fixed insdk(half-life rotation, 2026-08-06). This PR is the server-side defence: whatever the cause, a provider that has stopped carrying traffic should stop being advertised, and it now does.