Filetable· Part 1 of 3
Queues and priorities: orchestrating thousands of documents with Temporal
A document goes through a dozen steps of OCR, AI and SQL. The real challenge is not chaining them, it is deciding who goes first.
7 min readLire en français
When you drop an invoice into Filetable, it goes through a series of steps: reading the PDF, picking the right table, extracting every field, linking suppliers or orders, filing it into a folder. Some take a tenth of a second, others two minutes. And sometimes hundreds arrive at once.
Why Temporal
A process like this has three enemies: external services that go down, servers that restart, and time itself. You can build it all by hand, with a message queue, statuses in the database and scheduled jobs to catch whatever got lost. You then end up rewriting, less well, what Temporal already does.
Temporal is an open-source durable execution platform: it guarantees that your code runs to completion, whatever fails along the way. Built by the creators of Cadence, Uber's workflow engine, it now runs, according to its website, products at OpenAI, Replit and Cursor. You write the workflow as ordinary code, step by step, and Temporal records the result of each one. If a worker stops halfway, another one resumes exactly where it was: nothing is redone, nothing is lost. Retries, timeouts and waits of several days become lines of code, not infrastructure.
Durable execution
Temporal is available as a managed service, Temporal Cloud, billed by usage. But the server is open source, under the MIT license, so Filetable runs it itself, in its own cluster, next to the application and the database. Workflow data stays in-house, no step pays for a round trip to another region, and the bill does not depend on how many steps run. The trade-off: operating, upgrading and monitoring it is on us. I say more about this in Launch fast, then last.
So every document has its workflow, and every step is an activity: a piece of work Temporal hands to a worker, retries when it fails, and whose result it keeps.
- Read the documentOCRAI lane
- Pick the tableAIAI lane
- Extract the fieldsAIAI lane
- The row appearsSQLfast lane
- Relations × NAISQLrelations lane
- FinalizeSQLfast lane
- File into a folderAIAI lane
- “Done”SQLfast lane
The challenge. These steps have nothing in common. A call to the AI model can take two minutes; writing “Done” on one row takes a fraction of a second. When fifty documents arrive together, the tiny write waits behind the big calls, and the user watches a finished document keep spinning.
Priority is not enough
Temporal can prioritise: within a queue, an urgent task jumps ahead of the others. But priority only orders the backlog. It never frees a worker already busy with a two-minute call: if every slot is taken, the urgent write still waits.
The answer lies elsewhere: one queue per kind of work, each with slots of its own. Short writes never queue behind AI again, and each queue is sized for what it really costs.
One queue per kind of work
backlog → slotsMeasured on real batches, before and after:
Before → after separate queues
wait to write “Done”, a single row
median wait before extraction
Fair between customers
Within one queue, a customer uploading 500 invoices must not hold up one uploading a single one. Temporal can share tasks by fairness key: every workflow carries its workspace's, weighted by its plan. A customer alone gets the whole capacity; with several, each moves at its own pace.
Dispatch order
customer Acustomer BWaiting without blocking
When the AI provider is saturated, it refuses calls, sometimes for twenty minutes. It is not the document's fault: you wait, then try again. But where you wait changes everything. An activity that sleeps keeps its slot for nothing; a workflow timer costs nothing, and even survives a restart.
AI provider overloaded
tryslot held for nothingslot freeWhat I take from it
- Durable execution changes how you write a back end. Retries and waits become readable code.
- Priority orders, it does not free. Split queues by how long the work takes.
- Measure before tuning. A step that weighs under 1% of the time can still keep the user waiting two minutes.
- A wait lives in the workflow, never in an activity that keeps its place.