The SKIP LOCKED
clause allows concurrent transactions to skip rows currently locked by other transactions.
This behavior makes SKIP LOCKED
ideal for implementing a non-blocking task queue in Postgres.
Steps
- Create a tasks table
- Insert tasks into the queue
- Fetch tasks using SKIP LOCKED
- Mark tasks as completed
- Track stuck tasks
- Optimize with indexing
Create a tasks table
First, you need a table to store your tasks. Each task should have a unique identifier, a status, and a payload containing the task to run and any parameters. Use the following SQL statement to create the tasks table:
This table defines a tasks queue with a payload
column containing semi-structured task-specific data as a JSONB
.
Insert tasks into the queue
Next, populate the tasks table with sample data. Each task is represented as a JSON object in the payload
column:
You can then verify the rows in the task collection using the following.
SKIP LOCKED
Fetch tasks using When implementing a task queue, it is important to ensure that only one worker can run a given task, otherwise you may end up with tasks running multiple times.
FOR UPDATE SKIP LOCKED
ensures that only one process retrieves and locks tasks, while others skip over already-locked rows.
Here’s a query to fetch and lock a single task.
This query:
- Selects the oldest task with status
pending
. - Locks the task row to prevent other transactions from processing it.
- Updates the task's status to
in_progress
and records the task'sstarted_at
.
Mark tasks as completed
After processing a task, you should update the task's status to completed
to indicate that it's finished:
Track stuck tasks
In production, tasks may get stuck in_progress
due to worker errors.
To identify tasks that may be hanging, you can query for tasks that have been in progress for more than 5 minutes as follows.
Optimize with indexing
As the number of tasks grows, queries on tasks can get slow.
Adding an index on the status
and created_at
columns can help ensure consistent performance for the SKIP LOCKED
query: