Vai al contenuto
EuroVPSEuroVPS

Un'intera rete di siti web è diventata lenta tutta insieme, e nessun server era occupato.

Una rete di siti di recruiting si è sentita sovraccarica tutta in una volta. Ogni server era inattivo. Una ricerca di città valeva più di metà di tutto il tempo del database. Ecco come l'abbiamo trovata e risolta con un singolo indice online.

Di EuroVPS EngineeringLa sala macchine

5 min di lettura
In questo articolo 8 sezioni
In breve
  • Ogni sito di una rete di recruiting è rallentato insieme agli altri mentre i server restavano inattivi, e l'istinto era comprarne di più grandi.
  • Ogni livello era inattivo tranne uno: i load balancer e i web server lavoravano appena. L'intera farm aspettava il database, e il database aspettava una query.
  • Quella query, una semplice ricerca di città, valeva più di metà di tutto il tempo del database, scansionando un'intera tabella a ogni chiamata per restituire una sola riga.
  • Un indice online, applicato senza interrompere i siti, ha trasformato la scansione in una ricerca puntuale. Nessun server più grande necessario.

Una rete di siti di recruiting si è ammorbidita tutta nello stesso momento. Non una pagina, non un sito: tutti, insieme, quel tipo di nebbia su tutta la farm che porta un operatore a presumere che i server stiano annegando e che sia ora di comprarne di più grandi. Quella supposizione è costosa, è comune, e in questo caso era sbagliata. Ogni macchina della farm si annoiava. L'intera farm aspettava il database, e il database aspettava una singola query.

Ogni livello era inattivo tranne uno

In un rallentamento su tutta la farm, leggete l'utilizzo di ogni livello prima di qualsiasi altra cosa, e guardate quale è davvero inchiodato. I load balancer giravano a vuoto. I web server si annoiavano. Nulla nel percorso delle richieste lavorava abbastanza da spiegare un sito lento.

LA FARM, MISURATA
Load balancer
inattivi
Web server
lavoravano appena
Database
in attesa di una query

Questo è l'indizio che chiude la discussione sulla capacità prima che cominci. Se ogni livello è inattivo tranne il database, un web server più grande vi compra una macchina inattiva più veloce. Il vincolo non è mai stato il calcolo, e nessun ordine di hardware l'avrebbe risolto.

La risposta costosa era comprare più macchine

L'istintoLa misurazione
Tutta la farm è lenta, quindi i server sono sovraccarichi. Compratene di più grandi.Ogni livello tranne il database era inattivo. La capacità non è mai stata il vincolo.
COSA DICEVANO I DATIDev'essere un problema di hardware.L'hardware era sano, e in modo dimostrabile: load balancer e web server lavoravano appena.
Distribuite il carico su più macchine.Una query valeva più di metà di tutto il tempo del database. Più macchine l'avrebbero eseguita più spesso, sempre scansionando.

Più hardware avrebbe scalato lo spreco, non l'avrebbe rimosso. L'unico passo onesto successivo era scoprire su cosa il database stesse davvero spendendo il suo tempo.

Abbiamo aggregato la cronologia delle query per fingerprint

Un'applicazione scrive raramente la stessa query nello stesso modo due volte. La stessa ricerca compare parametrizzata in diversi modi, quindi contare il testo grezzo delle query nasconde il colpevole nel rumore. La soluzione è aggregare per un fingerprint che sopravvive a quell'incoerenza, poi classificare ogni statement per la sua quota del tempo totale del database. L'abbiamo eseguita sull'intera cronologia delle query.

AGGREGATE BY FINGERPRINT, RANK BY SHARE OF DB TIME
-- reconstructed, de-identified rendering of cleared facts. not a real capture.
-- full query history, grouped by query_hash
-- (a fingerprint that survives inconsistent parameterisation)
SELECT TOP 5
  query_hash,
  SUM(execution_count)                AS calls,
  SUM(total_logical_reads)            AS pages_read,
  SUM(total_elapsed_time) * 100.0
    / SUM(SUM(total_elapsed_time)) OVER () AS pct_of_db_time
FROM   sys.dm_exec_query_stats
GROUP  BY query_hash
ORDER  BY pct_of_db_time DESC;

Una riga spiccava: più di metà di tutto ciò che il database faceva, speso su un solo statement. Il metodo è volutamente noioso, non indovina, ed è lo stesso ogni volta.

Una query valeva più di metà del database

Lo statement che ne è uscito è del tipo che non guardereste mai due volte. Una ricerca di città, due colonne, un filtro.

THE QUERY AT THE TOP OF THE LIST
-- reconstructed, de-identified. table/column names renamed from the real schema.
SELECT CityID, CityName
FROM   Cities
WHERE  RoutingPath = @p;

-- Execution plan: Clustered Index Scan (a full scan)
--   Returns:          ONE row
--   Filtered column:  RoutingPath --> no index

La colonna su cui filtrava non aveva indice. Così ogni chiamata scansionava l'intera tabella per restituire una sola riga. Di per sé è un costo piccolo; ripetuto a ogni visualizzazione di pagina su tutta la rete, è diventato la cosa più grande che il database faceva.

Una risposta di una riga dovrebbe leggere un paio di pagine, non l'intera tabella

“Una risposta di una riga dovrebbe leggere due o tre pagine, non centinaia.”

LA FRASE CHE RIASSUME LA DIAGNOSI

Quella frase è l'intera diagnosi, compressa in qualcosa che potete dire alla persona che possiede il codice. Nient'altro fa leggere così tanto a una ricerca di una sola riga. Non un disco lento, non una CPU occupata, non una brutta nottata. Una risposta di una riga che legge l'intera tabella è una full scan, e una full scan su una colonna filtrata è una firma inconfondibile: la colonna ha bisogno di un indice. Non abbiamo dovuto discutere di hardware, abbiamo solo mostrato le letture.

Un indice online, da una scansione a una ricerca puntuale

OGNI CHIAMATA, PRIMA DELL'INDICE
  • Una scansione completa della tabella, per una riga
  • Nessun indice sulla colonna filtrata
  • Più di metà di tutto il tempo del database
OGNI CHIAMATA, DOPO UN INDICE
  • Una ricerca su indice, un paio di pagine per la stessa riga
  • Una piccola frazione del lavoro per chiamata
  • L'indice è entrato online, senza interrompere i siti
ONE ONLINE INDEX ON THE COLUMN THE APP ACTUALLY FILTERS ON
-- reconstructed, de-identified. index name illustrative, on the renamed identifiers.
-- Index the column in the WHERE clause; cover the two columns it returns
CREATE NONCLUSTERED INDEX IX_Cities_RoutingPath
  ON Cities (RoutingPath)
  INCLUDE (CityID, CityName)
  WITH (ONLINE = ON);   -- applied without interrupting the site

-- New execution plan: Index Seek

Un indice, sull'unica colonna su cui l'applicazione filtrava davvero. È stato applicato online, senza interrompere i siti, e la ricerca che aveva dominato il database è diventata una delle cose più economiche che facesse.

L'ingegnere del cliente ha arricchito la diagnosi

La parte migliore di questo intervento non è stato il nostro indice. È stato ciò che è successo quando l'ingegnere database del cliente si è unito e ha arricchito la diagnosi: ha portato uno schema di blocco tra letture e scritture dal suo lato del sistema. Abbiamo unito la sua correzione alla nostra, e le due sono entrate insieme.

“L'ingegnere del cliente ha lasciato la chiamata da collaboratore.”

COME DOVREBBERO FINIRE QUESTE CHIAMATE

Questa è la differenza tra un ticket di assistenza e una diagnosi. Quando il riscontro è vero e mostrate le prove, entrambe le parti smettono di difendere posizioni e cominciano a migliorare la piattaforma insieme.

Quando ogni livello è inattivo tranne uno, smettete di guardare la capacità

La lezione è breve ed è l'opposto del riflesso. Lentezza su tutta la farm con macchine inattive non è un problema di capacità, è una questione software travestita da capacità. Ecco cosa controllare prima di firmare un ordine di hardware.

01

Leggete prima l'utilizzo di ogni livello.

Load balancer, web, database. Se tutto è inattivo tranne un livello, la capacità non è il vostro problema e un server più grande non lo risolverà.

02

Aggregate le statistiche delle query per fingerprint.

Raggruppate la cronologia per un hash che sopravvive alla parametrizzazione incoerente, così che lo stesso statement scritto in più modi conti come uno. Il peggior colpevole di solito spicca con ampio margine.

03

Confrontate le pagine lette con le righe restituite.

Una risposta di una riga che legge centinaia di pagine sta scansionando, non cercando. Quel divario è la firma di un indice mancante.

04

Indicizzate la colonna su cui l'app filtra davvero.

Non ogni colonna. Quella nella clausola WHERE che gira milioni di volte. Applicatela online, senza interrompere il sito.

05

Mettete in cache le piccole tabelle calde.

Tabelle di lookup piccole e che cambiano di rado appartengono alla cache, non al percorso critico del database per ogni visualizzazione di pagina.

Per tutto il tempo, la macchina è rimasta calma. I load balancer giravano a vuoto, i web server si annoiavano, e la correzione è stata un indice sulla colonna su cui l'applicazione filtrava. Il server database era sano; uno statement al suo interno aveva bisogno di un indice.

Risposte dirette

Come può una query valere più di metà del carico di un database?+

Quando una query gira abbastanza spesso e fa troppo lavoro ogni volta, domina tutto il resto. Questa girava a ogni visualizzazione di pagina e scansionava un'intera tabella a ogni chiamata per restituire una sola riga, perché la colonna su cui filtrava non aveva indice. Moltiplicate un piccolo spreco per milioni di chiamate e diventa l'intero database.

Perché i server erano inattivi se i siti erano lenti?+

Perché il collo di bottiglia era dentro il database, in attesa di una query. I load balancer e i web server lavoravano appena. L'intera farm aspettava il database, e il database aspettava una ricerca che avrebbe dovuto essere istantanea. Aggiungere server avrebbe solo aggiunto altre macchine inattive.

Come si trova la query che è davvero il problema?+

Si aggrega l'intera cronologia delle query per un fingerprint che sopravvive alla parametrizzazione incoerente, poi si classifica per quota del tempo totale del database. Il metodo è noioso e non indovina.

Qual è la firma di un indice mancante?+

Una risposta di una riga che legge centinaia di pagine. Se una query restituisce una riga e il piano mostra una scansione dell'intera tabella, la colonna su cui filtra non è indicizzata.

Risolverlo ha richiesto downtime?+

No. L'indice è entrato online, applicato senza interrompere i siti, e ha trasformato la scansione in una ricerca puntuale.

Chi l'ha risolto, voi o il cliente?+

Entrambi, insieme. Noi abbiamo trovato la ricerca e aggiunto l'indice; l'ingegnere database del cliente si è unito alla diagnosi e ha portato uno schema di blocco tra letture e scritture dal suo lato. Abbiamo unito la sua correzione alla nostra.

Sito lento, server inattivi? Troveremo la vera causa.

Parlate con un ingegnere che legge i piani di esecuzione, non solo i dashboard, di cosa sta davvero rallentando la vostra piattaforma.

Parla con un ingegnere

L'istinto su una farm lenta è comprare una via d'uscita. La risposta onesta è quasi sempre più economica: leggete ogni livello, trovate quello inchiodato, e andate a leggere le sue query.

Siete arrivati alla fine.

Mettete la lettura al lavoro

Cosa fate girare?

Raccontateci il carico di lavoro, il traffico e i requisiti di ripristino. Partite dal configuratore di server, oppure discutete i dettagli con un ingegnere.

Scritto dagli ingegneri che costruiscono e gestiscono l'infrastruttura che descrive.
Parla con un ingegnere
XIn

5 min rimasti