fix(ofac): stop downloading 267 MB per sweep; source always timed out - #130
Open
YAMRAJ13y wants to merge 1 commit into
Open
fix(ofac): stop downloading 267 MB per sweep; source always timed out#130YAMRAJ13y wants to merge 1 commit into
YAMRAJ13y wants to merge 1 commit into
Conversation
`briefing()` issued three full-body requests to the OFAC publication exports
on every sweep:
SDN.XML 27.5 MB
SDN_ADVANCED.XML 120.0 MB (metadata)
SDN_ADVANCED.XML 120.0 MB (again, for sample entries)
-------------------------
267.5 MB every 15 minutes = ~25.7 GB/day
Sizes confirmed from the origin's own Content-Range headers.
`safeFetch` reads the entire body with `res.text()` and only then truncates
to `rawText: text.slice(0, 500)`, so all 267 MB was pulled into memory and
discarded. The inline comment claiming it "will get the first 500 chars"
described an optimisation that does not exist.
The source therefore never completed: two 20s fetches in parallel followed by
a sequential 25s fetch cannot finish inside the 30s per-source budget in
apis/briefing.mjs, so every sweep logged
Source OFAC timed out after 30s
Two of the three downloads were also pointless. SDN_ADVANCED.XML uses a
different schema — it has no <sdnEntry>, no <Publish_Date> and no
<Record_Count> — so `advancedList` was always all-null and `sampleEntries`
was always empty.
Fix:
- request only the first 64 KB via `Range: bytes=0-65535`; the S3 origin
advertises `Accept-Ranges: bytes` and answers 206
- bound the read with a streaming reader too, so a proxy that ignores Range
still cannot pull 120 MB into memory
- fetch each list once and reuse the buffer for metadata and sampling
- take sample entries from SDN.XML, which actually contains <sdnEntry>
- parse the advanced export's <DateOfIssue> block so its date populates
Measured before/after:
requests 3 -> 2
transfer 267.5 MB -> 128.0 KB (~2,140x less)
duration timeout -> 5.0 s
sampleEntries 0 -> 10
advancedList.publishDate null -> 2026-08-07
This does not touch apis/utils/fetch.mjs, so it does not conflict with calesthio#121.
Co-Authored-By: Claude Opus 5 (1M context) <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.
Summary
The OFAC source downloads 267 MB per sweep — about 25.7 GB/day at the default 15-minute cadence — discards all but 500 characters of it, and never completes inside its 30-second budget. This makes it fetch only the ~128 KB it actually reads.
Why
briefing()issues three full-body requests:SDN.XMLSDN_ADVANCED.XMLSDN_ADVANCED.XMLSizes are from the origin's own
Content-Rangeheaders:None of it is kept.
safeFetchreads the whole body withres.text()and only then truncates:catch { return { rawText: text.slice(0, 500) } }. The comment at the call site —// The full SDN XML is large; safeFetch will get the first 500 chars— describes an optimisation that doesn't exist. There is no ranged request and no early abort; the full 267 MB is buffered in memory and thrown away.
So the source never completes.
apis/briefing.mjsallows 30s per source. This path needs two parallel 20s fetches followed by a sequential 25s fetch. Every sweep logs:And two of the three downloads were never going to work anyway.
SDN_ADVANCED.XMLuses a different schema fromSDN.XML— no<sdnEntry>, no<Publish_Date>, no<Record_Count>:so
advancedListwas always{publishDate: null, entryCount: null, recordCount: null}andparseRecentEntrieson it always returned[]. 240 MB of the 267 MB was downloaded twice to produce nothing.Fix
Range: bytes=0-65535. The origin redirects to S3, which advertisesAccept-Ranges: bytesand answers206 Partial Content.Rangeand starts streaming 120 MB still can't pull it into memory.SDN.XML, which actually contains<sdnEntry>.<DateOfIssue>block so its date populates.Everything the briefing reports lives in the first few KB — the header block carries
<Publish_Date>08/07/2026</Publish_Date>and<Record_Count>19199</Record_Count>within the first 300 bytes.Measured before / after
sampleEntries[]advancedList.publishDatenull2026-08-07sdnList.recordCountNotes
apis/utils/fetch.mjs.safeFetchcan't express "read at most N bytes", and adding that to the shared helper would collide with the retry/backoff work in flight, so the bounded read lives inofac.mjs.entryCountnow counts entries in the sampled window rather than the whole file, and is commented as such —recordCount(19199, straight from the file header) remains the authoritative total. PreviouslyentryCountcounted within a 500-char string, so it was 0 or 1.dashboard/inject.mjsreadsdata.sources.OFAC, so no dashboard surface changes.Scope
Validation
Config and Docs
.env.exampleunchanged — OFAC needs no keyREADME.mdunchanged