PostgreSQL persistence workflow practice: build a task orchestration system in 30 minutes
Use PostgreSQL to replace Temporal/Airflow and build a lightweight persistence workflow engine in 30 minutes
Tutorial Objectives
In 30 minutes, build a lightweight persistence workflow engine using PostgreSQL. There is no need to deploy additional heavy-duty orchestration systems such as Temporal and Airflow - PostgreSQL itself is the orchestrator.
What will you learn?
- Implement workflow state persistence using PostgreSQL tables
- Use
SELECT ... FOR UPDATE SKIP LOCKEDto implement multi-Worker task distribution - Implement breakpoint continuation and automatic recovery of Worker crashes
- Use SQL query to directly obtain the workflow running status, without the need for a dedicated Dashboard
Preparation list
- PostgreSQL 14+ (local installation, or use Neon / Supabase cloud service, free version is sufficient)
- Python 3.9+ (the example uses psycopg2, replace it with Node.js/Go for the same reason)
- A task scenario that requires reliable asynchronous execution
Overall architecture
Traditional workflow engines (Temporal, Airflow, AWS Step Functions) rely on the two-layer architecture of "central orchestrator + worker pool". The orchestrator is responsible for receiving tasks, allocating workers, recording status, and handling failover. This architecture is powerful, but has high operation and maintenance costs—it requires additional deployment and maintenance of orchestrator clusters.
The PostgreSQL solution "sinks" the orchestration logic to the database layer: the application server no longer communicates through an intermediary, but directly reads and writes Postgres tables to complete task retrieval, execution and status updates.
| Components | Traditional Scenario | PostgreSQL Scenario |
|---|---|---|
| Task queue | Redis/RabbitMQ | Postgres table + SKIP LOCKED |
| State storage | Orchestrator internal KV storage | Postgres row-level data |
| Worker discovery | Orchestrator proactive allocation | Worker polling + row lock contention |
| Failure recovery | Orchestrator heartbeat detection → reallocation | Worker read checkpoint → skip completed steps |
| Observability | Dedicated Web Dashboard | SQL queries to integrate with existing monitoring tools |
Step 1: Create workflow status table (5 minutes)
Design two core tables: workflows records the global status of the workflow instance, and workflow_steps records the execution history of each step - this table is your checkpoint storage.
--
CREATE TABLE workflows (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
workflow_type VARCHAR(100) NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'pending',
input_data JSONB,
current_step INTEGER DEFAULT 0,
max_retries INTEGER DEFAULT 3,
error_message TEXT,
created_at TIMESTAMPTZ DEFAULT now(),
updated_at TIMESTAMPTZ DEFAULT now()
);
-- ()
CREATE TABLE workflow_steps (
id SERIAL PRIMARY KEY,
workflow_id UUID REFERENCES workflows(id),
step_number INTEGER NOT NULL,
step_name VARCHAR(200),
status VARCHAR(20) NOT NULL DEFAULT 'pending',
output_data JSONB,
started_at TIMESTAMPTZ,
completed_at TIMESTAMPTZ,
error_message TEXT,
UNIQUE(workflow_id, step_number)
);
-- Worker
CREATE INDEX idx_workflows_status ON workflows(status, created_at);
CREATE INDEX idx_workflow_steps_lookup ON workflow_steps(workflow_id, step_number);****
UNIQUE(workflow_id, step_number)。 Worker ,PostgreSQL (),。
2 Worker (10 )
SQLSELECT ... FOR UPDATE SKIP LOCKED。
pending- Worker (
SKIP LOCKED) - Worker
import psycopg2
import json
def dequeue_workflow(conn):
""" Postgres """
with conn.cursor() as cur:
cur.execute("""
SELECT id, workflow_type, input_data, current_step
FROM workflows
WHERE status = 'pending'
ORDER BY created_at
LIMIT 1
FOR UPDATE SKIP LOCKED
""")
row = cur.fetchone()
if row:
cur.execute(
"UPDATE workflows SET status = 'running', updated_at = now() WHERE id = %s",
(row[0],)
)
conn.commit()
return {
"id": row[0],
"type": row[1],
"input": row[2],
"step": row[3]
}
conn.rollback()
return NoneSKIP LOCKED PostgreSQL 9.5 ,—— Redis、 RabbitMQ、。「PostgreSQL 」。
3 (10 )
, workflow_steps 。 Worker , Worker ,,。
def execute_step(conn, workflow_id, step_number, step_fn, step_name=""):
""","""
# ()
with conn.cursor() as cur:
cur.execute(
"""SELECT status, output_data FROM workflow_steps
WHERE workflow_id = %s AND step_number = %s""",
(workflow_id, step_number)
)
existing = cur.fetchone()
if existing and existing[0] == 'completed':
return existing[1] # ,
# (ON CONFLICT DO NOTHING )
with conn.cursor() as cur:
cur.execute(
"""INSERT INTO workflow_steps (workflow_id, step_number, step_name, status, started_at)
VALUES (%s, %s, %s, 'running', now())
ON CONFLICT (workflow_id, step_number) DO NOTHING""",
(workflow_id, step_number, step_name)
)
conn.commit()
try:
result = step_fn()
with conn.cursor() as cur:
cur.execute(
"""UPDATE workflow_steps
SET status = 'completed', output_data = %s, completed_at = now()
WHERE workflow_id = %s AND step_number = %s""",
(json.dumps(result), workflow_id, step_number)
)
cur.execute(
"UPDATE workflows SET current_step = %s, updated_at = now() WHERE id = %s",
(step_number, workflow_id)
)
conn.commit()
return result
except Exception as e:
with conn.cursor() as cur:
cur.execute(
"""UPDATE workflow_steps
SET status = 'failed', error_message = %s, completed_at = now()
WHERE workflow_id = %s AND step_number = %s""",
(str(e), workflow_id, step_number)
)
conn.commit()
raise| PostgreSQL | ||
|---|---|---|
| Worker | → | Worker → |
| Worker | SKIP LOCKED + UNIQUE | |
| Python timeout, failed | ||
| Worker | Worker , Postgres |
4 SQL (5 )
Postgres , SQL Dashboard—— Prometheus、 Grafana。
-- 24
SELECT
workflow_type,
COUNT(*) AS total,
COUNT(*) FILTER (WHERE status = 'completed') AS completed,
ROUND(100.0 * COUNT(*) FILTER (WHERE status = 'completed') / COUNT(*), 1) AS success_pct
FROM workflows
WHERE created_at > now() - INTERVAL '24 hours'
GROUP BY workflow_type;
-- 5 ( Worker )
SELECT id, workflow_type, current_step, updated_at
FROM workflows
WHERE status = 'running'
AND updated_at < now() - INTERVAL '5 minutes';
-- ,
SELECT
w.workflow_type,
ws.step_name,
ROUND(AVG(EXTRACT(EPOCH FROM (ws.completed_at - ws.started_at)))::numeric, 2) AS avg_seconds
FROM workflow_steps ws
JOIN workflows w ON w.id = ws.workflow_id
WHERE ws.status = 'completed'
GROUP BY w.workflow_type, ws.step_name
ORDER BY avg_seconds DESC;SQL ( SELECT ... WHERE status = 'running' AND updated_at < now() - INTERVAL '5 minutes',)。
(FAQ)
Q1 n8n / Temporal , PostgreSQL?
n8n API ,Temporal 。PostgreSQL 「 Postgres,」。 SaaS , Postgres,。
Q2 Postgres ?
8 vCPU PostgreSQL 。, CockroachDB( PostgreSQL ), Citus ——。
Q3 Redis ,?
。PostgreSQL 「」, Redis 。 fire-and-forget(),Redis 。 5 、、—— PostgreSQL 。
PostgreSQL()、Neon(Serverless Postgres,)、Supabase( Postgres + API )、n8n()、DBOS (an enterprise-level framework based on the ideas of this article, providing SDK packaging).
Internal link guidance
- Someone has successfully practiced it: I used n8n + OpenAI to build AI content automation website: a complete review of monthly income $4,500
- Automated monetization case: He Built an AI Automation Stack with Claude + n8n — $4K to $12K/mo in 6 Months
Related tutorials
How to Write .cursorrules: Cursor Prompt Engineering Guide (2026)
If Cursor's AI keeps generating code you have to fix every time, the problem is not the model: it is your rules file. A good .cursorrules can cut your edit-to-accept ratio from 3:1 to near 1:1. After eight months of iterating on Cursor rules across React, Python, and Go projects, this guide walks through what works, what breaks, and the templates I actually use.
Claude Code Pricing Guide: Plans, Credits, and Cost Optimization (2026)
I spend $80-150/month on Claude Code and use it 6-7 days a week. This guide breaks down exactly how Claude Code pricing works: per-token API billing, Anthropic credits, Max mode costs, the June 2026 billing change, and real monthly budgets from three developer profiles. I also share the six cost optimization techniques that cut my bill from $340 to $80 without reducing how much I ship.
Topic hub
AI Agent Tutorials & Workflow Guides
Evergreen how-tos for coding agents, content pipelines, and n8n automation—linked to news context and real earn cases.
Explore AI Agent Tutorials & Workflow Guides →Monetization angle
How can you make money from this trend?
WayToClawEarn focuses on verified earn playbooks—not just news. Start from these cases.
n8n + OpenAI affiliate site
Automate content and affiliate monetization
Claude + n8n automation agency
Charge monthly for agent workflow builds