Add to the queue, and let a person's submission cut in line - #139
Merged
Conversation
Submitting was not queueing. Below a hundred entries `submitCatalogue` resolved every URL inline -- an outbound fetch each, up to eleven sequential candidates at a fifteen-second timeout, then the feed insert, the items and the topics -- and the route only answered early when something had been *queued*, which below a hundred nothing ever was. Measured against production: eight URLs already in the directory took **65 seconds** and inserted nothing at all. Above a hundred it was worse, not better. `importFeeds` opens by reading every feed_url and slug in the directory, paged as `limit 5000 offset ?`, and offset paging is O(offset): at 416,000 feeds it was still running after **550 seconds**, inside a route capped at 300. Every paste of 101 to 2,000 URLs timed out having queued nothing. Response time was non-monotonic -- 2,001 URLs were instant because the uploader's own staged path took over there. So a list is queued now and the submitter is sent to watch it, which takes 0.1s where it took 65. A single URL keeps its inline resolve, because landing on the blog you just added is the nicest thing this page does -- now bounded, so a site that publishes no feed cannot hold the request for three minutes. Two things that made the queueing itself cheap: - `submitOne` asks `feedByUrl` before it resolves. Most of what people submit is already here, and re-fetching a document we hold was the whole of that 65 seconds. - `submitCatalogue` uses `queueFeeds`, whose cost depends on the batch rather than on the directory. And the half that makes it mean anything: a queue you are added to instantly is no use if it never reaches you. `dueFeeds` orders by `next_fetch_at asc`, a new feed is stamped `now`, and ~307,000 feeds from the bulk uploads were already overdue -- so a blog submitted today sorted behind every one of them and was never crawled. Submissions of a hundred entries or fewer now go in an express lane that is read first. It cannot starve the backlog (at most half a tick) and it cannot be camped in (the lane is feeds with no `last_fetched_at`, which the crawler writes on success and on failure alike, so one attempt is all any feed gets). Verified end to end against a local instance: 8 URLs 65s -> 0.108s, 150 URLs 0.030s, a resubmitted feed 0.033s with no network at all, and one URL still redirects to the blog it added in 0.182s. 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.
/submitwas not queueing. Below 100 entries every URL was resolved inline — an outbound fetch each, up to eleven sequential candidates at a 15s timeout, then the feed insert, the items and the topics — and the route only answered early when something had been queued, which below 100 nothing ever was.Measured against production before this change:
queued: 0, nothing insertedmaxDuration = 300Response time was non-monotonic: 2,001 URLs were instant while 101 timed out.
What was actually slow
Neither the database nor the crawler. Prod reads and autocommit writes measured ~90–100ms throughout, and
resolveFeedis 47ms–2.5s per URL. Three separate things:submitOnecalledresolveFeedbeforeq.feedByUrl. That was the whole 65 seconds — every one of those fetches was of a document we already held.INLINE_LIMITwas 100, so the tail was empty, soqueuedwas 0, so the early 303 never fired.importFeedsreads the entire directory first.select feed_url, slug … limit 5000 offset ?is 84 pages at 416k feeds, and offset paging is O(offset) — late pages measured 16.5s each. I let it run 550s without finishing, inside a route capped at 300s.The other half: the queue never reached you
Queueing instantly is no use if the queue never gets there.
dueFeedsorders bynext_fetch_at asc, a new feed is stampednow, and ~307,000 bulk-uploaded feeds were already overdue — so a blog submitted today sorted behind every one of them.Submissions of ≤100 entries (
EXPRESS_MAX— the line between a person and an export) now go in an express lane read before the backlog. It is bounded at both ends:priority > 0 and last_fetched_at is null, and the crawler writeslast_fetched_aton success and on failure — so a feed leaves after exactly one attempt, with nothing to clear afterwards.The partial index means the express query reads an index that is normally empty and never larger than one afternoon's submissions.
Also
SUBMIT_INLINE_WAIT_MS, 8s): past that the submitter gets the status page and the same promise finishes in the background, so there is no queued duplicate racing the insert.submit_feedMCP tool gets the same treatment and the sameEXPRESS_MAXbound — it accepts up to 200 URLs, so that check does real work.Verification
1,105 tests pass (12 new). Verified end to end against a local instance on real SQLite: 8 URLs 65s → 0.108s, 150 URLs 0.030s, a resubmitted feed 0.033s with no network at all, one URL still lands on
/high-signalin 0.182s, and the next tick ordering is 5 express then 5 backlog exactly as designed.🤖 Generated with Claude Code