Skip to content

Commit a2fa9dc

Browse files
AlexTugarevroboquat
authored andcommitted
Clean up webhook events
by running a periodic garbage collector. This is a pattern we use for other resources. fixes #12430
1 parent d795694 commit a2fa9dc

File tree

5 files changed

+79
-0
lines changed

5 files changed

+79
-0
lines changed

components/gitpod-db/src/typeorm/webhook-event-db-impl.ts

+16
Original file line numberDiff line numberDiff line change
@@ -51,4 +51,20 @@ export class WebhookEventDBImpl implements WebhookEventDB {
5151
query.limit(limit);
5252
return query.getMany();
5353
}
54+
55+
public async deleteOldEvents(ageInDays: number, limit?: number): Promise<void> {
56+
const repo = await this.getRepo();
57+
const d = new Date();
58+
d.setDate(d.getDate() - ageInDays);
59+
const expirationDate = d.toISOString();
60+
const query = repo
61+
.createQueryBuilder("event")
62+
.update()
63+
.set({ deleted: true })
64+
.where("event.creationTime < :expirationDate", { expirationDate });
65+
if (typeof limit === "number") {
66+
query.limit(limit);
67+
}
68+
await query.execute();
69+
}
5470
}

components/gitpod-db/src/webhook-event-db.ts

+1
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,5 @@ export interface WebhookEventDB {
1111
createEvent(parts: Omit<WebhookEvent, "id" | "creationTime">): Promise<WebhookEvent>;
1212
updateEvent(id: string, update: Partial<WebhookEvent>): Promise<void>;
1313
findByCloneUrl(cloneUrl: string, limit?: number): Promise<WebhookEvent[]>;
14+
deleteOldEvents(ageInDays: number, limit?: number): Promise<void>;
1415
}

components/server/src/container-module.ts

+3
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ import {
115115
getExperimentsClientForBackend,
116116
} from "@gitpod/gitpod-protocol/lib/experiments/configcat-server";
117117
import { VerificationService } from "./auth/verification-service";
118+
import { WebhookEventGarbageCollector } from "./projects/webhook-event-garbage-collector";
118119

119120
export const productionContainerModule = new ContainerModule((bind, unbind, isBound, rebind) => {
120121
bind(Config).toConstantValue(ConfigFile.fromFile());
@@ -287,4 +288,6 @@ export const productionContainerModule = new ContainerModule((bind, unbind, isBo
287288
.inSingletonScope();
288289

289290
bind(VerificationService).toSelf().inSingletonScope();
291+
292+
bind(WebhookEventGarbageCollector).toSelf().inSingletonScope();
290293
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/**
2+
* Copyright (c) 2022 Gitpod GmbH. All rights reserved.
3+
* Licensed under the GNU Affero General Public License (AGPL).
4+
* See License-AGPL.txt in the project root for license information.
5+
*/
6+
7+
import { injectable, inject } from "inversify";
8+
import * as opentracing from "opentracing";
9+
import { DBWithTracing, TracedWorkspaceDB, WorkspaceDB, WebhookEventDB } from "@gitpod/gitpod-db/lib";
10+
import { Disposable } from "@gitpod/gitpod-protocol";
11+
import { ConsensusLeaderQorum } from "../consensus/consensus-leader-quorum";
12+
import { TraceContext } from "@gitpod/gitpod-protocol/lib/util/tracing";
13+
import { log } from "@gitpod/gitpod-protocol/lib/util/logging";
14+
import { repeat } from "@gitpod/gitpod-protocol/lib/util/repeat";
15+
16+
@injectable()
17+
export class WebhookEventGarbageCollector {
18+
static readonly GC_CYCLE_INTERVAL_SECONDS = 4 * 60; // every 6 minutes
19+
20+
@inject(WebhookEventDB) protected readonly db: WebhookEventDB;
21+
22+
@inject(ConsensusLeaderQorum) protected readonly leaderQuorum: ConsensusLeaderQorum;
23+
@inject(TracedWorkspaceDB) protected readonly workspaceDB: DBWithTracing<WorkspaceDB>;
24+
25+
public async start(intervalSeconds?: number): Promise<Disposable> {
26+
const intervalSecs = intervalSeconds || WebhookEventGarbageCollector.GC_CYCLE_INTERVAL_SECONDS;
27+
return repeat(async () => {
28+
try {
29+
if (await this.leaderQuorum.areWeLeader()) {
30+
await this.collectObsoleteWebhookEvents();
31+
}
32+
} catch (err) {
33+
log.error("webhook event garbage collector", err);
34+
}
35+
}, intervalSecs * 1000);
36+
}
37+
38+
protected async collectObsoleteWebhookEvents() {
39+
const span = opentracing.globalTracer().startSpan("collectObsoleteWebhookEvents");
40+
log.debug("webhook-event-gc: start collecting...");
41+
try {
42+
await this.db.deleteOldEvents(10 /* days */, 600 /* limit per run */);
43+
log.debug("webhook-event-gc: done collecting.");
44+
} catch (err) {
45+
TraceContext.setError({ span }, err);
46+
log.error("webhook-event-gc: error collecting webhook events: ", err);
47+
throw err;
48+
} finally {
49+
span.finish();
50+
}
51+
}
52+
}

components/server/src/server.ts

+7
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ import { DebugApp } from "@gitpod/gitpod-protocol/lib/util/debug-app";
4848
import { LocalMessageBroker } from "./messaging/local-message-broker";
4949
import { WsConnectionHandler } from "./express/ws-connection-handler";
5050
import { InstallationAdminController } from "./installation-admin/installation-admin-controller";
51+
import { WebhookEventGarbageCollector } from "./projects/webhook-event-garbage-collector";
5152

5253
@injectable()
5354
export class Server<C extends GitpodClient, S extends GitpodServer> {
@@ -75,6 +76,7 @@ export class Server<C extends GitpodClient, S extends GitpodServer> {
7576
@inject(OneTimeSecretServer) protected readonly oneTimeSecretServer: OneTimeSecretServer;
7677

7778
@inject(PeriodicDbDeleter) protected readonly periodicDbDeleter: PeriodicDbDeleter;
79+
@inject(WebhookEventGarbageCollector) protected readonly webhookEventGarbageCollector: WebhookEventGarbageCollector;
7880

7981
@inject(BearerAuth) protected readonly bearerAuth: BearerAuth;
8082

@@ -269,6 +271,11 @@ export class Server<C extends GitpodClient, S extends GitpodServer> {
269271
// Start DB updater
270272
this.startDbDeleter().catch((err) => log.error("starting DB deleter", err));
271273

274+
// Start WebhookEvent GC
275+
this.webhookEventGarbageCollector
276+
.start()
277+
.catch((err) => log.error("webhook-event-gc: error during startup", err));
278+
272279
this.app = app;
273280
log.info("server initialized.");
274281
}

0 commit comments

Comments
 (0)