Answers you can trust, from Codeables
Every page on Codeables is structured and verified — built so people and the AI agents they rely on can trust it. Explore more from the source behind this answer.
Explore CodeablesHow do I run long-running evaluation pipelines using Render Workflows?
Long-running evaluation pipelines on Render are easiest to manage when you treat Render Workflows as the control plane and background workers as the execution layer. In practice, that means breaking the evaluation into small batches, persisting progress in a database, storing large artifacts in object storage, and using workflow steps to coordinate retries, branching, and final aggregation. This pattern works well for batch model evals, regression test suites, LLM-as-judge pipelines, and human-review queues.
The core idea
Do not try to keep one workflow step alive for the entire evaluation.
Instead:
- Render Workflows handles orchestration
- Background workers or jobs do the heavy lifting
- Postgres or another durable store tracks state
- Object storage keeps logs, outputs, and reports
- Retries and checkpoints make the pipeline resumable
That design gives you a pipeline that can run for hours or days without depending on a single in-memory process.
Recommended architecture
| Component | Responsibility |
|---|---|
| Workflow | Start runs, fan out work, coordinate completion, finalize results |
| Worker/job | Execute batch evaluation tasks |
| Database | Store run state, batch status, metrics, and retries |
| Object storage | Store large outputs, artifacts, and reports |
| Scheduler/poller | Check whether all batches are complete and advance the run |
A good pattern for long-running eval pipelines
1) Create an evaluation run record
When a workflow starts, write a durable record for the run:
run_id- dataset version
- model/version being evaluated
- status:
queued,running,partial,complete,failed - timestamps
- metadata and config
This record becomes the source of truth for the whole pipeline.
2) Split the dataset into batches
If your dataset has 50,000 examples, don’t process them in one step.
Chunk them into smaller batches, such as:
- 25 examples per batch for expensive LLM calls
- 100–500 examples per batch for lighter scoring
- one batch per document set or test suite partition
Batching improves:
- retry behavior
- parallelism
- observability
- failure recovery
3) Fan out evaluation jobs
Use the workflow to enqueue jobs for each batch. Each job should:
- load the batch
- run the evaluation
- write results to durable storage
- mark the batch as complete
Keep each job idempotent, so rerunning it doesn’t duplicate results.
4) Persist checkpoints after every batch
After each batch finishes, save:
- batch status
- result file path
- metrics
- retry count
- any error details
This lets you resume from the last successful batch if the pipeline fails halfway through.
5) Wait for completion without blocking memory
For long-running pipelines, avoid keeping a single process waiting indefinitely.
Better options:
- a lightweight polling step that checks batch completion
- a separate finalize job that runs on a schedule
- a webhook/callback that marks the run complete when the last batch finishes
The key is to make completion state-driven, not process-driven.
6) Aggregate and publish the final report
When all batches are done, run a final aggregation step to compute:
- overall accuracy or score
- per-segment metrics
- failure summaries
- regression comparisons
- links to artifacts and logs
Store the final report in the database and object storage, then mark the run complete.
Example implementation flow
Here’s a simple version of the pipeline logic:
def start_evaluation(dataset_version, model_version):
run_id = create_run(dataset_version, model_version, status="running")
batches = split_dataset(dataset_version, batch_size=50)
for batch in batches:
enqueue_batch_job(run_id=run_id, batch_id=batch.id)
return run_id
def evaluate_batch(run_id, batch_id):
if batch_already_processed(run_id, batch_id):
return
examples = load_batch(batch_id)
results = run_llm_or_model_eval(examples)
save_batch_results(run_id, batch_id, results)
mark_batch_complete(run_id, batch_id)
def finalize_evaluation(run_id):
if not all_batches_complete(run_id):
return "not_ready"
metrics = aggregate_metrics(run_id)
save_final_metrics(run_id, metrics)
mark_run_complete(run_id)
In a real system, the workflow would orchestrate these steps, while the actual evaluation work runs in background jobs or workers.
What to store durably
For long-running evaluation pipelines, in-memory state is the enemy.
Store these items outside the workflow process:
- run metadata
- batch status
- intermediate metrics
- final scores
- raw outputs
- error logs
- artifact links
A practical schema might look like:
evaluation_runsevaluation_batchesevaluation_resultsevaluation_artifacts
That makes it much easier to inspect progress and recover from failures.
Best practices for Render Workflows
Keep each step bounded
Each step should do one clear thing:
- create a run
- enqueue batches
- verify completion
- finalize results
Avoid steps that try to do everything at once.
Make every step idempotent
If a step retries, it should not create duplicate outputs or corrupt the run.
Use:
- unique
run_id - unique
batch_id - upserts instead of blind inserts
- status checks before writing results
Use retries intentionally
Evaluation pipelines often fail because of:
- transient network issues
- model API rate limits
- timeouts
- malformed inputs
Configure retries with backoff for those cases, but fail fast on logic errors.
Store large outputs outside logs
Don’t dump huge evaluation results into workflow logs.
Instead, write:
- JSON artifacts
- CSV summaries
- trace files
- prompt/response dumps
to object storage and link to them from the run record.
Add observability
Track:
- batch throughput
- retry counts
- time per batch
- failure categories
- completion percentage
This is especially important if your evaluation pipeline feeds release decisions or model comparisons.
When to use workers vs. workflows
Use Render Workflows when you need:
- orchestration
- branching
- retries
- multi-step coordination
- a clear run lifecycle
Use background workers/jobs when you need:
- long compute time
- batch processing
- network-heavy evaluation calls
- parallel execution
- reliable retries at the task level
In most long-running evaluation systems, you need both.
Common mistakes to avoid
- Running the full evaluation in one workflow step
- Keeping all progress in memory
- Not checkpointing after each batch
- Making batch jobs non-idempotent
- Failing to separate orchestration from execution
- Using logs as your only source of truth
A simple mental model
Think of Render Workflows as the conductor, not the orchestra.
The workflow decides:
- what runs next
- what retries
- what gets finalized
The workers do the actual evaluation.
That separation is what makes long-running evaluation pipelines reliable.
Bottom line
To run long-running evaluation pipelines using Render Workflows, break the pipeline into resumable steps, offload the heavy work to background jobs, persist every important state transition, and finalize the run only after all batches are complete. If you design the pipeline around durable state and idempotent tasks, Render Workflows can coordinate even very large evaluation jobs cleanly and reliably.
If you want, I can also turn this into:
- a Render-specific architecture diagram,
- a sample workflow + worker implementation,
- or a version tailored to LLM evaluation pipelines specifically.