-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
We want to process bulk uploads asynchronously, and so this means we need a worker queue. Since this initial task is going to be infrequent, we felt it did not warrant adding a new dependency to our stack (e.g. reddis or rabbitmq), and so we're going to just leverage postgres as our backend. I explored two tools for this purpose: pg-boss, and graphile-worker. Both packages appeared to meet our technical needs, and both seem to be maintained and similarly popular. I decided to go with graphile-worker as it appears to be part of a suite of related tools and the primary developer appears to have built a community around that suite of tools (and earns some income to support the development of the project). Issue #559 Set up a queue / processing system for handling bulk proposal uploads
- Loading branch information
Showing
14 changed files
with
501 additions
and
165 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export class JobQueueStateError extends Error {} | ||
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
import { | ||
Logger, | ||
quickAddJob, | ||
run, | ||
runMigrations, | ||
} from 'graphile-worker'; | ||
import { getLogger } from './logger'; | ||
import { db } from './database/db'; | ||
import { processBulkUpload } from './tasks'; | ||
import type { ProcessBulkUploadJobPayload } from './types'; | ||
|
||
const logger = getLogger(__filename); | ||
|
||
enum JobType { | ||
PROCESS_BULK_UPLOAD = 'processBulkUpload', | ||
} | ||
|
||
export const jobQueueLogger = new Logger((scope) => ( | ||
(level, message, meta) => { | ||
switch (level.valueOf()) { | ||
case 'error': | ||
logger.error({ meta, scope }, message); | ||
break; | ||
case 'warn': | ||
logger.warn({ meta, scope }, message); | ||
break; | ||
case 'info': | ||
logger.info({ meta, scope }, message); | ||
break; | ||
case 'debug': | ||
logger.debug({ meta, scope }, message); | ||
break; | ||
default: | ||
logger.info({ meta, scope }, message); | ||
} | ||
} | ||
)); | ||
|
||
export const startJobQueue = async () => { | ||
const runner = await run({ | ||
logger: jobQueueLogger, | ||
pgPool: db.pool, | ||
concurrency: 5, | ||
noHandleSignals: false, | ||
pollInterval: 1000, | ||
taskList: { | ||
processBulkUpload, | ||
}, | ||
}); | ||
await runner.promise; | ||
}; | ||
|
||
export const runJobQueueMigrations = async () => ( | ||
runMigrations({ | ||
logger: jobQueueLogger, | ||
pgPool: db.pool, | ||
}) | ||
); | ||
|
||
export const addJob = async ( | ||
jobType: JobType, | ||
payload: unknown, | ||
) => ( | ||
quickAddJob( | ||
{ | ||
logger: jobQueueLogger, | ||
pgPool: db.pool, | ||
}, | ||
jobType, | ||
payload, | ||
) | ||
); | ||
|
||
export const addProcessBulkUploadJob = async (payload: ProcessBulkUploadJobPayload) => ( | ||
addJob( | ||
JobType.PROCESS_BULK_UPLOAD, | ||
payload, | ||
) | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { getMockJobHelpers } from '../../test/mockGraphileWorker'; | ||
import { processBulkUpload } from '../processBulkUpload'; | ||
import { InternalValidationError } from '../../errors'; | ||
|
||
describe('processBulkUpload', () => { | ||
it('should error when passed an invalid payload', async () => { | ||
await expect(processBulkUpload( | ||
{}, | ||
getMockJobHelpers(), | ||
)).rejects.toBeInstanceOf(InternalValidationError); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './processBulkUpload'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import { isProcessBulkUploadJobPayload } from '../types'; | ||
import { InternalValidationError } from '../errors'; | ||
import type { JobHelpers } from 'graphile-worker'; | ||
|
||
export const processBulkUpload = async ( | ||
payload: unknown, | ||
helpers: JobHelpers, | ||
): Promise<void> => { | ||
if (!isProcessBulkUploadJobPayload(payload)) { | ||
helpers.logger.debug('Malformed bulk upload job payload', { errors: isProcessBulkUploadJobPayload.errors ?? [] }); | ||
throw new InternalValidationError( | ||
'The bulk upload job payload is not properly formed', | ||
isProcessBulkUploadJobPayload.errors ?? [], | ||
); | ||
} | ||
helpers.logger.debug(`Started processBulkUpload Job for Bulk Upload ID ${payload.bulkUploadId}`); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import { jobQueueLogger } from '../jobQueue'; | ||
import type { JobHelpers } from 'graphile-worker'; | ||
|
||
export const getMockJobHelpers = (): JobHelpers => ({ | ||
logger: jobQueueLogger, | ||
} as JobHelpers); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import { ajv } from '../ajv'; | ||
import type { JSONSchemaType } from 'ajv'; | ||
|
||
export interface ProcessBulkUploadJobPayload { | ||
bulkUploadId: number; | ||
} | ||
|
||
export const processBulkUploadJobPayloadSchema: JSONSchemaType<ProcessBulkUploadJobPayload> = { | ||
type: 'object', | ||
properties: { | ||
bulkUploadId: { | ||
type: 'integer', | ||
}, | ||
}, | ||
required: [ | ||
'bulkUploadId', | ||
], | ||
}; | ||
|
||
export const isProcessBulkUploadJobPayload = ajv.compile(processBulkUploadJobPayloadSchema); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters