Skip to content

Commit 129938e

Browse files
committed
fix: use batches for queue
1 parent 0bbaac2 commit 129938e

File tree

6 files changed

+107
-38
lines changed

6 files changed

+107
-38
lines changed

package-lock.json

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
"@tus/server": "https://gitpkg.now.sh/supabase/tus-node-server/packages/server/dist?build",
3838
"agentkeepalive": "^4.2.1",
3939
"axios": "^0.27.2",
40+
"axios-rate-limit": "^1.3.0",
4041
"axios-retry": "^3.3.1",
4142
"connection-string": "^4.3.6",
4243
"conventional-changelog-conventionalcommits": "^5.0.0",

src/config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ type StorageConfigType = {
4040
webhookURL?: string
4141
webhookApiKey?: string
4242
webhookQueuePullInterval?: number
43+
webhookQueueBatchSize: number
4344
enableImageTransformation: boolean
4445
imgProxyURL?: string
4546
imgProxyRequestTimeout: number
@@ -143,6 +144,7 @@ export function getConfig(): StorageConfigType {
143144
webhookQueuePullInterval: parseInt(
144145
getOptionalConfigFromEnv('WEBHOOK_QUEUE_PULL_INTERVAL') || '700'
145146
),
147+
webhookQueueBatchSize: parseInt(getOptionalConfigFromEnv('WEBHOOK_QUEUE_BATCH_SIZE') || '100'),
146148
enableImageTransformation: getOptionalConfigFromEnv('ENABLE_IMAGE_TRANSFORMATION') === 'true',
147149
imgProxyRequestTimeout: parseInt(
148150
getOptionalConfigFromEnv('IMGPROXY_REQUEST_TIMEOUT') || '15',

src/queue/events/base-event.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,20 @@ export abstract class BaseEvent<T extends Omit<BasePayload, '$version'>> {
4646
return {}
4747
}
4848

49+
static ack(job: Job<BaseEvent<any>['payload']> | Job<BaseEvent<any>['payload']>[]) {
50+
if (enableQueueEvents) {
51+
const jobs = Array.isArray(job) ? job : [job]
52+
return Promise.all(jobs.map((job) => Queue.getInstance().complete(job.id)))
53+
}
54+
}
55+
56+
static fail(job: Job<BaseEvent<any>['payload']> | Job<BaseEvent<any>['payload']>[]) {
57+
if (enableQueueEvents) {
58+
const jobs = Array.isArray(job) ? job : [job]
59+
return Promise.all(jobs.map((job) => Queue.getInstance().fail(job.id)))
60+
}
61+
}
62+
4963
static send<T extends BaseEvent<any>>(
5064
this: StaticThis<T>,
5165
payload: Omit<T['payload'], '$version'>
@@ -75,7 +89,7 @@ export abstract class BaseEvent<T extends Omit<BasePayload, '$version'>> {
7589
})
7690
}
7791

78-
static handle(job: Job<BaseEvent<any>['payload']>) {
92+
static handle(job: Job<BaseEvent<any>['payload']> | Job<BaseEvent<any>['payload']>[]) {
7993
throw new Error('not implemented')
8094
}
8195

@@ -101,6 +115,21 @@ export abstract class BaseEvent<T extends Omit<BasePayload, '$version'>> {
101115
const constructor = this.constructor as typeof BaseEvent
102116

103117
if (!enableQueueEvents) {
118+
const options = constructor.getWorkerOptions()
119+
120+
if (options.batchSize) {
121+
return constructor.handle([
122+
{
123+
id: '',
124+
name: constructor.getQueueName(),
125+
data: {
126+
...this.payload,
127+
$version: constructor.version,
128+
},
129+
},
130+
])
131+
}
132+
104133
return constructor.handle({
105134
id: '',
106135
name: constructor.getQueueName(),

src/queue/events/webhook.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@ import { Job, WorkOptions } from 'pg-boss'
33
import axios from 'axios'
44
import { getConfig } from '../../config'
55
import { logger } from '../../monitoring'
6+
import rateLimit from 'axios-rate-limit'
67

7-
const { webhookURL, webhookApiKey, webhookQueuePullInterval } = getConfig()
8+
const { webhookURL, webhookApiKey, webhookQueuePullInterval, webhookQueueBatchSize } = getConfig()
9+
10+
const httpClient = rateLimit(axios.create(), { maxRPS: 100 })
811

912
interface WebhookEvent {
1013
event: {
@@ -26,6 +29,7 @@ export class Webhook extends BaseEvent<WebhookEvent> {
2629
static getWorkerOptions(): WorkOptions {
2730
return {
2831
newJobCheckInterval: webhookQueuePullInterval,
32+
batchSize: webhookQueueBatchSize,
2933
}
3034
}
3135

@@ -38,7 +42,7 @@ export class Webhook extends BaseEvent<WebhookEvent> {
3842
logger.info({ job }, 'handling webhook')
3943

4044
try {
41-
await axios.post(
45+
await httpClient.post(
4246
webhookURL,
4347
{
4448
type: 'Webhook',

src/queue/queue.ts

Lines changed: 53 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -71,50 +71,68 @@ export abstract class Queue {
7171
const workers: Promise<string>[] = []
7272

7373
Queue.events.forEach((event) => {
74+
const options = event.getWorkerOptions()
75+
7476
workers.push(
7577
Queue.getInstance().work(
7678
event.getQueueName(),
77-
event.getWorkerOptions(),
78-
async (job: Job<BasePayload>) => {
79-
try {
80-
const res = await event.handle(job)
81-
82-
QueueJobCompleted.inc({
83-
tenant_id: job.data.tenant.ref,
84-
name: event.getQueueName(),
85-
})
79+
options,
80+
async (job: Job<BasePayload> | Job<BasePayload>[]) => {
81+
if (!Array.isArray(job)) {
82+
job = [job]
83+
}
8684

87-
return res
88-
} catch (e) {
89-
QueueJobRetryFailed.inc({
90-
tenant_id: job.data.tenant.ref,
91-
name: event.getQueueName(),
92-
})
85+
await Promise.all(
86+
job.map(async (j) => {
87+
try {
88+
const res = await event.handle(j)
9389

94-
Queue.getInstance()
95-
.getJobById(job.id)
96-
.then((dbJob) => {
97-
if (!dbJob) {
98-
return
90+
if (options.batchSize) {
91+
await event.ack(j)
9992
}
100-
if (dbJob.retrycount === dbJob.retrylimit) {
101-
QueueJobError.inc({
102-
tenant_id: job.data.tenant.ref,
103-
name: event.getQueueName(),
93+
94+
QueueJobCompleted.inc({
95+
tenant_id: j.data.tenant.ref,
96+
name: event.getQueueName(),
97+
})
98+
99+
return res
100+
} catch (e) {
101+
QueueJobRetryFailed.inc({
102+
tenant_id: j.data.tenant.ref,
103+
name: event.getQueueName(),
104+
})
105+
106+
Queue.getInstance()
107+
.getJobById(j.id)
108+
.then((dbJob) => {
109+
if (!dbJob) {
110+
return
111+
}
112+
if (dbJob.retrycount === dbJob.retrylimit) {
113+
QueueJobError.inc({
114+
tenant_id: j.data.tenant.ref,
115+
name: event.getQueueName(),
116+
})
117+
}
104118
})
105-
}
106-
})
107119

108-
logger.error(
109-
{
110-
job: JSON.stringify(job),
111-
rawError: normalizeRawError(e),
112-
},
113-
'Error while processing job'
114-
)
120+
logger.error(
121+
{
122+
job: JSON.stringify(j),
123+
rawError: normalizeRawError(e),
124+
},
125+
'Error while processing job'
126+
)
115127

116-
throw e
117-
}
128+
if (!options.batchSize) {
129+
throw e
130+
}
131+
132+
await event.fail(j)
133+
}
134+
})
135+
)
118136
}
119137
)
120138
)

0 commit comments

Comments
 (0)