0.5 缩减重构
This commit is contained in:
@@ -34,14 +34,78 @@ async function cleanupByCreationTime(ctx: any, table: string, cutoffMs: number,
|
||||
return deleted;
|
||||
}
|
||||
|
||||
async function cleanupOrphanConvexFiles(
|
||||
ctx: any,
|
||||
args: { cutoffMs: number; maxDeletes: number; dryRun: boolean },
|
||||
): Promise<{ deleted: number; scanned: number; hitLimit: boolean }> {
|
||||
const batchSize = 200;
|
||||
let deleted = 0;
|
||||
let scanned = 0;
|
||||
let lastCreationTime: number | null = null;
|
||||
let lastId: any = null;
|
||||
|
||||
while (deleted < args.maxDeletes) {
|
||||
let q = ctx.db.system.query("_storage").order("asc");
|
||||
if (lastCreationTime != null && lastId != null) {
|
||||
// 说明:Convex 的 `paginate` 每个函数只能调用一次,这里用“游标过滤 + take”模拟分页。
|
||||
// 使用 (_creationTime, _id) 作为游标,避免同一毫秒内多条记录导致遗漏/死循环。
|
||||
q = q.filter((qb: any) =>
|
||||
qb.or(
|
||||
qb.gt(qb.field("_creationTime"), lastCreationTime),
|
||||
qb.and(qb.eq(qb.field("_creationTime"), lastCreationTime), qb.gt(qb.field("_id"), lastId)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
const rows = await q.take(batchSize);
|
||||
if (!rows.length) break;
|
||||
|
||||
let reachedNewer = false;
|
||||
for (const row of rows) {
|
||||
scanned += 1;
|
||||
const created = typeof row._creationTime === "number" ? row._creationTime : 0;
|
||||
lastCreationTime = created;
|
||||
lastId = row._id;
|
||||
if (created >= args.cutoffMs) {
|
||||
reachedNewer = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// 说明:当前仓库里,Convex Files 仅用于 media_assets.storage_id。
|
||||
// 若未来新增其它引用表,需要把引用检查一并补齐,避免误删仍被使用的文件。
|
||||
const ref = await ctx.db
|
||||
.query("media_assets")
|
||||
.withIndex("by_storage_id", (q: any) => q.eq("storage_id", row._id))
|
||||
.first();
|
||||
if (ref) continue;
|
||||
|
||||
if (!args.dryRun) {
|
||||
try {
|
||||
await ctx.storage.delete(row._id);
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
deleted += 1;
|
||||
if (deleted >= args.maxDeletes) break;
|
||||
}
|
||||
|
||||
if (reachedNewer) break;
|
||||
}
|
||||
|
||||
return { deleted, scanned, hitLimit: deleted >= args.maxDeletes };
|
||||
}
|
||||
|
||||
export const cleanupWeekly = internalMutation({
|
||||
args: {
|
||||
dryRun: v.optional(v.boolean()),
|
||||
maxDeletes: v.optional(v.number()),
|
||||
maxFileDeletes: v.optional(v.number()),
|
||||
},
|
||||
handler: async (ctx, args) => {
|
||||
const dryRun = Boolean(args.dryRun);
|
||||
const maxDeletes = Math.max(100, Math.min(50_000, Math.floor(args.maxDeletes ?? 20_000)));
|
||||
const maxFileDeletes = Math.max(100, Math.min(50_000, Math.floor(args.maxFileDeletes ?? 10_000)));
|
||||
|
||||
const cutoffMs = Date.now() - WEEK_MS;
|
||||
|
||||
@@ -60,11 +124,21 @@ export const cleanupWeekly = internalMutation({
|
||||
const deletedAuthSessions = await cleanupByCreationTime(ctx, "authSessions", cutoffMs, perTable, dryRun);
|
||||
const deletedJobs = await cleanupByCreationTime(ctx, "jobs", cutoffMs, maxDeletes - perTable * 2, dryRun);
|
||||
|
||||
// 说明:清理 Convex Files 孤儿数据(_storage 里“7 天前且无引用”的文件)。
|
||||
const files = await cleanupOrphanConvexFiles(ctx, { cutoffMs, maxDeletes: maxFileDeletes, dryRun });
|
||||
|
||||
// 若达到上限,兜底再跑一轮(避免一次删太多导致超时)
|
||||
const hitLimit =
|
||||
deletedAuthRefreshTokens >= perTable || deletedAuthSessions >= perTable || deletedJobs >= maxDeletes - perTable * 2;
|
||||
deletedAuthRefreshTokens >= perTable ||
|
||||
deletedAuthSessions >= perTable ||
|
||||
deletedJobs >= maxDeletes - perTable * 2 ||
|
||||
files.hitLimit;
|
||||
if (!dryRun && hitLimit) {
|
||||
await ctx.scheduler.runAfter(60_000, (internal as any).maintenance.cleanupWeekly, { dryRun: false, maxDeletes });
|
||||
await ctx.scheduler.runAfter(60_000, (internal as any).maintenance.cleanupWeekly, {
|
||||
dryRun: false,
|
||||
maxDeletes,
|
||||
maxFileDeletes,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -75,9 +149,12 @@ export const cleanupWeekly = internalMutation({
|
||||
authRefreshTokens: deletedAuthRefreshTokens,
|
||||
authSessions: deletedAuthSessions,
|
||||
jobs: deletedJobs,
|
||||
convexFiles: files.deleted,
|
||||
},
|
||||
scanned: {
|
||||
convexFiles: files.scanned,
|
||||
},
|
||||
rescheduled: !dryRun && hitLimit,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user