# 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