Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/components/gitpod-db/src/periodic-deleter.ts
2498 views
1
/**
2
* Copyright (c) 2020 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 { inject, injectable } from "inversify";
8
import { log } from "@gitpod/gitpod-protocol/lib/util/logging";
9
10
import { GitpodTableDescriptionProvider, TableDescription } from "./tables";
11
import { TypeORM } from "./typeorm/typeorm";
12
13
@injectable()
14
export class PeriodicDbDeleter {
15
@inject(GitpodTableDescriptionProvider) protected readonly tableProvider: GitpodTableDescriptionProvider;
16
@inject(TypeORM) protected readonly typeORM: TypeORM;
17
18
async runOnce(): Promise<number> {
19
const tickID = new Date().toISOString();
20
log.info("[PeriodicDbDeleter] Starting to collect deleted rows.", {
21
periodicDeleterTickId: tickID,
22
});
23
const sortedTables = this.tableProvider.getSortedTables();
24
const toBeDeleted: { table: string; deletions: string[] }[] = [];
25
for (const table of sortedTables) {
26
const rowsForTableToDelete = await this.collectRowsToBeDeleted(table);
27
if (rowsForTableToDelete.deletions.length === 0) {
28
continue;
29
}
30
log.info(
31
`[PeriodicDbDeleter] Identified ${rowsForTableToDelete.deletions.length} entries in ${rowsForTableToDelete.table} to be deleted.`,
32
{
33
periodicDeleterTickId: tickID,
34
},
35
);
36
toBeDeleted.push(rowsForTableToDelete);
37
}
38
// when collecting the deletions do so in the inverse order as during update (delete dependency targes first)
39
const pendingDeletions: Promise<void>[] = [];
40
for (const { deletions } of toBeDeleted.reverse()) {
41
for (const deletion of deletions) {
42
const promise: Promise<void> = this.query(deletion).catch((err) =>
43
log.error(`[PeriodicDbDeleter] sync error`, err, {
44
periodicDeleterTickId: tickID,
45
query: deletion,
46
}),
47
);
48
pendingDeletions.push(promise);
49
}
50
}
51
await Promise.all(pendingDeletions);
52
log.info("[PeriodicDbDeleter] Finished deleting records.", {
53
periodicDeleterTickId: tickID,
54
});
55
return pendingDeletions.length;
56
}
57
58
protected async collectRowsToBeDeleted(table: TableDescription): Promise<{ table: string; deletions: string[] }> {
59
try {
60
await this.query(`SELECT 1 FROM ${table.name} LIMIT 1`);
61
} catch (err) {
62
// table does not exist - we're done here
63
return { table: table.name, deletions: [] };
64
}
65
66
const deletions: string[] = [];
67
const result = { table: table.name, deletions };
68
if (!table.deletionColumn) {
69
return result;
70
}
71
72
const { deletionColumn, primaryKeys } = table;
73
const markedAsDeletedQuery = `SELECT ${primaryKeys.join(", ")} FROM ${
74
table.name
75
} WHERE ${deletionColumn} = true LIMIT 100;`;
76
const rows = await this.query(markedAsDeletedQuery);
77
78
const whereClauseFn = (row: any) => primaryKeys.map((pk) => `${pk}='${row[pk]}'`).join(" AND ");
79
for (const i in rows) {
80
const row = rows[i];
81
const whereClause = whereClauseFn(row);
82
deletions.push(`DELETE FROM ${table.name} WHERE ${whereClause} AND ${deletionColumn} = true;`);
83
}
84
85
return result;
86
}
87
88
protected async query(sql: string): Promise<any> {
89
const connection = await this.connection;
90
const result = await connection.query(sql);
91
return result;
92
}
93
94
protected get connection() {
95
return this.typeORM.getConnection();
96
}
97
}
98
99