From 3cac33bed3154c771eab1c5ac7ed820c17305693 Mon Sep 17 00:00:00 2001 From: selenium39 Date: Thu, 22 May 2025 11:16:49 +0800 Subject: [PATCH] Optimize email cleanup and add database indexes --- app/lib/schema.ts | 10 +++++--- workers/cleanup.ts | 58 ++++++++++++---------------------------------- 2 files changed, 22 insertions(+), 46 deletions(-) diff --git a/app/lib/schema.ts b/app/lib/schema.ts index 989ae8d..285849e 100644 --- a/app/lib/schema.ts +++ b/app/lib/schema.ts @@ -1,4 +1,4 @@ -import { integer, sqliteTable, text, primaryKey, uniqueIndex } from "drizzle-orm/sqlite-core" +import { integer, sqliteTable, text, primaryKey, uniqueIndex, index } from "drizzle-orm/sqlite-core" import type { AdapterAccountType } from "next-auth/adapters" import { relations } from 'drizzle-orm'; @@ -46,7 +46,9 @@ export const emails = sqliteTable("email", { .notNull() .$defaultFn(() => new Date()), expiresAt: integer("expires_at", { mode: "timestamp_ms" }).notNull(), -}) +}, (table) => ({ + expiresAtIdx: index("email_expires_at_idx").on(table.expiresAt), +})) export const messages = sqliteTable("message", { id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()), @@ -60,7 +62,9 @@ export const messages = sqliteTable("message", { receivedAt: integer("received_at", { mode: "timestamp_ms" }) .notNull() .$defaultFn(() => new Date()), -}) +}, (table) => ({ + emailIdIdx: index("message_email_id_idx").on(table.emailId), +})) export const webhooks = sqliteTable('webhook', { id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()), diff --git a/workers/cleanup.ts b/workers/cleanup.ts index 5562ca6..8d05c56 100644 --- a/workers/cleanup.ts +++ b/workers/cleanup.ts @@ -6,9 +6,6 @@ const CLEANUP_CONFIG = { // Whether to delete expired emails DELETE_EXPIRED_EMAILS: true, - // Whether to delete messages from expired emails if not deleting the emails themselves - DELETE_MESSAGES_FROM_EXPIRED: true, - // Batch processing size BATCH_SIZE: 100, } as const @@ -18,50 +15,25 @@ const main = { const now = Date.now() try { - // Find expired emails - const { results: expiredEmails } = await env.DB - .prepare(` - SELECT id - FROM email - WHERE expires_at < ? - LIMIT ? - `) - .bind(now, CLEANUP_CONFIG.BATCH_SIZE) - .all() - - if (!expiredEmails?.length) { - console.log('No expired emails found') + if (!CLEANUP_CONFIG.DELETE_EXPIRED_EMAILS) { + console.log('Expired email deletion is disabled') return } - const expiredEmailIds = expiredEmails.map(email => email.id) - const placeholders = expiredEmailIds.map(() => '?').join(',') - - if (CLEANUP_CONFIG.DELETE_EXPIRED_EMAILS) { - // First delete associated messages - await env.DB.prepare(` - DELETE FROM message - WHERE emailId IN (${placeholders}) - `).bind(...expiredEmailIds).run() - - // Then delete the emails - await env.DB.prepare(` + // Directly delete expired emails (messages will be cascade-deleted via foreign key constraint) + const result = await env.DB + .prepare(` DELETE FROM email - WHERE id IN (${placeholders}) - `).bind(...expiredEmailIds).run() - - console.log(`Deleted ${expiredEmails.length} expired emails and their messages`) - } else if (CLEANUP_CONFIG.DELETE_MESSAGES_FROM_EXPIRED) { - // Only delete messages from expired emails - await env.DB.prepare(` - DELETE FROM message - WHERE emailId IN (${placeholders}) - `).bind(...expiredEmailIds).run() - - console.log(`Deleted messages from ${expiredEmails.length} expired emails`) - } else { - console.log('No cleanup actions performed (disabled in config)') - } + WHERE expires_at < ? + LIMIT ? + RETURNING id + `) + .bind(now, CLEANUP_CONFIG.BATCH_SIZE) + .run() + + const deletedCount = result?.meta?.changes || 0 + console.log(`Deleted ${deletedCount} expired emails and their associated messages`) + } catch (error) { console.error('Failed to cleanup:', error) throw error