Files
outline/plugins/webhooks/server/processors/WebhookProcessor.ts
T
Tom Moor 0c0facc2a1 perf: Avoid empty webhook processor work via cached subscription lookup (#12593)
* Avoid empty webhook processor work via cached subscription lookup

WebhookProcessor ran for every event but most teams have no matching
webhook subscription, costing an empty processor job and a database query
per event.

Cache a team's enabled subscriptions ({ id, events }) in Redis, invalidated
by model lifecycle hooks, and add an optional BaseProcessor.shouldQueue hook
consulted by the global event queue so the webhook processor only enqueues a
job when a matching subscription exists.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feedback

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 17:53:40 -04:00

54 lines
1.5 KiB
TypeScript

import { WebhookSubscription } from "@server/models";
import BaseProcessor from "@server/queues/processors/BaseProcessor";
import type { Event } from "@server/types";
import DeliverWebhookTask from "../tasks/DeliverWebhookTask";
export default class WebhookProcessor extends BaseProcessor {
static applicableEvents: ["*"] = ["*"];
/**
* Only queue an event when the team has an enabled webhook subscription that
* matches it. The vast majority of events belong to teams with no applicable
* subscriptions, so this avoids creating and running an empty job for them.
*
* @param event The event about to be queued.
* @returns true if a matching subscription exists.
*/
static async shouldQueue(event: Event): Promise<boolean> {
if (!event.teamId) {
return false;
}
const subscriptions = await WebhookSubscription.findEnabledByTeamId(
event.teamId
);
return subscriptions.some((subscription) =>
WebhookSubscription.matchEvent(subscription.events, event.name)
);
}
async perform(event: Event) {
if (!event.teamId) {
return;
}
const subscriptions = await WebhookSubscription.findEnabledByTeamId(
event.teamId
);
const applicableSubscriptions = subscriptions.filter((subscription) =>
WebhookSubscription.matchEvent(subscription.events, event.name)
);
await Promise.all(
applicableSubscriptions.map((subscription) =>
new DeliverWebhookTask().schedule({
event,
subscriptionId: subscription.id,
})
)
);
}
}