Pause and resume the metadata refresh when Apple throttles - #26
Conversation
A single 403 or 429 ended the whole refresh run. With a backlog of stale rows that is expensive: a throttled night could refresh a handful of apps and stop, and the next run starts from the same place. The run now pauses and retries the same app. The pause budget (--rate-limit-retries, default 3) belongs to the run rather than to each app, so a genuinely throttled client still gives up quickly instead of pausing once per remaining selection. The pause length doubles per pause taken and is capped at 15 minutes; Apple's Retry-After overrides it when present, under the same cap, so a cron service does not idle for an hour holding a database connection. Behaviour on giving up is unchanged: the run stops, the interrupted app records no failure of its own, and the stop reason now also reports how many pauses were taken. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Both jobs now connect with statement_timeout=30000 (METADATA_JOB_STATEMENT_TIMEOUT_MS, 0 disables). Neither runs a query that should take longer, and it is the one path in a run with no bound on it: a query blocked on a lock would hang the run indefinitely, and because Railway never terminates a deployment, every later firing would be skipped silently. The timeout is a session parameter, so it constrains these jobs' own queries only. The web service's pool and the analyser's uploads keep the server default. No whole-run deadline: the run is already bounded by the request timeout, the LIMIT, and the capped pause budget, so a deadline would only add configuration surface. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Added Why the timeout is safe. Why the cron cannot block uploads either. The refresher runs each statement as its own implicit transaction — there is no Why no deadline. The run is already bounded on every axis — 15s HTTP timeout per request, a
|
A minute is still far longer than any query these jobs run. The extra margin is for a deploy whose migration holds a heavy lock on app_store_cache while a run is in flight: the run should wait for that rather than fail. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
# Conflicts: # docs/design/design.css # docs/design/index.html # docs/design/report.html
Why
A single 403 or 429 from Apple
breaks the entire refresh run. That was cheap when the cache was fresh, but production currently has 3,358 of 5,082app_store_cacherows older than 30 days, so a throttled night could refresh a handful of apps and stop — and the next run starts from the same backlog.(Separately:
refresh_attempted_atis NULL for every row in production, i.e. the Railway cron service has never run. That's a deployment step, not a code change — this PR is about making the run survive throttling once it is scheduled.)What
--rate-limit-retries=, default 3,METADATA_REFRESH_RATE_LIMIT_RETRIES) belongs to the run, not to each app — a genuinely throttled client still gives up quickly rather than pausing once per remaining selection.--rate-limit-backoff-ms=(default 60s,METADATA_REFRESH_RATE_LIMIT_BACKOFF_MS). Apple'sRetry-Afteroverrides it when sent, parsed as seconds or as an HTTP date.pnpm metadata-cron; the result now carriespauses.Behaviour on giving up is unchanged: the run stops, the interrupted app records no failure of its own (it describes the client, not the app), and the stop reason now also reports the pause count.
Tests
node --test— 154 pass. New coverage: resume-after-pause, run-wide budget with doubling,Retry-Afterprecedence and the cap, and seconds/HTTP-date parsing. The two existing 403/429 tests now passrateLimitRetries: 0to assert the give-up path.🤖 Generated with Claude Code