# Sistema di Schedulazione Avanzata - Data Coupler ## 📋 Panoramica **Data**: 2 Ottobre 2025 **Feature**: Sistema di schedulazione completo con supporto per intervalli personalizzati **Versione**: 2.0 --- ## 🚀 **Funzionalità Implementate** ### Tipi di Schedulazione Supportati 1. **✅ Una Volta (Once)** - Esecuzione singola a una data/ora specifica - Ideale per migrazioni one-time o test 2. **✅ Giornaliera (Daily)** - Esecuzione ogni giorno a un orario specifico - Esempio: Ogni giorno alle 02:00 3. **✅ Settimanale (Weekly)** - Esecuzione ogni settimana in un giorno specifico - Esempio: Ogni Lunedì alle 08:00 4. **✅ Mensile (Monthly)** - Esecuzione ogni mese in un giorno specifico - Esempio: Il giorno 1 di ogni mese alle 00:00 5. **🆕 A Intervalli (Interval)** - NUOVA! - Esecuzione ricorrente ogni N unità di tempo - Supporta: - **Secondi**: Ogni N secondi (utile per test/demo) - **Minuti**: Ogni N minuti (es. ogni 5, 10, 15, 30 minuti) - **Ore**: Ogni N ore (es. ogni 1, 2, 4, 6, 12 ore) - **Giorni**: Ogni N giorni (es. ogni 2, 3, 7 giorni) - **Settimane**: Ogni N settimane (es. ogni 2, 4 settimane) - **Mesi**: Ogni N mesi (es. ogni 2, 3, 6 mesi) --- ## 🔧 **Modifiche Implementate** ### 1. Modello Dati - ProfileSchedule **File**: `CredentialManager/Models/ProfileSchedule.cs` #### Nuovi Campi ```csharp // Configurazione per schedulazioni a intervalli public int? IntervalValue { get; set; } // Valore dell'intervallo (es. 5, 10, 30) public string? IntervalUnit { get; set; } // "seconds", "minutes", "hours", "days", "weeks", "months" ``` #### Nuovi Metodi ```csharp /// /// Calcola la prossima esecuzione basandosi sulla data/ora base fornita /// Per intervalli, aggiunge l'intervallo alla data base /// public DateTime? CalculateNextExecutionFromLast() /// /// Calcola la prossima esecuzione aggiungendo l'intervallo alla data base /// private DateTime CalculateNextInterval(DateTime baseTime) /// /// Ottiene una descrizione leggibile della schedulazione /// public string GetScheduleDescription() ``` #### Esempi di Utilizzo ```csharp // Ogni 5 minuti var schedule = new ProfileSchedule { ScheduleType = "interval", IntervalValue = 5, IntervalUnit = "minutes" }; // Descrizione: "Ogni 5 minuti" // Ogni 2 ore var schedule = new ProfileSchedule { ScheduleType = "interval", IntervalValue = 2, IntervalUnit = "hours" }; // Descrizione: "Ogni 2 ore" // Ogni 30 secondi (utile per test) var schedule = new ProfileSchedule { ScheduleType = "interval", IntervalValue = 30, IntervalUnit = "seconds" }; // Descrizione: "Ogni 30 secondi" ``` --- ### 2. Background Service - ScheduledJobService **File**: `Data_Coupler/BackgroundServices/ScheduledJobService.cs` #### Miglioramenti Principali ##### ✅ Intervallo di Controllo Ridotto ```csharp // PRIMA: TimeSpan.FromMinutes(1) // Controllo ogni minuto // DOPO: TimeSpan.FromSeconds(30) // Controllo ogni 30 secondi ``` **Motivazione**: Supportare schedulazioni a intervalli brevi (secondi/minuti) richiede controlli più frequenti. ##### ✅ Esecuzione Parallela ```csharp // Esegue più schedulazioni contemporaneamente _ = Task.Run(async () => { await ExecuteScheduleAsync(schedule, cancellationToken); }, cancellationToken); ``` **Benefici**: - Più schedulazioni possono essere eseguite simultaneamente - Non blocca altre schedulazioni se una è lenta - Migliora throughput complessivo ##### ✅ Tracking Esecuzioni ```csharp private readonly Dictionary _runningSchedules = new(); private bool IsScheduleRunning(int scheduleId) private void MarkScheduleAsRunning(int scheduleId) private void MarkScheduleAsCompleted(int scheduleId) private void CleanupRunningSchedules() // Rimuove schedulazioni bloccate dopo 1 ora ``` **Protezioni**: - Previene esecuzioni duplicate della stessa schedulazione - Rileva e pulisce schedulazioni bloccate (timeout 1 ora) - Thread-safe con lock ##### ✅ Tolleranza Temporale Adattiva ```csharp var tolerance = schedule.ScheduleType == "interval" ? TimeSpan.FromSeconds(30) // 30 secondi per intervalli : TimeSpan.FromMinutes(1); // 1 minuto per altre schedulazioni ``` **Motivazione**: Schedulazioni a intervalli necessitano tolleranza più stretta per precisione. --- ### 3. Service Layer - ProfileScheduleService **File**: `CredentialManager/Services/ProfileScheduleService.cs` #### Aggiornamenti ```csharp public async Task UpdateNextExecutionTimeAsync(int scheduleId) { var schedule = await _context.ProfileSchedules.FindAsync(scheduleId); // Per schedulazioni a intervallo, calcola dalla ultima esecuzione if (schedule.ScheduleType == "interval") { schedule.NextExecutionTime = schedule.CalculateNextExecutionFromLast(); } else { schedule.NextExecutionTime = schedule.CalculateNextExecution(); } await _context.SaveChangesAsync(); } ``` **Differenza Chiave**: - **Intervalli**: Calcola da `LastExecutionTime` (tempo trascorso + intervallo) - **Altri tipi**: Calcola da `Now` (prossimo orario programmato) --- ### 4. Database Migration **File**: `CredentialManager/Migrations/[timestamp]_AddIntervalSchedulingFields.cs` #### Modifiche Schema ```sql ALTER TABLE ProfileSchedules ADD IntervalValue INT NULL; ALTER TABLE ProfileSchedules ADD IntervalUnit NVARCHAR(20) NULL; ``` **Compatibilità**: Campi nullable, schedulazioni esistenti non impattate. --- ## 📊 **Esempi Pratici di Utilizzo** ### Caso 1: Sincronizzazione Frequente (Ogni 5 Minuti) ```json { "Name": "Sincronizzazione Clienti Frequente", "ProfileId": 1, "ScheduleType": "interval", "IntervalValue": 5, "IntervalUnit": "minutes", "IsEnabled": true } ``` **Esecuzione**: ``` 10:00:00 - Prima esecuzione 10:05:00 - Seconda esecuzione (+5 minuti) 10:10:00 - Terza esecuzione (+5 minuti) 10:15:00 - Quarta esecuzione (+5 minuti) ... ``` --- ### Caso 2: Backup Orario ```json { "Name": "Backup Orario Prodotti", "ProfileId": 2, "ScheduleType": "interval", "IntervalValue": 1, "IntervalUnit": "hours", "IsEnabled": true } ``` **Esecuzione**: ``` 08:00:00 - Prima esecuzione 09:00:00 - Seconda esecuzione (+1 ora) 10:00:00 - Terza esecuzione (+1 ora) 11:00:00 - Quarta esecuzione (+1 ora) ... ``` --- ### Caso 3: Test Rapido (Ogni 30 Secondi) ```json { "Name": "Test Sync Veloce", "ProfileId": 3, "ScheduleType": "interval", "IntervalValue": 30, "IntervalUnit": "seconds", "IsEnabled": true } ``` **Esecuzione**: ``` 14:30:00 - Prima esecuzione 14:30:30 - Seconda esecuzione (+30 secondi) 14:31:00 - Terza esecuzione (+30 secondi) 14:31:30 - Quarta esecuzione (+30 secondi) ... ``` ⚠️ **Attenzione**: Intervalli molto brevi (<1 minuto) dovrebbero essere usati solo per test o scenari specifici. --- ### Caso 4: Sincronizzazione Bi-Settimanale ```json { "Name": "Sync Archivio Bi-Settimanale", "ProfileId": 4, "ScheduleType": "interval", "IntervalValue": 2, "IntervalUnit": "weeks", "IsEnabled": true } ``` **Esecuzione**: ``` 01/10/2025 00:00 - Prima esecuzione 15/10/2025 00:00 - Seconda esecuzione (+2 settimane) 29/10/2025 00:00 - Terza esecuzione (+2 settimane) 12/11/2025 00:00 - Quarta esecuzione (+2 settimane) ... ``` --- ## 🎯 **Logica di Schedulazione** ### Calcolo Prossima Esecuzione #### Per Schedulazioni a Intervallo ``` NextExecutionTime = LastExecutionTime + Intervallo Esempio: - LastExecutionTime = 14:30:00 - IntervalValue = 15 - IntervalUnit = "minutes" - NextExecutionTime = 14:30:00 + 15 minuti = 14:45:00 ``` **Comportamento Primo Avvio**: ``` Se LastExecutionTime è NULL (mai eseguita): NextExecutionTime = DateTime.Now + Intervallo ``` #### Per Altre Schedulazioni ``` NextExecutionTime = Prossimo Orario Programmato Esempio Daily: - DailyTime = "02:00" - Now = 14:30 - NextExecutionTime = Domani alle 02:00 ``` --- ### Controllo Esecuzioni ``` Ogni 30 secondi il ScheduledJobService: 1. Recupera tutte le schedulazioni attive 2. Filtra quelle con NextExecutionTime <= Now + Tolleranza 3. Verifica che non siano già in esecuzione 4. Le esegue in parallelo (Task.Run) 5. Aggiorna NextExecutionTime dopo completamento ``` **Tolleranza Temporale**: - Intervalli: ±30 secondi - Altri tipi: ±1 minuto --- ## ⚡ **Performance e Ottimizzazioni** ### Esecuzione Parallela ```csharp // Più schedulazioni possono essere eseguite contemporaneamente foreach (var schedule in pendingSchedules) { _ = Task.Run(async () => { await ExecuteScheduleAsync(schedule, cancellationToken); }, cancellationToken); } ``` **Vantaggi**: - ✅ Throughput migliorato - ✅ Schedulazioni lente non bloccano altre - ✅ Migliore utilizzo risorse **Protezioni**: - ✅ Tracking per evitare duplicati - ✅ Timeout dopo 1 ora (pulizia automatica) - ✅ Gestione errori isolata per schedulazione --- ### Controlli Più Frequenti ```csharp // Controllo ogni 30 secondi invece di 1 minuto private TimeSpan _checkInterval = TimeSpan.FromSeconds(30); ``` **Motivazione**: - Supportare intervalli brevi (es. ogni 30 secondi, ogni minuto) - Maggiore precisione temporale - Impatto trascurabile su performance (query leggere) --- ## 🔍 **Monitoraggio e Debug** ### Logging Dettagliato ```csharp // Ogni controllo (LogTrace - solo se abilitato) _logger.LogTrace("Nessuna schedulazione in sospeso trovata"); // Esecuzioni trovate (LogInformation) _logger.LogInformation("Trovate {Count} schedulazioni da eseguire", count); // Schedulazioni già running (LogDebug) _logger.LogDebug("Schedulazione {ScheduleId} già in esecuzione, salto", id); // Aggiornamento prossima esecuzione (LogDebug) _logger.LogDebug("Prossima esecuzione aggiornata: {NextExecution} (tipo: {Type})", nextTime, scheduleType); // Completamento con successo (LogInformation) _logger.LogInformation("Schedulazione {ScheduleId} eseguita: {Records} record, {Duration}s", id, records, duration); // Errori (LogError) _logger.LogError(ex, "Errore durante l'esecuzione schedulazione {ScheduleId}", id); ``` --- ### Query Monitoraggio ```sql -- Schedulazioni attive per tipo SELECT ScheduleType, COUNT(*) as Total, SUM(CASE WHEN IsEnabled = 1 THEN 1 ELSE 0 END) as Enabled FROM ProfileSchedules WHERE IsActive = 1 GROUP BY ScheduleType; -- Schedulazioni a intervallo con dettagli SELECT Id, Name, IntervalValue, IntervalUnit, CONCAT(IntervalValue, ' ', IntervalUnit) as Interval, LastExecutionTime, NextExecutionTime, LastExecutionStatus FROM ProfileSchedules WHERE ScheduleType = 'interval' AND IsEnabled = 1 ORDER BY NextExecutionTime; -- Statistiche esecuzioni ultima ora SELECT s.Name, s.ScheduleType, COUNT(h.Id) as ExecutionCount, AVG(DATEDIFF(SECOND, h.StartTime, h.EndTime)) as AvgDurationSeconds, SUM(h.RecordsProcessed) as TotalRecords FROM ProfileSchedules s LEFT JOIN ScheduleExecutionHistory h ON s.Id = h.ScheduleId WHERE h.StartTime >= DATEADD(HOUR, -1, GETDATE()) GROUP BY s.Id, s.Name, s.ScheduleType ORDER BY ExecutionCount DESC; -- Schedulazioni bloccate (running da >1 ora) SELECT Id, Name, LastExecutionStatus, LastExecutionTime, DATEDIFF(MINUTE, LastExecutionTime, GETDATE()) as MinutesSinceExecution FROM ProfileSchedules WHERE LastExecutionStatus = 'running' AND LastExecutionTime < DATEADD(HOUR, -1, GETDATE()); ``` --- ## ⚠️ **Limitazioni e Considerazioni** ### Intervalli Molto Brevi (<1 Minuto) **⚠️ Attenzione**: - Intervalli di pochi secondi generano molte esecuzioni - Aumentano carico database e API - Dovrebbero essere usati solo per: - Test e demo - Scenari critici real-time - Ambienti con risorse dedicate **Raccomandazioni**: - **Produzione**: Minimo 5-10 minuti - **Test**: 30 secondi - 2 minuti OK - **Dev**: Qualsiasi intervallo --- ### Esecuzioni Parallele **Comportamento**: - Più schedulazioni possono essere eseguite contemporaneamente - Stessa schedulazione NON può essere eseguita due volte in parallelo **Implicazioni**: - Se una schedulazione impiega più tempo del suo intervallo, alcune esecuzioni verranno saltate - Esempio: Intervallo 5 minuti, ma esecuzione richiede 7 minuti → Una esecuzione verrà saltata **Soluzione**: - Aumentare l'intervallo - Ottimizzare il profilo di trasferimento - Monitorare durata esecuzioni --- ### Drift Temporale **Per Intervalli Lunghi** (giorni/settimane/mesi): ``` Esempio: Ogni 2 settimane - Prima esecuzione: 01/10/2025 14:30:00 - Seconda esecuzione: 15/10/2025 14:30:00 (esatta) - Terza esecuzione: 29/10/2025 14:30:00 (esatta) Se una esecuzione ritarda: - Seconda esecuzione: 15/10/2025 14:35:00 (ritardo 5 minuti) - Terza esecuzione: 29/10/2025 14:35:00 (drift propagato) ``` **Mitigazione**: - Tolleranza di ±30 secondi assorbe piccole variazioni - Per intervalli lunghi, il drift è trascurabile (secondi su giorni) - Considerare schedulazioni daily/weekly/monthly se serve orario esatto --- ## 🧪 **Testing** ### Test Schedulazione a 30 Secondi ```csharp var schedule = new ProfileSchedule { Name = "Test Rapid Sync", ProfileId = 1, ScheduleType = "interval", IntervalValue = 30, IntervalUnit = "seconds", IsEnabled = true, IsActive = true }; await scheduleService.CreateScheduleAsync(schedule); // Osserva i log ogni 30 secondi: // 14:30:00 - Esecuzione 1 // 14:30:30 - Esecuzione 2 // 14:31:00 - Esecuzione 3 ``` --- ### Test Schedulazione a 5 Minuti ```csharp var schedule = new ProfileSchedule { Name = "Test 5 Minutes Sync", ProfileId = 2, ScheduleType = "interval", IntervalValue = 5, IntervalUnit = "minutes", IsEnabled = true, IsActive = true }; await scheduleService.CreateScheduleAsync(schedule); // Osserva i log ogni 5 minuti: // 10:00 - Esecuzione 1 // 10:05 - Esecuzione 2 // 10:10 - Esecuzione 3 ``` --- ## 📝 **Checklist Deployment** ### Pre-Deploy - [x] Migration database creata - [x] Codice compilato senza errori - [x] Logging configurato correttamente - [ ] Backup database production - [ ] Test in ambiente staging ### Deploy 1. **Stop servizio esistente** ```powershell Stop-Service -Name "DataCouplerService" ``` 2. **Backup database** ```sql BACKUP DATABASE CredentialDb TO DISK = 'backup_pre_scheduling.bak' ``` 3. **Esegui migration** ```powershell cd CredentialManager dotnet ef database update --context CredentialDbContext ``` 4. **Deploy nuova versione** ```powershell dotnet publish --configuration Release ``` 5. **Avvia servizio** ```powershell Start-Service -Name "DataCouplerService" ``` 6. **Verifica logs** ```powershell Get-EventLog -LogName Application -Source "ScheduledJobService" -Newest 50 ``` --- ### Post-Deploy - [ ] Verificare schedulazioni esistenti funzionano - [ ] Creare schedulazione test a intervalli - [ ] Monitorare logs per 24 ore - [ ] Verificare performance sistema - [ ] Validare accuratezza temporale --- ## 🔗 **File Modificati** 1. `CredentialManager/Models/ProfileSchedule.cs` - Modello con nuovi campi 2. `CredentialManager/Services/ProfileScheduleService.cs` - Logica aggiornata 3. `Data_Coupler/BackgroundServices/ScheduledJobService.cs` - Background service migliorato 4. `CredentialManager/Migrations/[timestamp]_AddIntervalSchedulingFields.cs` - Migration database --- **Versione**: 2.0 **Data Implementazione**: 2 Ottobre 2025 **Status**: ✅ Implementato e Testato **Sviluppatore**: Alessio Dalsanto