Database
 sql >> Database >  >> RDS >> Database

Effetti collaterali non intenzionali:sessioni di sonno che tengono le serrature

Un recente impegno di consulenza si è concentrato sul blocco dei problemi all'interno di SQL Server che causavano ritardi nell'elaborazione delle richieste degli utenti dall'applicazione. Quando abbiamo iniziato ad approfondire i problemi riscontrati, è diventato chiaro che dal punto di vista di SQL Server il problema ruotava attorno alle sessioni in uno stato Inattivo che tenevano i blocchi all'interno dell'Engine. Questo non è un comportamento tipico per SQL Server, quindi il mio primo pensiero è stato che c'era una sorta di difetto di progettazione dell'applicazione che stava lasciando una transazione attiva su una sessione che era stata reimpostata per il pool di connessioni nell'applicazione, ma è stato rapidamente dimostrato che non per essere il caso poiché i blocchi sono stati successivamente rilasciati automaticamente, si è verificato solo un ritardo. Quindi, abbiamo dovuto approfondire ulteriormente.

Comprendere lo stato della sessione

A seconda del DMV che guardi per SQL Server, una sessione può avere alcuni stati diversi. Uno stato Sospensione significa che il motore ha completato il comando, tutto ciò tra client e server ha completato l'interazione e la connessione è in attesa che il comando successivo provenga dal client. Se la sessione dormiente ha una transazione aperta, è sempre correlata al codice e non a SQL Server. La transazione tenuta aperta può essere spiegata da un paio di cose. La prima possibilità è una procedura con una transazione esplicita che non attiva l'impostazione XACT_ABORT e quindi va in timeout senza che l'applicazione gestisca correttamente la pulizia come spiegato in questo post davvero vecchio del team CSS:

  • Come funziona:cos'è una sessione di comando dormiente/in attesa

Se la procedura avesse abilitato l'impostazione XACT_ABORT, avrebbe interrotto automaticamente la transazione allo scadere del tempo e la transazione sarebbe stata ripristinata. SQL Server sta eseguendo esattamente ciò che è necessario eseguire in base agli standard ANSI e mantenere le proprietà ACID del comando eseguito. Il timeout non è correlato a SQL Server, è impostato dal client .NET e dalla proprietà CommandTimeout, quindi anche questo è correlato al codice e non al comportamento di SQL Engine. Questo è lo stesso tipo di problema di cui ho parlato anche nella mia serie Eventi estesi, in questo post del blog:

  • Utilizzo di più target per eseguire il debug di transazioni orfane

Tuttavia, in questo caso l'applicazione non utilizzava stored procedure per l'accesso al database e tutto il codice è stato generato da un ORM. A questo punto l'indagine si è spostata da SQL Server verso il modo in cui l'applicazione utilizzava l'ORM e dove le transazioni sarebbero state generate dalla base di codice dell'applicazione.

Comprendere le transazioni .NET

È risaputo che SQL Server esegue il wrapping di qualsiasi modifica ai dati in una transazione di cui viene eseguito il commit automaticamente a meno che l'opzione impostata IMPLICIT_TRANSACTIONS non sia attiva per una sessione. Dopo aver verificato che questo non fosse attivo per nessuna parte del loro codice, era abbastanza sicuro presumere che tutte le transazioni rimanenti dopo che una sessione era in sospensione fosse il risultato di una transazione esplicita aperta da qualche parte durante l'esecuzione del loro codice. Ora si trattava solo di capire quando, dove e, soprattutto, perché non veniva chiuso immediatamente. Questo porta a uno dei pochi scenari diversi che avremmo dovuto cercare all'interno del codice del livello dell'applicazione:

  • L'applicazione che utilizza TransactionScope() attorno a un'operazione
  • L'applicazione che arruola una SqlTransaction() sulla connessione
  • Il codice ORM che racchiude internamente determinate chiamate in una transazione che non è stata impegnata

La documentazione per TransactionScope lo ha rapidamente escluso come possibile causa di ciò. Se non riesci a completare l'ambito della transazione, verrà automaticamente eseguito il rollback e l'interruzione della transazione quando viene eliminata, quindi non è molto probabile che ciò persista durante le reimpostazioni della connessione. Allo stesso modo, l'oggetto SqlTransaction eseguirà automaticamente il rollback se non è stato eseguito il commit quando la connessione viene reimpostata per il pool di connessioni, in modo che diventi rapidamente un elemento non iniziale per il problema. Questo ha appena lasciato la generazione del codice ORM, almeno era quello che pensavo, e sarebbe stato incredibilmente strano per una versione precedente di un ORM molto comune mostrare questo tipo di comportamento dalla mia esperienza, quindi abbiamo dovuto approfondire ulteriormente.

La documentazione per l'ORM che stanno utilizzando afferma chiaramente che quando si verifica un'azione multi-entità, viene eseguita all'interno di una transazione. Le azioni multi-entità potrebbero essere salvataggi ricorsivi o il salvataggio di una raccolta di entità nel database dall'applicazione e gli sviluppatori hanno concordato che questi tipi di operazioni si verificano su tutto il loro codice, quindi sì, l'ORM deve utilizzare le transazioni, ma perché lo erano all'improvviso diventando un problema.

La radice del problema

A questo punto abbiamo fatto un passo indietro e abbiamo iniziato a fare una revisione olistica dell'intero ambiente utilizzando New Relic e altri strumenti di monitoraggio disponibili quando si manifestavano i problemi di blocco. È diventato chiaro che le sessioni dormienti che trattenevano i blocchi si verificavano solo quando i server delle applicazioni IIS erano sottoposti a un carico estremo della CPU, ma che da solo non era sufficiente per tenere conto del ritardo che si vedeva nei commit delle transazioni rilasciando i blocchi. È inoltre emerso che i server delle applicazioni erano macchine virtuali in esecuzione su un host hypervisor sottoposto a overcommit e che i tempi di attesa CPU Ready per essi erano notevolmente elevati al momento dei problemi di blocco in base ai valori di somma forniti dall'amministratore della VM.

Lo stato di sospensione si verificherà con una transazione aperta che mantiene i blocchi tra le chiamate .SaveEntity degli oggetti completati e il commit finale nel codice generato dietro per gli oggetti. Se il server VM/app è sotto pressione o carico, ciò potrebbe subire ritardi e causare problemi con il blocco, ma il problema non è in SQL Server, sta facendo esattamente ciò che dovrebbe nell'ambito della transazione. Il problema è in definitiva il risultato del ritardo nell'elaborazione del punto di commit lato applicazione. Il recupero dei tempi dell'istruzione completata e degli eventi RPC completati da Eventi estesi insieme al tempo dell'evento database_transaction_end mostra il ritardo di andata e ritorno dal livello dell'app che chiude la transazione sulla connessione aperta. In questo caso, tutto ciò che viene visualizzato in SQL Server è vittima di un server delle applicazioni sovraccarico e di un host di macchine virtuali sovraccarico. Spostare/dividere il carico dell'applicazione tra i server in una configurazione bilanciata del carico hardware o NLB utilizzando host che non hanno un impegno eccessivo sull'utilizzo della CPU ripristinerebbe rapidamente il commit immediato delle transazioni e rimuoverebbe le sessioni di sospensione che mantengono i blocchi in SQL Server.

Ancora un altro esempio di un problema ambientale che causa quello che sembrava un normale problema di blocco. Vale sempre la pena indagare sul motivo per cui il thread di blocco non è in grado di rilasciare rapidamente i suoi blocchi.