fx-dashboard: a fallback that only fires on ERROR cannot rescue you from SLOW - #667
Merged
Conversation
…ot rescue you from SLOW Found while investigating a revenue question: prod's self-check was reporting fx-dashboard as a 504 outage, and the tool was fine. Its second publisher was sitting idle. fx-dashboard has two sources - api.frankfurter.dev as primary, open.er-api.com as a keyless fallback - and the fallback is wired to the primary's catch block. That rescues a primary that FAILS. It does nothing for a primary that is merely slow, because a slow call eventually succeeds and the fallback never runs. Measured 2026-08-03: the primary answers in ~0.06s warm but took 7.8s and 11.6s cold, while the fallback answered in ~0.08s every time. Inheriting the default 15s timeout with one retry, a cold primary could hold a PAID $0.015 call for up to 30s before failing over, and the 12s self-check gave up long before that and booked it as an outage. The primary now gets 2.5s and no retry. If the spec'd source cannot beat a host that answers in under a tenth of a second, it has already lost, and retrying it is worse than falling through because the fallback IS the retry. NOT blanket-applied, which is the part worth keeping. There are three frankfurter call sites; fx-rate and fx-history have NO fallback, so a short leash there would convert slow successes into fast failures and lower the hit rate on a paid call. Same upstream, opposite correct answer, and the test pins the difference so a later "consistency" pass cannot quietly apply it to all three. scripts/test-fx-failover.js (12 assertions, offline, in CI). Mutations killed: reverting to the inherited default, stretching the budget past the self-check window, and applying the leash to a sole-source call. Note on the test: its first locator matched the first mention of the hostname, which is the explanatory comment, and then the first CALL, which is fx-rate - so it was asserting against the wrong one of three call sites while looking green on the mutation that mattered. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
Found while investigating a revenue question: prod's self-check was reporting
fx-dashboardas a 504 outage — and the tool was fine. Its second publisher was sitting idle.The defect
fx-dashboardhas two sources:api.frankfurter.dev(primary) andopen.er-api.com(keyless fallback). The fallback is wired to the primary'scatchblock.That rescues a primary that fails. It does nothing for a primary that is merely slow — a slow call eventually succeeds, so the fallback never runs.
Measured 2026-08-03:
Inheriting the default 15s timeout with a retry, a cold primary could hold a paid $0.015 call for up to 30s before failing over — and the 12s self-check gave up long before that and booked it as an outage.
The fix
The primary gets 2.5s and no retry. If the spec'd source cannot beat a host that answers in under a tenth of a second, it has already lost — and retrying it is worse than falling through, because the fallback is the retry.
The two publishers agree to 4dp on ECB daily reference rates, and the response already names which one served, so failing over costs the buyer nothing and is never silent.
Deliberately NOT blanket-applied
There are three
frankfurter.devcall sites.fx-rateandfx-historyhave no fallback, so a short leash there would convert slow successes into fast failures and lower the hit rate on a paid call. Same upstream, opposite correct answer.The test pins that difference, so a later "consistency" pass cannot quietly apply the leash to all three.
Verification
scripts/test-fx-failover.js— 12 assertions, offline, in CINote on the test: its first locator matched the first mention of the hostname (the explanatory comment), and then the first call — which is
fx-rate. It was asserting against the wrong one of three call sites while looking green on the mutation that mattered.