Deadlock voodoo

Ciao a tutti, vorrei raccontarvi, nel breve, l’esperienza che abbiamo avuto con l’errore di deadlock.

L’applicazione web è fatta con InDe Foundation 21.5, C#, .NET 2.0, che lavora con un db MSSQL, ed è stata creata nell’ambito dell’industria 4.0, riceve i dati dalla macchina, li elabora e li visualizza a video tramite un timer impostato con un tick al secondo.
Scendendo nel dettaglio:
. Una smartbox è in attesa che la macchina gli fornisca dei dati. Appena ricevuti apre una connessione al DB, esegue la insert e chiude la connessione. I valori passati dalla macchina sono memorizzati in formato grezzo in un campo Text.
. La procedura eseguita dall’applicazione InDe, ad ogni tick, esegue una foreach readwrite dei dati non trattati, ne ricava i valori e li memorizza in ulteriori campi dello stesso record. Dopodiché legge ciò che gli interessa e lo mostra a video.

Abbiamo faticato parecchio per ottimizzare il tutto (transazioni, indici, semafori, danza della pioggia…), ma senza risultati. Attivando (dal cliente) il profiler del management studio di SQL, per i soli deadlock, abbiamo scoperto questo:
Le operazioni che vanno in deadlock sono la SELECT dei dati non trattati e la INSERT della smartbox.
La SELECT applica un lock di tipo ‘S’ alla Pagina:1, e rimane in attesa di poter accedere alla Pagina:2.
La INSERT applica un lock di tipo ‘IX’ alla Pagina:2, e rimane in attesa di poter accedere alla Pagina:1.
I lock "S’ e ‘IX’ non sono compatibili, e il DB uccide la SELECT.

Supponiamo che il problema sia nel fatto che la INSERT debba lavorare su due pagine, Stackoverflow suggerisce che sia un problema di indici che contengono la stessa chiave, ma noi non abbiamo questa situazione. Forse invece la colpa è del campo Text. Allo stato attuale, non lo sappiamo.

La soluzione che abbiamo in mente di adottare è rendere l’applicazione web resistente ai deadlock, intercettando questo specifico errore e riprovando per un numero finito di volte, magari con un ritardo di qualche millisecondo.

A voi è mai capitato? Quali soluzioni avete adottato? :slight_smile: :wave:

3 Mi Piace

Ciao, non ho una soluzione ma ho un video di altissima qualità che da una serie di indicazioni per la mitigazione dei deadlock. Ho avuto modo di assistere ad una sua presentazione ed ho trovato estremamente interessante la presentazione. Long Story Short: non è possibile eliminarli del tutto ma sicuramente si possono mitigare facendo attenzione ad alcuni aspetti. Il video è su: Deadlocks – Analysing, Preventing and Mitigating by Erland Sommarskog - YouTube
La pagina di Erland è https://sommarskog.se/

5 Mi Piace

Che personaggio… Grazie, molto interessante :+1:

@r.bianco mi capitava quando lavoravo in SQL sulle macchine AS/400 di IBM e la soluzione di riprovare per un certo numero di volte con un ritardo era la soluzione adottata anche da noi.

Il video è molto interessante.

1 Mi Piace

Ciao
ti chiedo alcuni chiarimenti:

  • la procedura eseguita da InDe è una procedura sql o una procedura Inde?
  • cosa intendi per “foreach readwrite”?
  • la transazione è interna o esterna al foreach?
  • la procedura può impiegare più di un secondo e quindi posso avere più procedure in esecuzione contemporaneamente?

In genere i deadlock avuti in passato con Sql Server e che non erano riconducibili ad un errore di programmazione erano imputabili ad un lock escalation da parte del DBMS.

Consiglio:

  • tenere piccola la transazione sia in termini di spazio che di tempo
  • evitare eccessiva concorrenza di read/write sulla stessa tabella quando si opera su più righe

Ad esempio nel caso descritto eviterei di scrivere i dati ricavati dal campo text sulla stessa tabella “raw” che viene scritta dalla smartbox.
Procederei così:

  • creare tabella sibling con stesso id della tabella grezza e che contiene gli ulteriori campi (l’FK da sibling a grezza si potrebbe evitare)
  • i dati non trattati sono quelli della tabella grezza che non esistono nella tabella sibling, li seleziono (non in transazione), tratto il dato grezzo e inserisco con una where che verifica se il record nel frattempo è già stato scritto da qualche altro thread. Questo aspetto ti permette di evitare gestioni complesse di lock o sincronizzazioni: accetto che due o più thread elaborino lo stesso record piuttosto che appesantire la gestione dei thread stessi. La where evita che il db vada in errore e debba gestire il rollback, se la riga è stata già trattata inserirà 0 righe.

Che ne pensi?

1 Mi Piace

Procedura InDe

Il blocco foreach di InDe nasce come readonly, ma puoi cambiarlo in readwrite. In questo modo, all’interno del ciclo puoi modificare il valore degli elementi del foreach, e all’uscita viene eseguito un update del recordset estratto.
Se ne era parlato nel forum:
The Instant Developer Forums • Leggi argomento - deadlock

Esterna.

Quando il cliente ha lamentato il problema, abbiamo eseguito un test: in effetti la qry di estrazione dei dati impiegava 4 sec. Abbiamo aggiunto un indice e svuotato la tabella dai record ormai storici, arrivando a qualche millisecondo. Ma il problema non è stato risolto.

Bella idea. Noi usiamo un campo flag trattato nella stessa tabella, soluzione non ottimale perché potrei avere più processi che leggono e scrivono le stesse risorse.

Penso sia una buona soluzione, sicuramente più solida di quella che abbiamo messo in piedi noi.

Grazie :+1:

Per ora abbiamo optato per un meccanismo di retry, rinunciando alla transazione perché il deadlock la fa morire, e riprovando per tre volte (solo nel caso di deadlock) all’interno dello stesso tick (1 secondo).
Se anche dopo la terza volta permane l’errore di deadlock, questo non viene segnalato e si aspetta il prossimo tick.

1 Mi Piace