feat: Extend 1-minute data retention from 4 weeks to 1 year

- Updated lib/maintenance/data-cleanup.ts retention period: 28 days → 365 days
- Storage requirements validated: 251 MB/year (negligible)
- Rationale: 13× more historical data for better pattern analysis
- Benefits: 260-390 blocked signals/year vs 20-30/month
- Cleanup cutoff: Now Dec 2, 2024 (vs Nov 4, 2025 previously)
- Deployment verified: Container restarted, cleanup scheduled for 3 AM daily
This commit is contained in:
mindesbunister
2025-12-02 11:55:36 +01:00
parent 4239c99057
commit 5773d7d36d
11 changed files with 1191 additions and 7 deletions

View File

@@ -0,0 +1,187 @@
/**
* 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<void> {
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()
}