Durable Workflows
What makes a workflow durable
Section titled “What makes a workflow durable”After each task completes, Sayiir saves a checkpoint (snapshot) of the workflow state. If the process crashes or restarts, you can call resume to continue execution from the last checkpoint. There is no replay—tasks that already completed are never re-executed.
This checkpointing mechanism ensures that long-running workflows can survive process crashes, deployments, or infrastructure failures without losing progress.
InMemory vs PostgreSQL
Section titled “InMemory vs PostgreSQL”Sayiir supports two backend types:
- InMemoryBackend: Fast, ephemeral storage for development and testing. State is lost when the process stops.
- PostgresBackend: Durable, persistent storage for production. State survives process restarts.
from sayiir import InMemoryBackend, PostgresBackend
# Development/testingbackend = InMemoryBackend()
# Productionbackend = PostgresBackend( host="localhost", port=5432, user="postgres", password="password", database="sayiir")use sayiir_persistence::{InMemoryBackend, PostgresBackend};
// Development/testinglet backend = InMemoryBackend::new();
// Productionlet backend = PostgresBackend::new( "host=localhost port=5432 user=postgres password=password dbname=sayiir").await?;Lifecycle operations
Section titled “Lifecycle operations”Sayiir provides full control over workflow execution with pause, unpause, cancel, and resume operations. These operations are all durable—the workflow state is persisted and can be resumed even after a process restart.
Cancel a workflow
Section titled “Cancel a workflow”from sayiir import cancel_workflow
# Cancel a running or paused workflowcancel_workflow("job-123", backend=backend)// Cancel using the runnerrunner.cancel("job-123").await?;Pause and unpause
Section titled “Pause and unpause”from sayiir import pause_workflow, unpause_workflow
# Pause a running workflowpause_workflow("job-123", backend=backend)
# Unpause to allow resumptionunpause_workflow("job-123", backend=backend)// Pause a running workflowrunner.pause("job-123").await?;
// Unpause to allow resumptionrunner.unpause("job-123").await?;Resume after crash or pause
Section titled “Resume after crash or pause”from sayiir import resume_workflow
# Resume from last checkpointstatus = resume_workflow(workflow, "job-123", backend=backend)print(f"Workflow status: {status}")// Resume from last checkpointlet status = runner.resume(&workflow, "job-123").await?;println!("Workflow status: {:?}", status);Durable delays
Section titled “Durable delays”Delays are checkpointed just like task completions. If a workflow is waiting on a delay when the process crashes, the delay will be resumed from the correct point after recovery.
from datetime import timedeltafrom sayiir import task, Flow, run_durable_workflow
@taskdef send_email(user_id: int) -> str: return f"Email sent to user {user_id}"
@taskdef send_reminder(user_id: int) -> str: return f"Reminder sent to user {user_id}"
# Wait 24 hours between email and reminderworkflow = ( Flow("email_campaign") .then(send_email) .delay(timedelta(hours=24)) .then(send_reminder) .build())
status = run_durable_workflow(workflow, "campaign-1", 42, backend=backend)use std::time::Duration;use sayiir_core::prelude::*;
let workflow = WorkflowBuilder::new(ctx) .then("send_email", |user_id: u64| async move { Ok(format!("Email sent to user {}", user_id)) }) .delay("wait_24h", Duration::from_secs(86400)) .then("send_reminder", |user_id: u64| async move { Ok(format!("Reminder sent to user {}", user_id)) }) .build()?;
let status = runner.run(&workflow, "campaign-1", 42u64).await?;If the process crashes during the 24-hour delay, calling resume will continue the delay from where it left off, not restart it from zero.