
Tutti noi amiamo le storie. Ci piace raccontare le nostre vittorie passate, le battaglie o semplicemente le nostre esperienze sul lavoro mentre siamo seduti attorno a un fuoco.
Oggi è proprio un giorno così. E anche se ora non sei attorno a un fuoco, abbiamo comunque una storia per te. La storia di come abbiamo iniziato a lavorare con il database Tarantool.
Una volta, molto tempo fa, nella nostra azienda c'erano un paio di "monoliti" e un unico "soffitto" per tutti, a cui questi monoliti si avvicinavano lentamente ma inesorabilmente, limitando il volo della nostra azienda, la nostra crescita. E c'era una comprensione chiara: un giorno ci saremmo scontrati contro questo soffitto.
Ora ci governa l'ideologia della separazione di tutto, dall'hardware alla logica aziendale. Di conseguenza, abbiamo, ad esempio, due data center praticamente indipendenti a livello di rete. Ma allora tutto era molto diverso.
Oggi ci sono molti strumenti e mezzi per apportare modifiche, come CI/CD, K8S, ecc. All'epoca dei "monoliti" non avevamo bisogno di così tanti termini stranieri. Era sufficiente semplicemente modificare il "database di archivio".
Ma il tempo è andato avanti, e il numero di richieste è aumentato insieme ad esso, facendo schizzare le RPS a volte oltre le nostre capacità. Con l'ingresso nel mercato degli stati post-sovietici, il carico sul processore del database del primo monolito non scendeva sotto il 90%, mentre le RPS si mantenevano attorno a 2400. E non erano semplici piccole selezioni, ma richieste massive con un sacco di controlli e JOIN, che potevano attraversare quasi metà dei dati a causa di un alto IO.
Quando sono iniziate ad apparire le vere vendite per il "Black Friday" - e Wildberries è stata una delle prime in Russia a promuoverle - la situazione è diventata decisamente preoccupante. Infatti, il carico in questi giorni aumentava di tre volte.
Oh, quei "tempi monolitici"! Sono certo che anche tu hai vissuto esperienze simili e ancora non riesci a capire come possa essere successo.
Cosa puoi fare? La moda è propria anche delle tecnologie. Circa 5 anni fa, abbiamo dovuto ripensare una di queste mode in forma di un sito esistente su .NET e MS SQL Server, che custodiva gelosamente tutta la logica del sito stesso. La custodiva così bene che scindere un tale monolito è risultata un'attività lunga e per niente semplice.
Una piccola digressione.
A eventi di vario genere, dico: «se non hai tagliato il monolite, non sei cresciuto!» Mi interessa la tua opinione al riguardo, per favore scrivila nei commenti.
E scoppiò il tuono
Torniamo al nostro «focolare». Per distribuire il carico della funzionalità «monolitica», abbiamo deciso di suddividere il sistema in microservizi basati su tecnologie open source. Perché, perlomeno, la loro scalabilità è più economica. E avevamo il 100% di certezza che avrebbe funzionato, visto che nel frattempo eravamo riusciti ad entrare nei mercati dei paesi vicini e il numero di registrazioni, così come il numero degli ordini, iniziava a crescere ancora di più.
Analizzando i primi candidati per la migrazione dal monolito ai microservizi, abbiamo capito che nell'80% dei casi il salvataggio avviene per il 99% dalle sistemi back office, mentre la lettura proviene dal front-end. Questo riguardava soprattutto un paio di sottosistemi importanti per noi: i dati degli utenti e il sistema di calcolo del prezzo finale dei prodotti sulla base delle informazioni relative agli sconti e ai coupon dei clienti.
Come parentesi, fa strano pensare che, oltre ai sottosistemi sopra menzionati, dal nostro monolito sono stati estratti anche i cataloghi dei prodotti, il carrello degli utenti, il sistema di ricerca dei prodotti, il sistema di filtraggio dei cataloghi e vari sistemi di raccomandazione. Per ciascuno di essi esistono classi separate di sistemi specializzati, ma un tempo vivevano tutti insieme in un “piccolo rifugio”.
Abbiamo pianificato di estrarre i dati sui nostri clienti in un sistema shardato. Tuttavia, l'estrazione della funzionalità per il calcolo del prezzo finale dei prodotti richiedeva una buona scalabilità in lettura, poiché questa creava il maggiore carico in termini di RPS ed era la più complessa da eseguire per il database (molti dati sono coinvolti nel processo di calcolo).
Di conseguenza, abbiamo sviluppato uno schema che si adattava bene con Tarantool.
In quel periodo, per il funzionamento dei microservizi, sono stati scelti schemi di lavoro con più data center su macchine virtuali e fisiche. Come mostrato nelle immagini, sono state utilizzate opzioni di replicazione di Tarantool sia in modalità master-master che master-slave.

Architettura. Opzione 1. Servizio utenti
Attualmente, si tratta di 24 shard, ognuno con 2 istanze (una per ogni data center), tutte in modalità master-master.
Sopra il database si trovano applicazioni che accedono alle repliche del database. Le applicazioni lavorano con Tarantool attraverso la nostra libreria personalizzata, che implementa l'interfaccia del driver Go per Tarantool. Essa vede tutte le repliche e può lavorare con il master per lettura e scrittura. In sostanza, implementa un modello di replica set, a cui è stata aggiunta la logica di selezione delle repliche, esecuzione di retry, circuit breaker e limitazione della velocità.
Inoltre, è possibile configurare la politica di selezione delle repliche in riferimento agli shard. Ad esempio, utilizzando il round-robin.

Architettura. Opzione 2. Servizio di calcolo del costo finale del prodotto
Qualche mese fa, la maggior parte delle richieste per il calcolo del costo finale dei prodotti è stata trasferita a un nuovo servizio, che fondamentalmente funziona senza database, ma fino a un certo punto, il 100% era gestito dal servizio con Tarantool sotto il cofano.
Il database del servizio è composto da 4 master, nei quali il sincronizzatore raccoglie i dati, e ciascuno di questi master distribuisce i dati alle repliche in sola lettura tramite replica. Ogni master ha circa 15 di queste repliche.
In entrambe le architetture, in caso di indisponibilità di un datacenter, l'applicazione può ricevere dati dal secondo.
Vale la pena notare che in Tarantool la replica è piuttosto flessibile e può essere configurata a runtime. In altri sistemi ci sono stati problemi, ad esempio per modificare i parametri max_wal_senders e max_replication_slots in PostgreSQL è necessario riavviare il master, il che in alcuni casi può portare a disconnessioni tra l'applicazione e il DBMS.
Cerca e troverai!
Perché non abbiamo fatto "come le persone normali" e abbiamo scelto un modo atipico? Dipende da cosa consideriamo normale. Molti creano un cluster con Mongo e lo distribuiscono su tre datacenter geo-distribuiti.
All'epoca avevamo già due progetti su Redis. Il primo era una cache, e il secondo costituiva uno storage persistente per dati non troppo critici. Con quest'ultimo ci sono stati parecchi problemi, in parte per nostra responsabilità. A volte grossi volumi di dati si accumulavano nella chiave, e di tanto in tanto il sito diventava lento. Questo sistema è stato utilizzato in modalità master-slave. Ci sono stati molti casi in cui qualcosa è accaduto con il master e la replica si è interrotta.
Cioè, Redis è ottimo per compiti stateless, ma non per quelli stateful. Fondamentalmente, ha permesso di risolvere la maggior parte dei problemi, ma solo se si trattava di soluzioni key-value con un paio di indici. Tuttavia, all'epoca, la persistenza e la replica in Redis erano piuttosto deludenti. Inoltre, ci furono lamentele sulle prestazioni.
Abbiamo considerato MySQL e PostgreSQL. Ma il primo in qualche modo non ha trovato spazio da noi, mentre il secondo è un prodotto piuttosto complesso in sé, e costruire su di esso semplici servizi sarebbe stato poco pratico.
Abbiamo provato RIAK, Cassandra, persino database grafici. Tutte queste sono soluzioni piuttosto di nicchia, che non si prestavano a essere strumenti universali per la creazione di servizi.
Alla fine abbiamo optato per Tarantool.
Ci siamo rivolti a lui quando era in versione 1.6. Ci ha colpito la sinergia tra key-value e le funzionalità di un database relazionale. Ci sono indici secondari, transazioni e spazi, che sono come tabelle, ma non semplici, permettendo di memorizzare un numero variabile di colonne. Tuttavia, la killer feature di Tarantool è stata la combinazione di indici secondari con key-value e transazionalità.
Ha anche giocato un ruolo importante la reattività della comunità di lingua russa, pronta ad aiutare in chat. Abbiamo sfruttato attivamente questa opportunità e vivevamo in chat. E non va dimenticato il ragionevole livello di persistenza senza evidenti imperfezioni e difetti. Guardando la nostra storia con Tarantool, abbiamo avuto molti problemi e fallimenti con la replica, ma non abbiamo mai perso dati per colpa sua!
L'implementazione è iniziata in modo difficile
A quel tempo il nostro stack di sviluppo principale era .NET, per il quale non c'era un connettore per Tarantool. Abbiamo subito iniziato a fare qualcosa in Go. Anche con Lua le cose andavano piuttosto bene. Il principale problema a quel punto era il debug: in .NET era tutto magnifico, ma immersione nel mondo di Lua embedded, quando non avevi altra opzione se non i log, era piuttosto arduo. Inoltre, per qualche motivo, la replica si interrompeva periodicamente, e abbiamo dovuto approfondire il funzionamento del motore Tarantool. In questo ci ha aiutati la chat, meno la documentazione, a volte guardavamo anche il codice. A quel tempo la documentazione era piuttosto scarsa.
Così, nell'arco di alcuni mesi, siamo riusciti a fare esperienza e ottenere risultati soddisfacenti nel lavoro con Tarantool. Abbiamo formalizzato in git dei modelli di sviluppo, che hanno aiutato nella creazione di nuovi microservizi. Ad esempio, quando si presentava l'esigenza di realizzare un nuovo microservizio, il developer consultava il codice sorgente della soluzione di riferimento nel repository, e la creazione del nuovo richiedeva non più di una settimana.
Allora erano tempi speciali. In un certo senso, potevi avvicinarti all'amministratore al tavolo vicino e chiedere: "Dammela virtual machine". Dopo circa trenta minuti, la macchina era già a tua disposizione. Ti connettevi da solo, installavi tutto e ti attivavano il traffico su di essa.
Oggi non funziona più così: è necessario implementare il monitoraggio del servizio, la registrazione, coprire la funzionalità con test, ordinare una virtual machine o la fornitura in Kubernetes, e così via. In generale, sarà meglio così, anche se più lungo e complicato.
Dividi e conquista. Come stanno le cose con Lua?
C'era un serio dilemma: alcune squadre faticavano a implementare modifiche nel servizio con molta logica su Lua. Spesso questo si accompagnava a malfunzionamenti del servizio.
Cioè, gli sviluppatori preparano una certa modifica. Tarantool inizia a eseguire la migrazione, mentre la replica opera ancora con il codice vecchio; arriva per replicazione qualche DDL, qualcos'altro, e il codice semplicemente si rompe, perché ciò non è stato tenuto in considerazione. Di conseguenza, la procedura di aggiornamento per gli amministratori era scritta su un foglio A4: fermare la replicazione, aggiornare questo, riavviare la replicazione, spegnere qui, aggiornare là. Un incubo!
Alla fine, ora cerchiamo di non fare nulla su Lua. Semplicemente tramite iproto (protocollo binario per l'interazione con il server), e basta. Forse, è una mancanza di conoscenze da parte degli sviluppatori, ma da questo punto di vista il sistema è complicato.
Non seguiamo sempre ciecamente questo schema. Oggi non abbiamo nero e bianco: tutto su Lua, oppure tutto su Go. Stiamo già comprendendo come combinare le cose, per evitare problemi con la migrazione.
Dove si trova adesso Tarantool?
Tarantool viene utilizzato nel servizio di calcolo del costo finale dei prodotti tenendo conto dei coupon sconto, noto anche come «Promotaizer». Come già detto in precedenza, attualmente sta venendo messo da parte: un nuovo servizio di catalogo con prezzi pre-calcolati lo sta sostituendo, ma sei mesi fa tutti i calcoli venivano effettuati tramite il «Promotaizer». In passato metà della logica era scritta in Lua. Due anni fa il servizio è stato trasformato in un magazzino, e la logica è stata riscritta in Go, perché la meccanica degli sconti è cambiata un po' e il servizio aveva bisogno di una maggiore potenza.
Uno dei servizi più critici è il profilo utente. Quindi, tutti gli utenti di Wildberries sono conservati in Tarantool, e sono circa 50 milioni. Un sistema shardizzato per ID utente, distribuito su diversi centri di distribuzione con integrazioni su servizi Go.
Nel RPS, in passato, il leader era il «Promotaizer», che raggiungeva fino a 6.000 richieste. A un certo punto avevamo 50-60 istanze. Ora, il leader nel RPS sono i profili utenti, con circa 12.000 richieste. In questo servizio viene applicato uno sharding personalizzato suddiviso per intervalli di ID utente. Servizio gestisce più di 20 macchine, ma sono troppe; prevediamo di ridurre le risorse allocate, poiché sarebbe sufficiente la potenza di 4-5 macchine.
Il servizio di sessione è il nostro primo servizio su vshard e Cartridge. La configurazione di vshard e l'aggiornamento di Cartridge hanno richiesto un certo impegno da parte nostra, ma alla fine tutto ha funzionato.
Il servizio per la visualizzazione di diversi banner sul sito e nell'app mobile è stato uno dei primi ad essere lanciato direttamente su Tarantool. Questo servizio è notevole perché ha circa 6-7 anni, è ancora in funzione e non è mai stato riavviato. È stata utilizzata la replicazione master-master. Non si è mai rotto nulla.
C'è un esempio di utilizzo di Tarantool per la funzionalità di cataloghi rapidi nel sistema di magazzino, per verificare rapidamente le informazioni in alcuni casi. Abbiamo provato a utilizzare Redis per questo, ma i dati in memoria occupavano più spazio rispetto a Tarantool.
I servizi di lista di attesa, abbonamenti clienti, storie trendy e prodotti salvati funzionano anch'essi con Tarantool. L'ultimo servizio occupa in memoria circa 120 GB. È il servizio più voluminoso tra quelli menzionati.
Conclusione
Grazie agli indici secondari combinati con key-value e alla transazionalità, Tarantool è perfetto per architetture basate su microservizi. Tuttavia, abbiamo incontrato difficoltà quando implementavamo cambiamenti nei servizi con una logica complessa in Lua: i servizi smettevano spesso di funzionare. Non siamo riusciti a risolvere questo problema e col tempo siamo giunti a diverse combinazioni di Lua e Go: sappiamo dove è meglio utilizzare un linguaggio e dove l'altro.
Cosa leggere ancora sull'argomento
- Stiamo creando da zero un'applicazione ad alta richiesta su Tarantool
- Scelta affidabile del leader in Tarantool Cartridge
- Canale Telegram di Tarantool con notizie sul prodotto
- Discussa Tarantool nella chat della community
Fonte: habr.com
