Ciao, Habr! Negli ultimi mesi abbiamo vissuto una situazione molto interessante e vorrei condividere la nostra storia di scalabilità dell'infrastruttura. In questo periodo, SberMarket ha quadruplicato il numero di ordini e ha lanciato il servizio in 17 nuove città. L'esplosivo aumento della domanda per la consegna di prodotti ha richiesto da parte nostra un adeguamento dell'infrastruttura. Leggete dopo il tag per i dettagli più interessanti e utili.

Mi chiamo Dima Bobylev, sono il direttore tecnico di SberMarket. Poiché questo è il primo post del nostro blog, dirò alcune parole su di me e sull'azienda. Lo scorso autunno ho partecipato a un concorso per giovani leader di Runet. Per il contest, ho su come vediamo in SberMarket la cultura interna e l'approccio allo sviluppo del servizio. E anche se non ho vinto il concorso, ho comunque formulato i principi fondamentali per lo sviluppo dell'ecosistema IT.
Gestire un team richiede di comprendere e trovare un equilibrio tra ciò che serve al business e le esigenze di ogni singolo sviluppatore. Attualmente SberMarket cresce di 13 volte anno dopo anno, e questo influisce sul prodotto, richiedendo un costante incremento delle quantità e dei ritmi di sviluppo. Nonostante ciò, dedichiamo abbastanza tempo agli sviluppatori per un'analisi preliminare e una scrittura di codice di qualità. L'approccio consolidato aiuta non solo nella creazione di un prodotto funzionante, ma anche nella sua successiva scalabilità e sviluppo. Come risultato di questa crescita, SberMarket è già diventato un leader tra i servizi di consegna di prodotti: consegniamo quotidianamente circa 18.000 ordini al giorno, mentre all'inizio di febbraio erano circa 3.500.

Una volta un cliente ha chiesto a un corriere di SberMarket di consegnare i prodotti in modo contactless — direttamente sul balcone.
Passiamo ai dettagli. Negli ultimi mesi, abbiamo lavorato attivamente per scalare l'infrastruttura della nostra azienda. Questa necessità è stata motivata da fattori esterni e interni. Allo stesso tempo, con l’espansione della base clienti, il numero dei negozi connessi è aumentato da 90 all'inizio dell'anno a più di 200 a metà maggio. Ci siamo naturalmente preparati, riservando l'infrastruttura principale e prevedendo la possibilità di scalare verticalmente e orizzontalmente tutte le macchine virtuali ospitate nel cloud di Yandex. Tuttavia, la pratica ha dimostrato che: «Tutto ciò che può andare storto, andrà storto». Oggi voglio condividere con voi le situazioni più curiose che si sono verificate durante queste settimane. Spero che la nostra esperienza possa esservi utile.
Slave è pronto per l'azione
Ancora prima dell'inizio della pandemia, ci siamo trovati ad affrontare un aumento delle richieste sui nostri server backend. La tendenza a ordinare prodotti con consegna a domicilio stava guadagnando slancio e, con l'introduzione delle prime misure di isolamento sociale a causa del COVID-19, il carico aumentava drammaticamente durante tutto il giorno. Si è resa necessaria un'operazione rapida per scaricare i master server del database principale e spostare parte delle richieste di lettura sui server replica (slave).
Ci eravamo preparati in anticipo a questo passo e per tale manovra erano già stati attivati 2 server slave. Su di essi lavoravano principalmente i task batch per la generazione di feed informativi per lo scambio di dati con i partner. Questi processi creavano un carico aggiuntivo e sono stati giustamente esclusi «per qualche mese» prima.
Poiché su Slave avveniva la replica, abbiamo seguito il concetto secondo cui le applicazioni possono interagire con essi solo in modalità di sola lettura. Il Piano di Disaster Recovery prevedeva che, in caso di catastrofe, potessimo semplicemente montare Slave al posto di Master e reindirizzare tutte le richieste di scrittura e lettura su Slave. Tuttavia, volevamo anche utilizzare le repliche per le esigenze del dipartimento di analisi, quindi i server non erano completamente convertiti in modalità di sola lettura, con un set di utenti specifico su ciascun host, e alcuni avevano diritti di scrittura per salvare i risultati intermedi dei calcoli.
Fino a un certo livello di carico, avevamo sufficienti risorse per scrivere e leggere durante l'elaborazione delle richieste http. A metà marzo, proprio quando Sbermarket ha deciso di passare completamente al lavoro da remoto, abbiamo iniziato a vedere una crescita esponenziale del RPS. Sempre più clienti si sono messi in autoisolamento o hanno iniziato a lavorare da casa, il che ha avuto un impatto sui parametri di carico.
La capacità del "master" non era più sufficiente, quindi abbiamo iniziato a trasferire parte delle richieste di lettura più pesanti su una replica. Per dirigere in modo trasparente le richieste di scrittura al master e quelle di lettura allo slave, abbiamo utilizzato la gemma ruby “”. Abbiamo creato un utente speciale con il suffisso _readonly senza diritti di scrittura. Ma a causa di un errore nella configurazione di uno degli host, parte delle richieste di scrittura è andata al server slave a nome di un utente che aveva i diritti appropriati.
Il problema non si è manifestato subito, poiché il carico aumentato ha aumentato il ritardo degli slave. L'incoerenza dei dati è stata scoperta al mattino, quando, dopo le importazioni notturne, gli slave non hanno "raggiunto" il master. Abbiamo attribuito questo all'alta pressione sul servizio stesso e all'importazione legata all'apertura di nuovi negozi. Tuttavia, fornire dati con un ritardo di molte ore era inaccettabile, così abbiamo spostato i processi su un secondo slave analitico, poiché aveva risorse bunlarghe ed era poco caricato da richieste di lettura (il che ha spiegato a noi stessi l'assenza di lag nella replica).
Quando abbiamo risolto le cause del "disallineamento" dello slave principale, lo slave analitico era già fuori uso per la stessa ragione. Nonostante ci fosse un certo numero di server aggiuntivi su cui avevamo pianificato di trasferire il carico in caso di crash del master, a causa di un errore imprevisto ci siamo trovati senza alcuno di essi nel momento critico.
Ma poiché non ci limitavamo solo a un dump del database (il ripristino in quel momento richiedeva circa 5 ore), ma anche a uno snapshot del server master, siamo riusciti a avviare la replica in 2 ore. Tuttavia, dopo questo ci aspettava un lungo processo di applicazione del log di replica (poiché il processo avviene in modalità a thread singolo, ma questa è un'altra storia).
Conclusione: Dopo un incidente di questo tipo, è diventato chiaro che era necessario abbandonare la pratica di limitare la registrazione per gli utenti e dichiarare in sola lettura l'intero server. Con questo approccio, non c'è dubbio che le repliche saranno disponibili in un momento critico.
L'ottimizzazione anche di una sola query pesante può "ridare vita" al database.
Anche se aggiorniamo costantemente il catalogo sul sito, le query che eseguiamo sui server Slave mostrano un leggero ritardo rispetto al Master. Il tempo impiegato per scoprire e risolvere il problema dei "Slave improvvisamente disattivati" è stato superiore al "confine psicologico" (in quel lasso di tempo potrebbe essere avvenuto un aggiornamento dei prezzi e i clienti avrebbero visto dati obsoleti), e abbiamo dovuto reindirizzare tutte le query al server principale del database. Di conseguenza, il sito è stato lento... ma almeno funzionava. E mentre lo Slave si riprendeva, non ci rimaneva altro da fare se non ottimizzare.
Mentre i server Slave si riprendevano, i minuti passavano lentamente, il Master era sovraccarico e noi abbiamo concentrato tutte le forze nell'ottimizzazione delle attività attive secondo la "Regola di Pareto": abbiamo scelto le query TOP che generavano la maggior parte del carico e abbiamo iniziato a fare tuning. Questo è stato fatto "in tempo reale".
Un effetto interessante era che un MySQL carico rispondeva anche a un miglioramento minimo dei processi. L'ottimizzazione di un paio di query che davano solo il 5% del carico totale ha già mostrato un notevole alleviamento della CPU. Di conseguenza, siamo riusciti a garantire un adeguato margine di risorse per far funzionare il Master con il database e ottenere il tempo necessario per ripristinare le repliche.
Conclusione: Anche una piccola ottimizzazione consente di "sopravvivere" in caso di sovraccarico per alcune ore. Questo è stato sufficiente per il recupero dei server con le repliche. A proposito, discuteremo l'aspetto tecnico dell'ottimizzazione delle query in uno dei prossimi post. Quindi iscriviti al nostro blog se potrebbe esserti utile.
Organizza il monitoraggio della funzionalità dei servizi partner.
Ci occupiamo dell'elaborazione degli ordini dei clienti e, per questo motivo, i nostri servizi interagiscono costantemente con API esterne: sono gateway per l'invio di SMS, piattaforme di pagamento, sistemi di routing, geocodifica, servizi fiscali e molte altre persone. E quando il carico ha iniziato a crescere rapidamente, abbiamo cominciato a scontrarci con le limitazioni delle API dei nostri partner, che prima non avevamo nemmeno considerato.
Un'improvvisa superamento delle quote dei servizi partner può portare a tempi di inattività per il proprio. Molte API bloccano i clienti che superano i limiti e, in alcune situazioni, un eccesso di richieste può sovraccaricare il partner in produzione.
Ad esempio, nel momento in cui cresceva il numero delle consegne, i servizi di accompagnamento non riuscivano a gestire i compiti di distribuzione e determinazione dei percorsi. Di conseguenza, accadeva che gli ordini fossero stati effettuati, ma il servizio che creava il percorso non funzionava. Va detto che i nostri logisti hanno fatto l'impossibile in queste condizioni, e la chiara interazione del team ha contribuito a compensare i fallimenti temporanei dei servizi. Ma un tale volume di richieste non può essere gestito manualmente in modo continuato, e dopo un po' ci saremmo trovati di fronte a un'interruzione inaccettabile tra gli ordini e la loro esecuzione.
È stata adottata una serie di misure organizzative, e il lavoro coordinato della squadra ha aiutato a guadagnare tempo mentre discutevamo nuove condizioni e aspettavamo l'aggiornamento dei servizi da parte di alcuni partner. Esistono anche altre API che si distinguono per l'alta resistenza e tariffe ridicole in caso di alti volumi di traffico. Ad esempio, all'inizio abbiamo utilizzato un noto API di mapping per determinare l'indirizzo del punto di consegna. Ma alla fine del mese abbiamo ricevuto una fattura di quasi 2 milioni di rubli. Dopo di ciò, abbiamo deciso di sostituirlo rapidamente. Non farò pubblicità, ma dirò che le nostre spese sono diminuite significativamente.

Conclusione: È assolutamente necessario monitorare le condizioni operative di tutti i servizi partner e tenerle a mente. Anche se oggi sembra che abbiano un "ampio margine", non significa che domani non diventino un ostacolo per la crescita. E, ovviamente, è meglio stabilire in anticipo le condizioni finanziarie per le richieste crescenti al servizio.
A volte si scopre che "" (с)
Siamo abituati a blocchi nel database principale o nei server delle applicazioni, ma durante la scalabilità i problemi possono manifestarsi dove meno te lo aspetti. Per la ricerca full-text sul sito utilizziamo il motore Apache Solr. Con l'aumento del carico abbiamo notato un abbassamento dei tempi di risposta, mentre il carico della CPU del server raggiungeva già il 100%. Che cosa c'è di più semplice: forniamo al container Solr più risorse.
Invece dell'atteso aumento delle prestazioni, il server è semplicemente 'morto'. Si caricava immediatamente al 100% e rispondeva ancora più lentamente. Inizialmente avevamo 2 core e 2 GB di RAM. Abbiamo deciso di fare ciò che di solito aiuta: abbiamo dato al server 8 core e 32 GB. È andato tutto molto peggio (come e perché lo racconteremo in un post a parte).
In pochi giorni abbiamo approfondito le sottigliezze di questa questione e abbiamo raggiunto prestazioni ottimali con 8 core e 32 GB. Questa configurazione ci consente ancora oggi di continuare a incrementare il carico, il che è molto importante, perché la crescita non sta avvenendo solo per i clienti, ma anche per il numero di negozi collegati: in 2 mesi il loro numero è raddoppiato.
Conclusione: I metodi standard, come 'aggiungere più hardware', non funzionano sempre. Quindi, durante la scalabilità di qualsiasi servizio, è importante comprendere bene come utilizza le risorse e testarne prima il funzionamento in nuove condizioni.
Stateless — la chiave per una semplice scalabilità orizzontale
Nel complesso, il nostro team adotta l'approccio noto: i servizi non devono avere uno stato interno (stateless) e devono essere indipendenti dall'ambiente di esecuzione. Questo ci ha permesso di affrontare l'aumento del carico attraverso una semplice scalabilità orizzontale. Ma abbiamo avuto un servizio eccezionale: un gestore di lunghi processi in background. Si occupava dell'invio di email e sms, della gestione di eventi, della generazione di feed, dell'importazione di prezzi e scorte, della lavorazione delle immagini. Così è capitato che dipendesse da un'archiviazione locale e fosse presente in un unico esemplare.
Quando è aumentato il numero di compiti in coda nel processore (cosa accaduta naturalmente con l'aumento del numero di ordini), le prestazioni dell'host su cui erano ospitati il processore e il sistema di archiviazione files sono diventati un fattore limitante. Di conseguenza, l'aggiornamento dell'assortimento e dei prezzi, l'invio di notifiche agli utenti e molte altre funzioni critiche si sono bloccate in coda. Il team Ops ha migrato rapidamente il sistema di archiviazione a un deposito di rete simile a S3, il che ci ha permesso di attivare diverse macchine potenti per scalare il processore dei compiti in background.
Conclusione: La regola Stateless deve essere rispettata per tutti i componenti senza eccezione, anche se sembra "che qui non ci sia il rischio di problemi". È meglio spendere un po' di tempo nella corretta organizzazione del lavoro di tutti i sistemi piuttosto che dover riscrivere il codice e riparare il servizio sovraccarico in fretta.
7 principi per una crescita intensa
Nonostante la disponibilità di capacità aggiuntive, durante il processo di crescita abbiamo incontrato alcuni ostacoli. In questo periodo il numero di ordini è aumentato di oltre 4 volte. Ora consegniamo già più di 17.000 ordini al giorno in 62 città e prevediamo di espandere ulteriormente la nostra geografia: nel primo semestre del 2020 è prevista l'apertura del servizio in tutta la Russia. Per affrontare il carico crescente, tenendo conto delle esperienze già fatte, abbiamo definito 7 principi fondamentali per lavorare in condizioni di crescita costante:
- Incident management. Abbiamo creato una bacheca in Jira, dove ogni incidente è rappresentato da un ticket. Questo aiuta a prioritizzare e a svolgere i compiti relativi all'incidente. Infatti, non è spaventoso commettere errori — ciò che spaventa è sbagliare due volte per la stessa cosa. Per quei casi in cui gli incidenti si ripetono prima che si riesca a correggere la causa, si deve avere pronte delle istruzioni su cosa fare, poiché durante un carico elevato è importante rispondere prontamente.
- Monitoraggio è richiesto per tutti gli elementi dell'infrastruttura senza eccezioni. È grazie a questo che siamo stati in grado di prevedere la crescita del carico e di identificare correttamente i "collo di bottiglia" per la priorità di risoluzione. È probabile che, in caso di carico elevato, si rompa o inizi a rallentare tutto ciò di cui non avresti pensato. Pertanto, è meglio creare nuovi avvisi subito dopo i primi incidenti, per monitorarli e prevenirli.
- Avvisi corretti sono semplicemente necessari in caso di aumento improvviso del carico. In primo luogo, devono segnalare esattamente cosa si è rotto. In secondo luogo, non dovrebbero esserci troppi avvisi, poiché un eccesso di avvisi non critici porta all'ignoranza di tutte le notifiche.
- Le applicazioni devono essere stateless. Ci siamo assicurati che non ci siano eccezioni per questa regola. È necessaria una completa indipendenza dall'ambiente di esecuzione. A questo proposito, puoi memorizzare i dati condivisi in un database o, ad esempio, direttamente in S3. È anche meglio seguire le regole. Durante un aumento improvviso del tempo, non c'è modo di ottimizzare il codice e affrontare il carico sarà necessario farlo aumentando direttamente le risorse di calcolo e con la scalabilità orizzontale.
- Quote e prestazioni dei servizi esterni. Con una rapida crescita, il problema può sorgere non solo nella tua infrastruttura, ma anche nel servizio esterno. La cosa più frustrante è quando ciò accade non a causa di un guasto, ma a causa del raggiungimento di quote o limiti. Pertanto, i servizi esterni devono scalare altrettanto bene quanto te.
- Separare i processi e le code. Questo aiuta molto quando uno dei gateway ha un intoppo. Non avremmo avuto ritardi nella trasmissione dei dati se le code piene per l'invio degli SMS non avessero ostacolato lo scambio di notifiche tra i sistemi informativi. Inoltre, sarebbe stato più semplice aumentare il numero di worker se avessero lavorato separatamente.
- Realtà finanziarie. Quando si verifica una crescita esplosiva dei flussi di dati, non c'è tempo per pensare alle tariffe e agli abbonamenti. Ma è necessario ricordarle, soprattutto se siete una piccola azienda. Una grande fattura può essere presentata dal proprietario di qualsiasi API, così come dal tuo provider di hosting. Pertanto, è importante leggere attentamente i contratti.
Conclusione
Non senza perdite, ma siamo riusciti a superare questa fase e oggi ci impegniamo a rispettare tutti i principi trovati, e ogni macchina ha la possibilità di un facile aumento delle prestazioni di x4 per affrontare eventuali imprevisti.
Nei prossimi post condivideremo la nostra esperienza nell'indagine sui cali di performance in Apache Solr, e parleremo dell'ottimizzazione delle richieste e di come l'interazione con l'FNS aiuti l'azienda a risparmiare denaro. Iscriviti al nostro blog per non perdere nulla e raccontaci nei commenti se hai mai avuto simili problemi durante l'aumento del traffico.

Solo gli utenti registrati possono partecipare al sondaggio. , per favore.
Hai mai riscontrato rallentamenti/o inattività del servizio a causa di un improvviso aumento del carico dovuto a:
55,6%Impossibilità di aggiungere rapidamente risorse computazionali10
16,7%Limiti dell'infrastruttura del fornitore di hosting3
33,3%Limiti delle API esterne6
27,8%Violazione dei principi stateless delle tue applicazioni5
88,9%Non ottimizzazione del codice dei propri servizi16
Hanno votato 18 utenti. Si sono astenuti 6 utenti.
Fonte: habr.com
