Build1 distinct publisher3 min readPublished
A dev.to walkthrough rebuilds bulk video migration as a SQLite state machine. The arithmetic underneath it: 9,612 assets is roughly 40 minutes of the whole account's API budget.
The Engineer · Build desk
Compiled by The EngineerSomething wrong?How this is made
"Per user" is the phrase doing the work in the Cloudflare figure. A backfill process does not get its own allowance; it draws from the same 1,200 requests per five minutes the live application draws from [3], which works out to four requests a second for everything the account does [12]. So the for-loop version's real defect is not only that it dies at file 3,400 leaving no record of what completed [2]. For five minutes after it hits the ceiling, the calls your player pages make are blocked too [3].
Hold the example inventory against that budget. The ffprobe pass left 9,612 assets ready to move [9]. If the create call for each one costs a single request, the migration wants about 40 minutes of the account's entire API allowance before anything else happens [13]. Poll each of those assets once for status and you have spent the same allowance a second time, which is the unglamorous reason the walkthrough runs a webhook receiver rather than a status loop [1], and the reason Mux's own migration guide says to listen for webhooks instead of polling [4].
Backoff held in a variable dies with the process. The ledger keeps `retry_after` as a column and the batch query refuses to re-select a row before that timestamp passes [5][6], so a five-minute penalty survives a restart instead of being cheerfully ignored by the next run. The stale-release sweep is set at 900 seconds [7], which is three consecutive rate-limit windows [16]: long enough that a worker still grinding through a slow upload is not robbed of its row, short enough that an interrupted batch comes back the same shift.
The `rejected` state is the cheapest line in the schema and the one most scripts skip [8]. In the sample run, 41 files had no parseable video stream [9]. Classified as `failed`, each would sit in the retry queue until the attempts counter hit five: up to 205 requests spent on files that cannot succeed [14], all of it drawn from the bucket the production app needs. Classified as `rejected`, they become a list somebody reads once [10].
One caution on the batch size. The claim query takes up to 200 rows [6], which at one request per asset is a sixth of the five-minute window [15] before any retries or multipart uploads are counted. That sizing only holds if you know the per-asset request cost, and the published excerpt breaks off at the concurrency governor [18], so the adaptive part of adaptive concurrency is asserted rather than shown. The numbers here are one author's example output on Node 22 with better-sqlite3 [11][9], not a benchmark. The structure is the transferable part: the state machine is what lets you answer "what finished" without guessing, and the budget arithmetic is what tells you whether the migration is safe to run during business hours at all.
Ranked by verification strength, evidence, and original report placement.
Cloudflare's documented global API limit is 1,200 requests per five minutes per user, and exceeding it blocks all of that account's API calls for the next five minutes, including the ones the production app makes.
A dev.to walkthrough builds a migration worker that moves a video library into a hosted video API, using a SQLite ledger for resumability, a semaphore with adaptive concurrency, Retry-After aware backoff, idempotent creates, and a webhook receiver so status is never polled.
The ledger holds one row per asset with states pending, inventoried, uploading, processing, ready, failed and rejected, plus remote_id, duration_s, attempts, retry_after (a unix timestamp before which the row must not be re-selected), last_error and updated_at; the primary key is the stable ID and also the idempotency key.
claimBatch selects up to 200 rows in state inventoried or failed with attempts under 5 and retry_after null or elapsed, then flips them to 'uploading' inside the same transaction so a second worker or an overlapping restart cannot pick up the same rows.
releaseStale resets rows left in 'uploading' and not updated for 900 seconds back to 'inventoried', on the basis that anything left uploading from a previous run was interrupted rather than finished.
The post treats 'rejected' as a state distinct from 'failed': a file FFmpeg cannot parse will never succeed and does not belong in a retry queue, and that distinction is the difference between a migration that finishes and one that grinds forever against twelve corrupt files.
Follow any of these and your For You feed starts watching them — no settings page required.
Evidence-backed comparisons of source perspectives and observed adoption signals. Read the methodology
Which Builder, Operator, and Investor concerns the observed source mix emphasized—not a truth score.
Evidence, demonstrated adoption, hype gap, incentives, and confidence are assessed independently, each on its own current evidence. How these are measured.
Complete code, second-hand external numbers
The internal artifacts are fully inspectable: schema, transactional claimBatch, releaseStale, the ffprobe inventory pass and the Governor class are all printed, plus a concrete sample run output. What the story leans on hardest for its headline is weaker: the 1,200-per-five-minute account limit and the Mux guidance are paraphrases with no quoted or linked primary source, and the body is truncated before the idempotency and webhook sections, so part of the described system is never shown.
No adoption signal in cluster
The cluster contains one tutorial and no release, deployment, benchmark, usage disclosure or third-party uptake. The 9,612 inventoried and 41 rejected rows come from a local ffprobe pass that the post says touches nothing remote, so they do not evidence any migration actually executed against a hosted video API.
Modest claims, mildly outrunning proof
The post's rhetoric is restrained and its code is shown, which keeps the gap small. It tips positive because the strongest assertions are the least evidenced: a specific vendor rate-limit figure carried second-hand, an unlogged anecdote about a loop dying at file 3,400, and the claim that the rejected/failed distinction is 'the difference between a migration that finishes and one that grinds forever' with no run against a live API to demonstrate it. Interest in the account-wide-quota framing rests entirely on the unverified 1,200-request number.
No disclosure data supplied
The cluster provides no affiliation, sponsorship or commercial-relationship information for the author, and the post sells nothing. Two vendors are named only as sources of documentation. Assigning an incentive score would require inferring facts the supplied material does not contain.
Single publisher, partially verifiable
One source from one publisher, no corroboration, and the two externally checkable figures are paraphrases. Confidence is not lower because the implementation claims are self-evidencing from printed code and are internally consistent with the reported sample run; it is not higher because the operationally important quota claim, the article's own framing device, is unconfirmed and the text is cut off.
build
Strapi will send that webhook twice, so your receiver owns the idempotency key1 distinct publisher
build
98ms repo maps: what moving symbol indexing out of Node actually buys an agent1 distinct publisher
build
A GAN beauty filter is a device budget allocation, not a feature toggle1 distinct publisher
build
Every viewer hits your HLS key endpoint in the same second, and almost nobody tests it1 distinct publisher
Distinct publishers with included, body-backed reporting in this cluster.
dev.to
1 article · August 27, 2026