Take subscription lists as they come: 200k lines, and .txt everywhere - #134
Merged
Conversation
Three things stopped a hundred-and-ten-thousand-line list of feeds from being an ordinary thing to submit. The cap that was read as one was never a cap on a submission at all. RAW_INPUT_LINE_LIMIT bounds the copy of an upload kept to name it on its status page — the ceiling on a submission is MAX_UPLOAD_FEEDS and it is in the tens of millions — but "only the first 50,000 lines of the upload are kept" reads as a limit however it is meant, and fifty thousand is below the size of the lists people actually bring. Raised to 200,000, so a real export is recorded whole and the note is one almost nobody sees. A plain list of feed URLs uploaded as a file was silently thrown away by every client that is not our uploader. The field is named `opml` and the endpoint took the name for the truth, so a .txt of one feed per line was parsed as XML, matched no outlines, and came back `no-feeds-in-opml` having imported nothing — with JavaScript off, from curl, or from an agent. It is sniffed now, by the same call the browser has always made. sniffKind reads the file before the name while it is here: `<opml` in the head means OPML whatever the file is called, which is the case its own comment promised and the old order lost. And a large submission arriving in one piece was crawled inside the request that carried it — importFeeds reads every feed URL and slug in the directory, then a round trip per five hundred rows, which for a hundred and eight thousand entries is over two hundred of them under a five-minute ceiling. Past five thousand entries it is staged instead and released to the poller, exactly as the batched uploader stages it: one bulk insert per two thousand, nothing that scales with the directory. Measured on that file: 1.0s to hand over, all 108,000 recorded. Below the threshold nothing changes, and one URL still redirects to the blog it just added. Two things the staged path exposed once everything took it: - The progress bar counted only queued feeds, so the whole handover — now most of a large import's life — showed a full bar and the word "finished" under a heading saying "Import in progress". Staged entries count towards the total in both the page and the stream, and the tick interval is sized from them too, which had put the biggest uploads on the fastest poll. - The uploader switched its progress panel on the sniffed kind, so a .txt chosen in the file form drew its bar under the paste box. It switches on which form started it now. Verified end to end against a 108,000-line, 5.4 MB file: staged in 1.0s over multipart, 2.3s in the browser (54 batches), 50,000 feeds drained in 6.1s, unique slugs throughout, and OPML — including OPML living in a .txt — still read as OPML with its titles and entities intact. 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.
Why
A 110,000-line list of feeds should be an ordinary thing to submit. Three things stopped it.
The 50k number was never a submission cap.
RAW_INPUT_LINE_LIMITbounds the copy of an upload stored to name it on its status page — the real ceiling isMAX_UPLOAD_FEEDS, in the tens of millions. But "only the first 50,000 lines of the upload are kept" reads as a limit however it is meant, and 50k is below the size of the lists people actually bring. Raised to 200,000.A
.txtof one feed per line was silently discarded by every client that isn't our uploader. The upload field is namedopmland the endpoint took the name for the truth, so a plain list was parsed as XML, matched no outlines and returnedno-feeds-in-opmlhaving imported nothing — with JS off, from curl, or from an agent. A 4.8 MB list is under the 10 MB inline limit, so it reached that path. It is sniffed now, by the same call the browser already made.A large submission arriving in one piece was crawled inside the request carrying it.
importFeedsreads every feed URL and slug in the directory, then one round trip per 500 rows — over 200 of them for 108k entries, under a 5-minute ceiling. Past 5,000 entries it is now staged and released to the poller, exactly as the batched uploader does it.What changed
RAW_INPUT_LINE_LIMIT50,000 → 200,000.sniffKindreads content before name:<opmlin the head means OPML whatever the file is called — the case its own comment promised and the old order lost./api/submitsniffs uploaded files instead of assuming OPML, and stages anything over 5,000 entries rather than importing it inline. Below the threshold nothing changes: one URL still redirects to the blog it just added..txtchosen in the file form drew its bar under the paste box. It switches on which form started it now./submitsays what the field actually takes.Verified
End to end against a real 108,000-line, 5.4 MB file, app running on SQLite:
.txt, JS off&intact.txt0% — 0 of 108,000 — working(was100% — finished)pnpm -r test1,035 pass / 0 fail;pnpm buildclean.Known, not addressed
Storing 200k lines means
raw_inputcan hold up to the 10 MB the inline endpoint allows, and/submissions/<id>reads that column on every load while auto-refreshing every 30s. The worst case was already ~6 MB before this, so it is an amplification rather than a new cost — but if it matters, the fix is a bounded read (substr(raw_input, 1, …)pluslength(...)), which needs a decision about what the panel's "total" should then mean.🤖 Generated with Claude Code