Ciao, Habr! Mi chiamo Maxim Vasiliev, lavoro come analista e project manager in FINCH. Oggi vorrei raccontare come, utilizzando ElasticSearch, siamo riusciti a elaborare 15 milioni di richieste in 6 minuti e ottimizzare i carichi quotidiani sul sito di uno dei nostri clienti. Sfortunatamente, dovremo fare a meno dei nomi, poiché abbiamo un NDA, speriamo che il contenuto dell'articolo non ne risenta. Let`s go.
Come è strutturato il progetto
Sul nostro backend creiamo servizi che garantiscono il funzionamento dei siti e dell'applicazione mobile del nostro cliente. La struttura generale può essere vista nello schema:

Nel processo di lavoro elaboriamo un grande numero di transazioni: acquisti, pagamenti, operazioni con i saldi degli utenti, per i quali conserviamo molti log, oltre a importare ed esportare questi dati in sistemi esterni.
Ci sono anche processi inversi, quando riceviamo dati dal cliente e li trasmettiamo agli utenti. Inoltre, esistono anche processi relativi ai pagamenti e ai programmi di premi.
Breve introduzione
Inizialmente utilizzavamo PostgreSQL come unica soluzione di archiviazione dei dati. I suoi standard vantaggi per DBMS: disponibilità di transazioni, un linguaggio di query avanzato, un'ampia gamma di strumenti per l'integrazione; insieme a buone prestazioni, soddisfacevano le nostre esigenze per un bel po' di tempo.
Conservavamo in Postgres tutti i dati: dalle transazioni alle notizie. Ma il numero di utenti cresceva, e con esso anche il numero di richieste.
Per capire, il numero annuale di sessioni nel 2017 solo sul sito desktop era di 131 milioni. Nel 2018 era di 125 milioni. Nel 2019 ancora 130 milioni. Aggiungi altro 100-200 milioni dalla versione mobile del sito e dall'app mobile, e otterrai un numero colossale di richieste.
Con la crescita del progetto, Postgres ha smesso di far fronte al carico, eravamo in ritardo - è emerso un gran numero di richieste diverse, per le quali non abbiamo potuto creare un numero sufficiente di indici.
Abbiamo capito che c'era bisogno di altri sistemi di archiviazione dei dati, che soddisfacessero le nostre esigenze e riducessero il carico su PostgreSQL. Abbiamo considerato come opzioni Elasticsearch e MongoDB. Quest'ultimo perdeva su questi punti:
- La velocità di indicizzazione lenta con l'aumento del volume dei dati negli indici. Con Elastic, la velocità non dipende dal volume dei dati.
- Nessuna ricerca full-text
Così abbiamo scelto Elastic e ci siamo preparati alla migrazione.
Migrazione a Elastic
1. Abbiamo iniziato la migrazione dal servizio di ricerca dei punti vendita. Il nostro cliente ha un totale di circa 70.000 punti vendita e sono necessari diversi tipi di ricerca sul sito e nell'app:
- Ricerca testuale per nome della località
- Ricerca geolocalizzata entro un certo raggio da un punto specifico. Ad esempio, se un utente vuole vedere quali punti vendita sono più vicini a casa sua.
- Ricerca all'interno di un quadrato specificato: l'utente disegna un quadrato sulla mappa e gli vengono mostrati tutti i punti in quel raggio.
- Ricerca tramite filtri aggiuntivi. I punti vendita differiscono tra loro per assortimento.
Parlando dell'organizzazione, in Postgres abbiamo una fonte di dati sia per la mappa che per le notizie, mentre in Elastic vengono creati snapshot dei dati originali. Il problema è che inizialmente Postgres non riusciva a gestire la ricerca su tutti i criteri. Non solo c'erano molti indici, ma potevano anche sovrapporsi, quindi il planner di Postgres si confondeva e non capiva quale indice utilizzare.
2. Il prossimo passo è stato la sezione notizie. Ogni giorno appaiono pubblicazioni sul sito; per evitare che l'utente si perda nel flusso di informazioni, i dati devono essere ordinati prima della visualizzazione. A questo serve la ricerca: sul sito è possibile cercare per corrispondenza testuale e, in aggiunta, attivare filtri supplementari, poiché anche questi sono implementati tramite Elastic.
3. Poi abbiamo trasferito l'elaborazione delle transazioni. Gli utenti possono acquistare determinati prodotti sul sito e partecipare all'estrazione di premi. Dopo tali acquisti, elaboriamo una grande quantità di dati, specialmente nei fine settimana e durante le festività. Per fare un confronto, nei giorni normali il numero di acquisti si aggira intorno ai 1,5-2 milioni, mentre durante le festività può arrivare fino a 53 milioni.
Nel frattempo, i dati devono essere elaborati nel minor tempo possibile: gli utenti non amano aspettare giorni per un risultato. Attraverso Postgres non si possono ottenere tali tempi — ricevevamo spesso blocchi e, mentre elaboravamo tutte le richieste, gli utenti non potevano verificare se avevano vinto premi o meno. Questo non è molto gradito per il business, quindi abbiamo trasferito l'elaborazione in Elasticsearch.
Frequenza
Attualmente gli aggiornamenti sono impostati per eventi, secondo le seguenti condizioni:
- Punti vendita. Non appena riceviamo dati da una fonte esterna, avviamo immediatamente l'aggiornamento.
- Notizie. Appena una notizia viene modificata sul sito, essa viene automaticamente inviata a Elastic.
Qui vale la pena ripetere i vantaggi di Elastic. In Postgres, durante l'invio della richiesta, bisogna attendere che elabori onestamente tutte le registrazioni. In Elastic è possibile inviare 10.000 registrazioni e iniziare a lavorare immediatamente, senza attendere che le registrazioni vengano distribuite tra tutti i Shard. Certo, qualche Shard o Replica potrebbe non vedere immediatamente i dati, ma molto presto tutto sarà accessibile.
Metodi di integrazione
Ci sono 2 metodi di integrazione con Elastic:
- Attraverso il client nativo via TCP. Il driver nativo è in via di estinzione: non è più supportato ed ha una sintassi molto scomoda. Pertanto, lo utilizziamo praticamente mai e cerchiamo di rinunciare completamente ad esso.
- Attraverso l'interfaccia HTTP, nella quale è possibile utilizzare sia richieste JSON che la sintassi Lucene. Quest'ultima è il motore testuale che utilizza Elastic. In questa modalità otteniamo la possibilità di Batch tramite richieste JSON via HTTP. È proprio questa modalità che cerchiamo di utilizzare.
Grazie all'interfaccia HTTP possiamo utilizzare librerie che forniscono un'implementazione asincrona del client HTTP. Possiamo approfittare del Batch e delle API asincrone, il che alla fine offre elevate prestazioni, che sono state molto utili nei giorni di grandi eventi (di questo parleremo più avanti)
Alcuni numeri per il confronto:
- Salvataggio degli utenti che hanno ricevuto premi in Postgres in 20 thread senza raggruppamenti: 460.713 registrazioni in 42 secondi
- Elastic + client reattivo su 10 thread + batch su 1000 elementi: 596.749 registrazioni in 11 secondi
- Elastic + client reattivo su 10 thread + batch su 1000 elementi: 23.801.684 registrazioni in 4 minuti
Attualmente abbiamo scritto un gestore di richieste HTTP che costruisce JSON, come Batch/non Batch, e invia attraverso qualsiasi client HTTP indipendentemente dalla libreria. È anche possibile scegliere di inviare richieste in modo sincrono o asincrono.
In alcune integrazioni utilizziamo ancora il client di trasporto ufficiale, ma è solo una questione di una prossima rifattorizzazione. Nel frattempo, per l'elaborazione utilizziamo un client proprietario costruito sulla base di Spring WebClient.

Grande evento
Una volta all'anno, nel progetto si svolge una grande promozione per gli utenti: è il famoso Highload, poiché in questo periodo lavoriamo con decine di milioni di utenti contemporaneamente.
Di solito, i picchi di carico si verificano durante i giorni festivi, ma questa promozione è su un altro livello. Nell'anno precedente, nel giorno della promozione, abbiamo venduto 27.580.890 unità di prodotto. I dati sono stati elaborati per più di mezz'ora, causando disagi agli utenti. Gli utenti hanno ricevuto premi per la partecipazione, ma è diventato chiaro che il processo doveva essere accelerato.
All'inizio del 2019, abbiamo deciso che ci voleva ElasticSearch. Per un intero anno, abbiamo organizzato l'elaborazione dei dati ricevuti in Elastic e la loro fornitura nell'api dell'app mobile e del sito. Di conseguenza, l'anno successivo, durante la promozione abbiamo elaborato 15.131.783 record in 6 minuti.
Poiché abbiamo molti utenti interessati ad acquistare prodotti e partecipare all'estrazione dei premi nelle promozioni, si tratta di una misura temporanea. Attualmente, inviamo informazioni aggiornate a Elastic, ma in futuro prevediamo di trasferire le informazioni archiviate dei mesi passati in Postgres, come archivio permanente. Così da non intasare l'indice Elastic, che ha anch'esso le sue limitazioni.
Conclusioni
Al momento, abbiamo trasferito su Elastic tutti i servizi che volevamo e per ora ci siamo fermati. Ora stiamo costruendo un indice in Elastic sopra il principale archivio persistente in Postgres, che si occupa del carico degli utenti.
In futuro, prevediamo di trasferire i servizi se comprendiamo che le richieste di dati stanno diventando troppo variegate e vengono ricercate su un numero illimitato di colonne. Questo è già un compito che non si adatta a Postgres.
Se avremo bisogno di ricerca full-text nelle funzionalità o se ci saranno molti criteri di ricerca diversi, sappiamo già che dobbiamo trasferirlo in Elastic.
⌘⌘⌘
Grazie per aver letto. Se nella vostra azienda utilizzate anche ElasticSearch e avete casi di implementazione propri, fatecelo sapere. Sarebbe interessante scoprire come lavorano gli altri 🙂
Fonte: habr.com
