/** * Data Cleanup Service (Dec 2, 2025) * * Purpose: Automatic cleanup of old market data to prevent database bloat * Retention: 1 year of 1-minute data (extended from 4 weeks - minimal storage ~251 MB/year) * * Schedule: Runs daily at 3 AM * Impact: ~1,576,800 rows per year (1 row/min × 60 min × 24 hr × 365 days × 3 symbols) */ import { getPrismaClient } from '../database/trades' export class DataCleanupService { private prisma = getPrismaClient() private cleanupInterval: NodeJS.Timeout | null = null private isRunning = false /** * Start the automatic cleanup job * Runs daily at 3 AM */ public start(): void { if (this.isRunning) { console.log('⚠️ Data cleanup service already running') return } console.log('🧹 Starting data cleanup service...') this.isRunning = true // Run immediately on start this.runCleanup().catch(error => { console.error('❌ Error in initial cleanup:', error) }) // Calculate time until next 3 AM const now = new Date() const next3AM = new Date() next3AM.setHours(3, 0, 0, 0) if (now.getHours() >= 3) { // If it's already past 3 AM today, schedule for tomorrow next3AM.setDate(next3AM.getDate() + 1) } const msUntil3AM = next3AM.getTime() - now.getTime() // Schedule first run at 3 AM setTimeout(() => { this.runCleanup().catch(error => { console.error('❌ Error in scheduled cleanup:', error) }) // Then run every 24 hours this.cleanupInterval = setInterval(() => { this.runCleanup().catch(error => { console.error('❌ Error in cleanup:', error) }) }, 24 * 60 * 60 * 1000) // 24 hours }, msUntil3AM) const hoursUntil3AM = Math.round(msUntil3AM / (60 * 60 * 1000)) console.log(`✅ Data cleanup scheduled for 3 AM (in ${hoursUntil3AM} hours)`) } /** * Stop the cleanup service */ public stop(): void { if (this.cleanupInterval) { clearInterval(this.cleanupInterval) this.cleanupInterval = null } this.isRunning = false console.log('⏹️ Data cleanup service stopped') } /** * Run the cleanup job now */ public async runCleanup(): Promise { try { console.log('🧹 Starting data cleanup...') const startTime = Date.now() // Delete market data older than 1 year (365 days) const oneYearAgo = new Date() oneYearAgo.setDate(oneYearAgo.getDate() - 365) const deletedCount = await this.prisma.marketData.deleteMany({ where: { createdAt: { lt: oneYearAgo } } }) const duration = Date.now() - startTime console.log( `✅ Data cleanup complete: Deleted ${deletedCount.count} old market data rows ` + `(older than ${oneYearAgo.toISOString().split('T')[0]}) in ${duration}ms` ) // Log statistics const totalRows = await this.prisma.marketData.count() const oldestRow = await this.prisma.marketData.findFirst({ orderBy: { createdAt: 'asc' } }) const newestRow = await this.prisma.marketData.findFirst({ orderBy: { createdAt: 'desc' } }) console.log(`📊 Database stats:`) console.log(` Total rows: ${totalRows.toLocaleString()}`) if (oldestRow) { const oldestDays = Math.round( (Date.now() - oldestRow.createdAt.getTime()) / (24 * 60 * 60 * 1000) ) console.log(` Oldest data: ${oldestDays} days old`) } if (newestRow) { const newestMinutes = Math.round( (Date.now() - newestRow.createdAt.getTime()) / (60 * 1000) ) console.log(` Newest data: ${newestMinutes} minutes old`) } } catch (error) { console.error('❌ Error in data cleanup:', error) throw error } } /** * Get cleanup statistics (for monitoring) */ public async getStats(): Promise<{ totalRows: number oldestDate: Date | null newestDate: Date | null dataSpanDays: number }> { const totalRows = await this.prisma.marketData.count() const oldestRow = await this.prisma.marketData.findFirst({ orderBy: { createdAt: 'asc' } }) const newestRow = await this.prisma.marketData.findFirst({ orderBy: { createdAt: 'desc' } }) let dataSpanDays = 0 if (oldestRow && newestRow) { dataSpanDays = Math.round( (newestRow.createdAt.getTime() - oldestRow.createdAt.getTime()) / (24 * 60 * 60 * 1000) ) } return { totalRows, oldestDate: oldestRow?.createdAt || null, newestDate: newestRow?.createdAt || null, dataSpanDays } } } // Singleton instance let cleanupService: DataCleanupService | null = null export function getDataCleanupService(): DataCleanupService { if (!cleanupService) { cleanupService = new DataCleanupService() console.log('🔧 Initialized Data Cleanup Service') } return cleanupService } /** * Start the cleanup service (called from startup) */ export function startDataCleanup(): void { const service = getDataCleanupService() service.start() }