feat: configurable job queue processing order (LIFO/FIFO), allow sequential execution of jobs (#11897)
Previously, jobs were executed in FIFO order on MongoDB, and LIFO on Postgres, with no way to configure this behavior. This PR makes FIFO the default on both MongoDB and Postgres and introduces the following new options to configure the processing order globally or on a queue-by-queue basis: - a `processingOrder` property to the jobs config - a `processingOrder` argument to `payload.jobs.run()` to override what's set in the jobs config It also adds a new `sequential` option to `payload.jobs.run()`, which can be useful for debugging.
This commit is contained in:
@@ -24,6 +24,7 @@ import { updatePostJSONWorkflow } from './workflows/updatePostJSON.js'
|
||||
import { workflowAndTasksRetriesUndefinedWorkflow } from './workflows/workflowAndTasksRetriesUndefined.js'
|
||||
import { workflowRetries2TasksRetries0Workflow } from './workflows/workflowRetries2TasksRetries0.js'
|
||||
import { workflowRetries2TasksRetriesUndefinedWorkflow } from './workflows/workflowRetries2TasksRetriesUndefined.js'
|
||||
import { inlineTaskTestDelayedWorkflow } from './workflows/inlineTaskTestDelayed.js'
|
||||
import { parallelTaskWorkflow } from './workflows/parallelTaskWorkflow.js'
|
||||
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
@@ -104,6 +105,11 @@ export default buildConfigWithDefaults({
|
||||
},
|
||||
}
|
||||
},
|
||||
processingOrder: {
|
||||
queues: {
|
||||
lifo: '-createdAt',
|
||||
},
|
||||
},
|
||||
tasks: [
|
||||
{
|
||||
retries: 2,
|
||||
@@ -376,6 +382,7 @@ export default buildConfigWithDefaults({
|
||||
workflowRetries2TasksRetriesUndefinedWorkflow,
|
||||
workflowRetries2TasksRetries0Workflow,
|
||||
inlineTaskTestWorkflow,
|
||||
inlineTaskTestDelayedWorkflow,
|
||||
externalWorkflow,
|
||||
retriesBackoffTestWorkflow,
|
||||
subTaskWorkflow,
|
||||
|
||||
@@ -533,6 +533,106 @@ describe('Queues', () => {
|
||||
payload.config.jobs.deleteJobOnComplete = true
|
||||
})
|
||||
|
||||
it('ensure jobs run in FIFO order by default', async () => {
|
||||
await payload.jobs.queue({
|
||||
workflow: 'inlineTaskTestDelayed',
|
||||
input: {
|
||||
message: 'task 1',
|
||||
},
|
||||
})
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 100))
|
||||
|
||||
await payload.jobs.queue({
|
||||
workflow: 'inlineTaskTestDelayed',
|
||||
input: {
|
||||
message: 'task 2',
|
||||
},
|
||||
})
|
||||
|
||||
await payload.jobs.run({
|
||||
sequential: true,
|
||||
})
|
||||
|
||||
const allSimples = await payload.find({
|
||||
collection: 'simple',
|
||||
limit: 100,
|
||||
sort: 'createdAt',
|
||||
})
|
||||
|
||||
expect(allSimples.totalDocs).toBe(2)
|
||||
expect(allSimples.docs?.[0]?.title).toBe('task 1')
|
||||
expect(allSimples.docs?.[1]?.title).toBe('task 2')
|
||||
})
|
||||
|
||||
it('ensure jobs can run LIFO if processingOrder is passed', async () => {
|
||||
await payload.jobs.queue({
|
||||
workflow: 'inlineTaskTestDelayed',
|
||||
input: {
|
||||
message: 'task 1',
|
||||
},
|
||||
})
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 100))
|
||||
|
||||
await payload.jobs.queue({
|
||||
workflow: 'inlineTaskTestDelayed',
|
||||
input: {
|
||||
message: 'task 2',
|
||||
},
|
||||
})
|
||||
|
||||
await payload.jobs.run({
|
||||
sequential: true,
|
||||
processingOrder: '-createdAt',
|
||||
})
|
||||
|
||||
const allSimples = await payload.find({
|
||||
collection: 'simple',
|
||||
limit: 100,
|
||||
sort: 'createdAt',
|
||||
})
|
||||
|
||||
expect(allSimples.totalDocs).toBe(2)
|
||||
expect(allSimples.docs?.[0]?.title).toBe('task 2')
|
||||
expect(allSimples.docs?.[1]?.title).toBe('task 1')
|
||||
})
|
||||
|
||||
it('ensure job config processingOrder using queues object is respected', async () => {
|
||||
await payload.jobs.queue({
|
||||
workflow: 'inlineTaskTestDelayed',
|
||||
queue: 'lifo',
|
||||
input: {
|
||||
message: 'task 1',
|
||||
},
|
||||
})
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 100))
|
||||
|
||||
await payload.jobs.queue({
|
||||
workflow: 'inlineTaskTestDelayed',
|
||||
queue: 'lifo',
|
||||
input: {
|
||||
message: 'task 2',
|
||||
},
|
||||
})
|
||||
|
||||
await payload.jobs.run({
|
||||
sequential: true,
|
||||
queue: 'lifo',
|
||||
})
|
||||
|
||||
const allSimples = await payload.find({
|
||||
collection: 'simple',
|
||||
limit: 100,
|
||||
sort: 'createdAt',
|
||||
})
|
||||
|
||||
expect(allSimples.totalDocs).toBe(2)
|
||||
expect(allSimples.docs?.[0]?.title).toBe('task 2')
|
||||
expect(allSimples.docs?.[1]?.title).toBe('task 1')
|
||||
})
|
||||
|
||||
it('can create new inline jobs', async () => {
|
||||
await payload.jobs.queue({
|
||||
workflow: 'inlineTaskTest',
|
||||
|
||||
@@ -123,6 +123,7 @@ export interface Config {
|
||||
workflowRetries2TasksRetriesUndefined: WorkflowWorkflowRetries2TasksRetriesUndefined;
|
||||
workflowRetries2TasksRetries0: WorkflowWorkflowRetries2TasksRetries0;
|
||||
inlineTaskTest: WorkflowInlineTaskTest;
|
||||
inlineTaskTestDelayed: WorkflowInlineTaskTestDelayed;
|
||||
externalWorkflow: WorkflowExternalWorkflow;
|
||||
retriesBackoffTest: WorkflowRetriesBackoffTest;
|
||||
subTask: WorkflowSubTask;
|
||||
@@ -313,6 +314,7 @@ export interface PayloadJob {
|
||||
| 'workflowRetries2TasksRetriesUndefined'
|
||||
| 'workflowRetries2TasksRetries0'
|
||||
| 'inlineTaskTest'
|
||||
| 'inlineTaskTestDelayed'
|
||||
| 'externalWorkflow'
|
||||
| 'retriesBackoffTest'
|
||||
| 'subTask'
|
||||
@@ -722,6 +724,15 @@ export interface WorkflowInlineTaskTest {
|
||||
message: string;
|
||||
};
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "WorkflowInlineTaskTestDelayed".
|
||||
*/
|
||||
export interface WorkflowInlineTaskTestDelayed {
|
||||
input: {
|
||||
message: string;
|
||||
};
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "WorkflowExternalWorkflow".
|
||||
|
||||
38
test/queues/workflows/inlineTaskTestDelayed.ts
Normal file
38
test/queues/workflows/inlineTaskTestDelayed.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import type { WorkflowConfig } from 'payload'
|
||||
|
||||
export const inlineTaskTestDelayedWorkflow: WorkflowConfig<'inlineTaskTestDelayed'> = {
|
||||
slug: 'inlineTaskTestDelayed',
|
||||
inputSchema: [
|
||||
{
|
||||
name: 'message',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
handler: async ({ job, inlineTask }) => {
|
||||
await inlineTask('1', {
|
||||
task: async ({ input, req }) => {
|
||||
// Wait 100ms
|
||||
await new Promise((resolve) => setTimeout(resolve, 100))
|
||||
|
||||
const newSimple = await req.payload.create({
|
||||
collection: 'simple',
|
||||
req,
|
||||
data: {
|
||||
title: input.message,
|
||||
},
|
||||
})
|
||||
await new Promise((resolve) => setTimeout(resolve, 100))
|
||||
|
||||
return {
|
||||
output: {
|
||||
simpleID: newSimple.id,
|
||||
},
|
||||
}
|
||||
},
|
||||
input: {
|
||||
message: job.input.message,
|
||||
},
|
||||
})
|
||||
},
|
||||
}
|
||||
Reference in New Issue
Block a user