Skip to content

Drain an upload even while the crawler is catching up - #135

Merged
ralyodio merged 1 commit into
mainfrom
import-drain-in-catchup
Aug 19, 2026
Merged

Drain an upload even while the crawler is catching up#135
ralyodio merged 1 commit into
mainfrom
import-drain-in-catchup

Conversation

@ralyodio

Copy link
Copy Markdown
Contributor

A list of 109,474 URLs was submitted at 09:21 today. Every entry staged into import_entries within 55 seconds (entries_ready_at 09:22:16), and then nothing happened at all — queued_count stayed 0, and the page the submitter was given said:

0% of 0 of 109,474 feeds — working

It would have said that for ever.

The cause was position

apps/poller/src/index.js:284   if (catchupOnly) return;
apps/poller/src/index.js:336   const drained = await drainImport(db)    never reached

CRAWL_CATCHUP=1 is set on the poller service. Nothing errored and nothing logged, because from the tick's point of view nothing went wrong — it simply never reached the queue somebody was waiting on. The only way to find it was to read the source.

Why the import is the exception

Catch-up mode is right about everything else it skips. Discovery finds feeds on its own schedule, cards and clusters decorate what is already indexed, and none of it has a person watching a page. An import is the opposite: somebody handed over a list and was given a URL to follow. One slice a tick is a small enough price that it never had to be deferred.

notifyFinishedSubmissions moves with it, because the two are a pair — this file already said so:

Queued submissions finish long after the upload, so the daemon that drains the queue is also what tells the submitter it drained.

Draining in catch-up mode while leaving that below the return would finish somebody's upload and never say so, which is a stranger failure than not draining at all. It is now try-guarded as well: below the return a throw cost only the housekeeping that followed it; above it, it would cost the crawl.

notifyFinishedDiscoveries deliberately stays below. Nobody is waiting on it.

About the test

It is a source-order assertion, which is a compromise worth naming rather than hiding: apps/poller/src/index.js connects to a database and starts timers at import, so tick() cannot be loaded by a test without a refactor far larger than the bug. Checking the order of three lines in the source is crude, and it does hold the one invariant whose violation is invisible from outside.

Verified it fails — three assertions go red with the drain moved back below the return — and passes on the fix.

Not in this PR

The submission page still has no live progress; it is a static render, which is most of why this looked like a stalled crawler rather than a stalled queue. Worth its own change.

Testing

All 11 workspace suites green under Node 22, the version CI runs.

🤖 Generated with Claude Code

A list of 109,474 URLs was submitted at 09:21 today. Every entry staged into
import_entries within 55 seconds, and then nothing happened at all:
queued_count stayed 0, and the page the submitter was given said "0% of 0 of
109,474 feeds — working". It would have said that for ever.

The cause was position. `drainImport` sat eleven lines below
`if (catchupOnly) return`, and CRAWL_CATCHUP=1 is set on the poller. Nothing
errored and nothing logged, because from the tick's point of view nothing went
wrong — it simply never reached the queue somebody was waiting on. The only way
to find it was to read this file.

Catch-up mode is right about everything else it skips. Discovery finds feeds on
its own schedule, cards and clusters decorate what is already indexed, and none
of it has a person watching a page. An import is the opposite: somebody handed
over a list and was given a URL to follow. A slice a tick is a small enough
price that it never had to be deferred.

`notifyFinishedSubmissions` moves with it, because the two are a pair — this
file already said so, that the daemon draining the queue is the one that tells
the submitter it drained. Draining in catch-up mode while leaving that below
would finish an upload and never say so, which is a stranger failure than not
draining. It is now guarded, too: down there a throw cost only the housekeeping
that followed, up here it would cost the crawl.

`notifyFinishedDiscoveries` deliberately stays below. Nobody is waiting on it.

The test is a source-order assertion, which is a compromise worth naming:
index.js connects to a database and starts timers at import, so `tick()` cannot
be loaded by a test without a refactor far larger than the bug. Checking the
order of three lines is crude and it does hold the one invariant whose
violation is invisible from outside. Verified it fails three ways with the
drain moved back below the return.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@ralyodio
ralyodio merged commit 889f04c into main Aug 19, 2026
3 checks passed
@ralyodio
ralyodio deleted the import-drain-in-catchup branch August 19, 2026 09:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant