Riscrivite a basa di dati di messagi VKontakte da zero è sopravvive

I nostri utilizatori scrivenu missaghji à l'altri senza sapè a fatigue.
Riscrivite a basa di dati di messagi VKontakte da zero è sopravvive
Hè assai assai. Sè vo site à leghje tutti i missaghji di tutti l 'utilizatori, avissi a piglià più di 150 mila anni. A condizione chì site un lettore abbastanza avanzatu è ùn spende micca più di un secondu nantu à ogni missaghju.

Cù un tali voluminu di dati, hè criticu chì a logica per almacenà è accede hè custruita in modu ottimali. Altrimenti, in un mumentu micca cusì maravigliu, pò esse chjaru chì tuttu andarà prestu.

Per noi, stu mumentu hè ghjuntu un annu è mezu fà. Cumu avemu ghjuntu à questu è ciò chì hè accadutu à a fine - vi dicemu in ordine.

Fondo

In a prima implementazione, i missaghji VKontakte anu travagliatu nantu à una cumminazione di PHP backend è MySQL. Questa hè una suluzione cumplettamente normale per un situ web di un picculu studiente. In ogni casu, stu situ hà crisciutu senza cuntrollu è hà cuminciatu à dumandà ottimisazione di strutture di dati per ellu stessu.

À a fine di u 2009, u primu repository di u mutore di testu hè statu scrittu, è in u 2010 i missaghji sò stati trasferiti.

In u mutore di testu, i missaghji sò stati guardati in listi - un tipu di "cassette postali". Ogni tali lista hè determinata da un uid - l'utilizatore chì pussede tutti sti missaghji. Un missaghju hà un inseme di attributi: identificatore di interlocutore, testu, annessi, etc. L'identificatore di u messagiu in a "scatola" hè local_id, ùn cambia mai è hè assignatu in sequenza per i novi messagi. I "scatole" sò indipindenti è ùn sò micca sincronizati cù l'altri in u mutore; a cumunicazione trà elli si trova à u livellu PHP. Pudete vede a struttura di dati è e capacità di u mutore di testu da l'internu ccà.
Riscrivite a basa di dati di messagi VKontakte da zero è sopravvive
Questu era abbastanza per a currispundenza trà dui utilizatori. Indovinate ciò chì hè accadutu dopu?

In May 2011, VKontakte hà introduttu conversazioni cù parechji participanti-multi-chat. Per travaglià cun elli, avemu criatu dui novi clusters - membri-chat è chat-membri. U primu guarda dati nantu à i chats da l'utilizatori, u sicondu almacena dati nantu à l'utilizatori per chats. In più di i listessi stessi, questu include, per esempiu, l'utilizatore invitatu è u tempu chì sò stati aghjuntu à u chat.

"PHP, mandemu un missaghju à u chat", dice l'utilizatore.
"Venite, {username}", dice PHP.
Riscrivite a basa di dati di messagi VKontakte da zero è sopravvive
Ci sò disadvantages à stu schema. A sincronizazione hè sempre a responsabilità di PHP. Grandi chats è utilizatori chì simultaneamente li mandanu missaghji sò una storia periculosa. Siccomu l'istanza di u mutore di testu dipende da l'uid, i participanti di chat puderanu riceve u stessu missaghju in tempi diversi. Si puderia campà cun questu si u prugressu si ferma. Ma ùn succede micca.

À a fine di 2015, avemu lanciatu missaghji cumunitarii, è à u principiu di 2016, avemu lanciatu una API per elli. Cù l'avventu di grandi chatbots in e cumunità, era pussibule di scurdà ancu di distribuzione di carica.

Un bon bot genera parechji milioni di missaghji per ghjornu - ancu l'utilizatori più parlanti ùn ponu micca vantassi di questu. Questu significa chì certi casi di u mutore di testu, nantu à quale tali bots campanu, cuminciaru à soffrenu à u massimu.

I motori di messagiu in 2016 sò 100 casi di membri di chat è chat di membri, è 8000 motori di testu. Sò stati ospitati nantu à un milla servitori, ognunu cù 64 GB di memoria. Cum'è una prima misura d'emergenza, avemu aumentatu a memoria da un altru 32 GB. Avemu stimatu e previsioni. Senza cambiamenti drastici, questu seria abbastanza per un altru annu. Avete bisognu di ottene u hardware o ottimisà e basa di dati stessi.

A causa di a natura di l'architettura, hè solu sensu per aumentà u hardware in multiplici. Questu hè, almenu duppià u numeru di vitture - ovviamente, questu hè un percorsu piuttostu caru. Avemu da ottimisimu.

Novu cuncettu

L'essenza centrale di u novu approcciu hè u chat. Un chat hà una lista di messagi chì sò in relazione cù questu. L'utilizatore hà una lista di chats.

U minimu necessariu hè duie basa di dati novi:

  • chat-motore. Questu hè un repository di vettori di chat. Ogni chat hà un vettore di missaghji chì si cuncernanu. Ogni missaghju hà un testu è un identificatore di missaghju unicu in u chat - chat_local_id.
  • user-engine. Questu hè un almacenamentu di vettori di l'utilizatori - ligami per l'utilizatori. Ogni utilizatore hà un vettore di peer_id (interlocutori - altri utilizatori, multi-chat o cumunità) è un vettore di missaghji. Ogni peer_id hà un vettore di messagi chì sò in relazione cù ellu. Ogni missaghju hà un chat_local_id è un ID di missaghju unicu per quellu utilizatore - user_local_id.

Riscrivite a basa di dati di messagi VKontakte da zero è sopravvive
I novi clusters cumunicanu cù l'altri usendu TCP - questu assicura chì l'ordine di e dumande ùn cambia micca. E dumande stessu è cunferma per elli sò arregistrati nantu à u discu duru - cusì pudemu restaurà u statu di a fila in ogni mumentu dopu un fallimentu o riavvia di u mutore. Siccomu u user-engine è u chat-engine sò 4 mila shards ognunu, a fila di richieste trà i clusters serà distribuitu uniformemente (ma in realtà ùn ci hè nimu - è funziona assai rapidamente).

U travagliu cù u discu in a nostra basa di dati in a maiò parte di i casi hè basatu annantu à una cumminazione di un logu binariu di cambiamenti (binlog), snapshots statici è una maghjina parziale in memoria. I cambiamenti durante u ghjornu sò scritti in un binlog, è un snapshot di u statu attuale hè periodicamente creatu. Un snapshot hè una cullizzioni di strutture di dati ottimizzati per i nostri scopi. Hè custituitu da un header (metaindex of the image) è un inseme di metafiles. L'intestazione hè almacenata in permanenza in a RAM è indica induve circà e dati da a snapshot. Ogni metafile includenu dati chì sò prubabilmente necessarii in punti vicinu à u tempu, per esempiu, in relazione cù un unicu utilizatore. Quandu dumandate a basa di dati utilizendu l'intestazione di l'istantanea, u metafile necessariu hè lettu, è dopu i cambiamenti in u binlog chì sò accaduti dopu chì a snapshot hè stata creata sò cunsiderate. Pudete leghje più nantu à i benefici di stu approcciu ccà.

À u stessu tempu, i dati nantu à u discu duru stessu cambianu solu una volta à ghjornu - a tarda di notte in Mosca, quandu a carica hè minima. Grazie à questu (sapennu chì a struttura nantu à u discu hè custanti in tuttu u ghjornu), pudemu permette di rimpiazzà i vettori cù arrays di una dimensione fissa - è per quessa, guadagnà in memoria.

Mandatu un missaghju in u novu schema s'assumiglia cusì:

  1. U backend PHP cuntattate u user-engine cù una dumanda di mandà un missaghju.
  2. user-engine proxy a dumanda à l'istanza di chat-engine desiderata, chì torna à user-engine chat_local_id - un identificatore unicu di un novu messagiu in stu chat. U chat_engine poi trasmette u messagiu à tutti i destinatari in u chat.
  3. user-engine riceve chat_local_id da chat-engine è torna user_local_id à PHP - un identificatore di missaghju unicu per questu utilizatore. Questu identificatore hè allora utilizatu, per esempiu, per travaglià cù i missaghji via l'API.

Riscrivite a basa di dati di messagi VKontakte da zero è sopravvive
Ma in più di veramente mandà missaghji, avete bisognu di implementà uni pochi di cose più impurtanti:

  • Sublists sò, per esempiu, i missaghji più recenti chì vi vede quandu apre a lista di cunversazione. missaghji unread, missaghji cù tag ("Importante", "Spam", etc.).
  • Cumpressione di missaghji in chat-engine
  • Cache di missaghji in u mutore di l'utilizatori
  • Ricerca (attraversu tutti i dialoghi è in una specifica).
  • Actualizazione in tempu reale (Longpolling).
  • Salvà a storia per implementà a cache in i clienti mobili.

Tutte e subliste sò strutture chì cambianu rapidamente. Per travaglià cun elli avemu aduprà Splay arburi. Questa scelta hè spiegata da u fattu chì à a cima di l'arbulu avemu qualchì volta guardatu un segmentu sanu di missaghji da una snapshot - per esempiu, dopu a reindexing di notte, l'arbulu hè custituitu da una cima, chì cuntene tutti i missaghji di a sublista. L'arbulu di Splay facilita l'inserimentu in u mità di un tali vertice senza avè da pensà à equilibriu. Inoltre, Splay ùn guarda micca dati innecessarii, chì ci risparmia memoria.

I missaghji implicanu una grande quantità di informazioni, soprattuttu testu, chì hè utile per pudè cumpressà. Hè impurtante chì pudemu unarchive accurately ancu un missaghju individuale. Adupratu per cumpressà i missaghji L'algoritmu di Huffman cù a nostra propria heuristiche - per esempiu, sapemu chì in i missaghji i parolle alternanu cù "non-parole" - spazii, segni di puntuazione - è ricurdate ancu alcune di e caratteristiche di l'usu di simboli per a lingua russa.

Siccomu ci sò assai menu utilizatori cà chats, per salvà e dumande di discu d'accessu aleatoriu in chat-engine, cachemu i missaghji in u user-engine.

A ricerca di messagi hè implementata cum'è una dumanda diagonale da u mutore di l'utilizatori à tutti l'istanze di u mutore di chat chì cuntenenu chats di questu utilizatore. I risultati sò cumminati in u user-engine stessu.

Ebbè, tutti i ditagli sò stati cunsiderati, tuttu ciò chì resta hè di cambià à un novu schema - è preferibile senza chì l'utilizatori l'anu nutatu.

Migrazione di dati

Dunque, avemu un mutore di testu chì guarda i missaghji per l'utilizatori, è dui gruppi di chat-membri è membri-chat chì guardanu dati nantu à e sale multi-chat è l'utilizatori in elli. Cumu passà da questu à u novu user-engine è chat-engine?

membri-chat in u vechju schema hè stata utilizata principalmente per ottimisazione. Avemu trasfirutu rapidamente i dati necessarii da ellu à i chat-membri, è dopu ùn hà più participatu à u prucessu di migrazione.

Fila per i membri di chat. Include 100 casi, mentri chat-engine hà 4 mila. Per trasfiriri i dati, avete bisognu di mette in cunfurmità - per questu, i membri di u chat sò stati divisi in i stessi 4 mila copie, è dopu a lettura di u binlog di i membri di chat hè stata attivata in u chat-engine.
Riscrivite a basa di dati di messagi VKontakte da zero è sopravvive
Avà chat-engine sapi di multi-chat da i chat-membri, ma ùn sapi ancu nunda di dialoghi cù dui interlocutori. Tali dialoghi sò situati in u mutore di testu cù riferimentu à l'utilizatori. Quì avemu pigliatu i dati "head-on": ogni istanza di chat-engine hà dumandatu à tutti l'istanze di u mutore di testu s'ellu avianu u dialogu necessariu.

Grande - chat-engine sapi ciò chì ci sò chats multi-chat è sapi chì dialoghi ci sò.
Avete bisognu di cumminà missaghji in chats multi-chat in modu chì finiscinu cù una lista di missaghji in ogni chat. Prima, chat-engine recupera da testu-mutore tutti i missaghji di l'utilizatori da stu chat. In certi casi, ci sò assai di elli (finu à centinaie di milioni), ma cù eccezzioni rarissimi, u chat si mette interamente in RAM. Avemu messagi senza ordine, ognunu in parechje copie - dopu tuttu, sò tutti tirati da diverse istanze di mutore di testu chì currispondenu à l'utilizatori. U scopu hè di sorte i missaghji è caccià e copie chì occupanu spaziu inutile.

Ogni missaghju hà un timestamp chì cuntene l'ora di mandatu è u testu. Utilizemu u tempu per a classificazione - pusemu puntatori à i missaghji più antichi di i participanti multichat è paragunemu l'hash da u testu di e copie previste, andendu versu un timestamp crescente. Hè logicu chì e copie anu u stessu hash è timestamp, ma in pratica ùn hè micca sempre u casu. Comu vi ricordate, a sincronizazione in u vechju schema hè stata realizata da PHP - è in casi rari, u tempu di mandà u stessu missaghju hè diversu trà l'utilizatori. In questi casi, avemu permessu di edità u timestamp - di solitu in un secondu. U sicondu prublema hè l'ordine sfarente di i missaghji per i diversi destinatari. In tali casi, avemu permessu di creà una copia extra, cù diverse opzioni di ordine per diversi utilizatori.

Dopu questu, i dati nantu à i missaghji in multichat sò mandati à l'utilizatori di u mutore. È quì vene una funzione dispiacevule di i missaghji impurtati. In u funziunamentu normale, i missaghji chì venenu à u mutore sò urdinati strettamente in ordine crescente da user_local_id. I missaghji impurtati da u vechju mutore in u mutore di l'utilizatori anu persu sta pruprietà utile. À u listessu tempu, per a cunvenzione di a prova, avete bisognu di pudè accede rapidamente, cercate qualcosa in elli è aghjunghje novi.

Utilizemu una struttura di dati speciale per almacenà missaghji impurtati.

Rapprisenta un vettore di taglia Riscrivite a basa di dati di messagi VKontakte da zero è sopravviveinduve sò tutti Riscrivite a basa di dati di messagi VKontakte da zero è sopravvive - sò diffirenti è urdinati in ordine discendente, cù un ordine speciale di elementi. In ogni segmentu cù indici Riscrivite a basa di dati di messagi VKontakte da zero è sopravvive elementi sò ordinati. A ricerca di un elementu in una tale struttura piglia u tempu Riscrivite a basa di dati di messagi VKontakte da zero è sopravvive attraversu Riscrivite a basa di dati di messagi VKontakte da zero è sopravvive ricerche binari. L'aghjunzione di un elementu hè ammortizzata Riscrivite a basa di dati di messagi VKontakte da zero è sopravvive.

Cusì, avemu capitu cumu trasfiriri dati da vechji mutori à novi. Ma stu prucessu dura parechji ghjorni - è hè improbabile chì durante questi ghjorni i nostri utilizatori rinunceranu à l'abitudine di scrive à l'altri. Per ùn perde micca i missaghji in questu tempu, cambiamu à un schema di travagliu chì usa i vechji è novi clusters.

I dati sò scritti à i chat-members è user-engine (è micca à text-engine, cum'è in u funziunamentu normale secondu u vechju schema). user-engine proxy a dumanda à chat-engine - è quì u cumpurtamentu dipende s'ellu sta chat hè digià stata unita o micca. Se u chat ùn hè ancu statu unitu, u chat-engine ùn scrive micca u missaghju per ellu stessu, è u so processu si trova solu in u testu-mutore. Se u chat hè digià unitu in chat-engine, torna chat_local_id à user-engine è manda u messagiu à tutti i destinatari. user-engine proxys all data to text-engine - cusì chì se qualcosa succede, pudemu sempre retrocede, avè tutte e dati attuali in u vechju mutore. text-engine torna user_local_id, chì user-engine guarda è torna à u backend.
Riscrivite a basa di dati di messagi VKontakte da zero è sopravvive
In u risultatu, u prucessu di transizione s'assumiglia cusì: cunnettamu clusters vacanti di user-engine è chat-engine. chat-engine leghje u binlog tutale di i membri di chat, poi proxy principia secondu u schema descrittu sopra. Trasferemu i vechji dati è uttene dui clusters sincronizati (vechju è novu). Tuttu ciò chì resta hè di cambià a lettura da u mutore di testu à u mutore d'utilizatore è disattivà a proxy.

Risultati

Grazie à u novu approcciu, tutte e metriche di rendiment di i mutori sò state migliurate è i prublemi cù a cunsistenza di e dati sò stati risolti. Avà pudemu implementà rapidamente e funzioni novi in ​​i missaghji (è avemu digià cuminciatu à fà questu - avemu aumentatu u numeru massimu di participanti di chat, implementatu una ricerca di messagi inoltrati, lanciatu missaghji pinned è elevatu u limitu nantu à u numeru tutale di missaghji per utilizatore) .

I cambiamenti in a logica sò veramente enormi. È vogliu nutà chì questu ùn significa micca sempre anni sanu di sviluppu da una squadra enormosa è una miriade di linee di codice. chat-engine è user-engine inseme cù tutte e storie supplementari cum'è Huffman per a compressione di missaghju, l'arburi Splay è a struttura per i missaghji impurtati hè menu di 20 mila linee di codice. È sò stati scritti da 3 sviluppatori in solu 10 mesi (in ogni casu, vale a pena tene in mente chì tutte e trè sviluppatore - campioni di u mondu in a prugrammazione sportiva).

Inoltre, invece di radduppià u numeru di servitori, avemu riduciutu u so numeru à a mità - avà u user-engine è u chat-engine vive nantu à 500 macchine fisiche, mentre chì u novu schema hà un grande spaziu per a carica. Avemu salvatu assai soldi nantu à l'equipaggiu - circa $ 5 milioni + $ 750 mila annu in spese operative.

Ci sforzemu di truvà e migliori suluzioni per i prublemi più cumplessi è di grande scala. Avemu assai di elli - è hè per quessa chì cercamu sviluppatori talentu in u dipartimentu di basa di dati. Sè amate è sapete cumu risolve tali prublemi, avete una cunniscenza eccellente di l'algoritmi è e strutture di dati, vi invitemu à unisce à a squadra. Cuntattate i nostri HRper i dettagli.

Ancu s'è sta storia ùn hè micca di voi, per piacè nutate chì valoremu i cunsiglii. Dì à un amicu vacanti di sviluppatore, è s'ellu compie successu u periodu di prova, riceverete un bonus di 100 mila rubles.

Source: www.habr.com

Add a comment