Files
Data-Coupler/ADVANCED_SCHEDULING_SYSTEM.md
T
Alessio d042863a56 feat: Implementazione completa sistema schedulazione con intervalli personalizzati
- Aggiunto supporto schedulazione con intervalli flessibili (secondi/minuti/ore/giorni/settimane/mesi)
- Esteso modello ProfileSchedule con campi IntervalValue e IntervalUnit
- Ottimizzato ScheduledJobService per controlli ogni 30s con esecuzione parallela
- Implementata interfaccia UI completa con anteprima real-time in italiano
- Aggiunta migrazione database AddIntervalSchedulingFields
- Implementati metodi calcolo NextExecutionTime per intervalli
- Aggiunta gestione tracking anti-duplicati e cleanup automatico
- Creata documentazione completa (6 file, 2500+ righe)

Modifiche tecniche:
- ProfileSchedule.cs: Nuovi campi e metodi CalculateNextInterval/GetScheduleDescription
- ScheduledJobService.cs: Ridotto check interval a 30s, aggiunto parallel processing
- ProfileScheduleService.cs: Supporto calcolo intervalli in UpdateNextExecutionTimeAsync
- Scheduling.razor: Aggiunta sezione UI per configurazione intervalli
- Scheduling.razor.cs: Implementato GetIntervalPreview() e gestione stato campi
2025-10-02 01:12:39 +02:00

15 KiB

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

// 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

/// <summary>
/// Calcola la prossima esecuzione basandosi sulla data/ora base fornita
/// Per intervalli, aggiunge l'intervallo alla data base
/// </summary>
public DateTime? CalculateNextExecutionFromLast()

/// <summary>
/// Calcola la prossima esecuzione aggiungendo l'intervallo alla data base
/// </summary>
private DateTime CalculateNextInterval(DateTime baseTime)

/// <summary>
/// Ottiene una descrizione leggibile della schedulazione
/// </summary>
public string GetScheduleDescription()

Esempi di Utilizzo

// 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
// 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
// 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
private readonly Dictionary<int, DateTime> _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
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

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

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)

{
    "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

{
    "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)

{
    "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

{
    "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

// 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

// 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

// 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

-- 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

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

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

  • Migration database creata
  • Codice compilato senza errori
  • Logging configurato correttamente
  • Backup database production
  • Test in ambiente staging

Deploy

  1. Stop servizio esistente

    Stop-Service -Name "DataCouplerService"
    
  2. Backup database

    BACKUP DATABASE CredentialDb TO DISK = 'backup_pre_scheduling.bak'
    
  3. Esegui migration

    cd CredentialManager
    dotnet ef database update --context CredentialDbContext
    
  4. Deploy nuova versione

    dotnet publish --configuration Release
    
  5. Avvia servizio

    Start-Service -Name "DataCouplerService"
    
  6. Verifica logs

    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