Indexuri bitmap în Go: căutare cu viteză sălbatică

Indexuri bitmap în Go: căutare cu viteză sălbatică

discurs de deschidere

Am dat acest raport în engleză la conferința GopherCon Russia 2019 de la Moscova și în rusă la o întâlnire la Nijni Novgorod. Vorbim despre un index bitmap - mai puțin comun decât B-tree, dar nu mai puțin interesant. Partajarea record discursuri la conferință în limba engleză și transcrieri text în rusă.

Ne vom uita la modul în care funcționează un index bitmap, când este mai bun, când este mai rău decât alți indici și în ce cazuri este semnificativ mai rapid decât ei; Să vedem ce SGBD populare au deja indecși bitmap; Să încercăm să-l scriem pe al nostru în Go. Și „pentru desert” vom folosi biblioteci gata făcute pentru a crea propria noastră bază de date specializată super-rapidă.

Sper cu adevărat că lucrările mele vă vor fi utile și interesante. Merge!

Introducere


http://bit.ly/bitmapindexes
https://github.com/mkevac/gopherconrussia2019

Salutare tuturor! E șase seara și suntem cu toții super obosiți. Moment bun să vorbim despre teoria plictisitoare a indexului bazei de date, nu? Nu vă faceți griji, voi avea câteva rânduri de cod sursă ici și colo. 🙂

Lăsând toate glumele deoparte, raportul este plin de informații și nu avem mult timp. Asadar, haideti sa începem.
Indexuri bitmap în Go: căutare cu viteză sălbatică
Astăzi voi vorbi despre următoarele:

  • ce sunt indicii;
  • ce este un index bitmap;
  • unde este folosit și unde NU este folosit și de ce;
  • implementare simplă în Go și puțină luptă cu compilatorul;
  • implementare ceva mai puțin simplă, dar mult mai productivă în Go assembler;
  • „probleme” ale indicilor bitmap;
  • implementari existente.

Deci, ce sunt indicii?

Indexuri bitmap în Go: căutare cu viteză sălbatică

Indexul este o structură de date separată pe care o menținem și o actualizăm în plus față de datele principale. Este folosit pentru a accelera căutarea. Fără indici, căutarea ar necesita parcurgerea completă a datelor (un proces numit scanare completă), iar acest proces are o complexitate algoritmică liniară. Dar bazele de date conțin de obicei cantități uriașe de date, iar complexitatea liniară este prea lentă. În mod ideal, am obține unul logaritmic sau constant.

Acesta este un subiect extrem de complex, plin de subtilități și compromisuri, dar după ce am analizat zeci de ani de dezvoltare și cercetare a bazelor de date, sunt dispus să spun că există doar câteva abordări utilizate pe scară largă pentru a crea indici de baze de date.

Indexuri bitmap în Go: căutare cu viteză sălbatică

Prima abordare este reducerea ierarhică a spațiului de căutare, împărțind spațiul de căutare în părți mai mici.

De obicei facem acest lucru folosind diferite tipuri de copaci. Un exemplu ar fi o cutie mare de materiale din dulapul tău care conține cutii mai mici de materiale împărțite în diferite subiecte. Dacă aveți nevoie de materiale, probabil că le veți căuta într-o cutie pe care scrie „Materiale” mai degrabă decât într-una pe care scrie „Cookies”, nu?

Indexuri bitmap în Go: căutare cu viteză sălbatică

A doua abordare este de a selecta imediat elementul sau grupul de elemente dorit. Facem acest lucru în hărți hash sau în indexuri inverse. Folosirea hărților hash este foarte asemănătoare cu exemplul anterior, dar în loc de o cutie de cutii, aveți o grămadă de cutii mici cu articole finale în dulap.

Indexuri bitmap în Go: căutare cu viteză sălbatică

A treia abordare este eliminarea nevoii de căutare. Facem acest lucru folosind filtre Bloom sau filtre cuc. Primii dau un răspuns instantaneu, scutindu-te de a fi nevoit să cauți.

Indexuri bitmap în Go: căutare cu viteză sălbatică

Ultima abordare este să folosim pe deplin toată puterea pe care ne-o oferă hardware-ul modern. Este exact ceea ce facem în indexurile bitmap. Da, atunci când le folosim, uneori trebuie să parcurgem întregul index, dar o facem super eficient.

După cum am spus, subiectul indexurilor bazelor de date este vast și plin de compromisuri. Aceasta înseamnă că uneori putem folosi mai multe abordări în același timp: dacă trebuie să grăbim și mai mult căutarea, sau dacă trebuie să acoperim toate tipurile de căutare posibile.

Astăzi voi vorbi despre cea mai puțin cunoscută abordare a acestora - indicii bitmap.

Cine sunt eu să vorbesc pe această temă?

Indexuri bitmap în Go: căutare cu viteză sălbatică

Lucrez ca șef de echipă la Badoo (poate că ești mai familiarizat cu celălalt produs al nostru, Bumble). Avem deja peste 400 de milioane de utilizatori din întreaga lume și multe funcții care selectează cea mai bună potrivire pentru ei. Facem acest lucru folosind servicii personalizate, inclusiv indici bitmap.

Deci, ce este un index bitmap?

Indexuri bitmap în Go: căutare cu viteză sălbatică
Indexurile bitmap, după cum sugerează și numele, folosesc bitmap sau seturi de biți pentru a implementa un index de căutare. Din perspectiva unei păsări, acest index constă dintr-una sau mai multe astfel de hărți biți reprezentând orice entități (cum ar fi oamenii) și proprietățile sau parametrii acestora (vârsta, culoarea ochilor etc.) și un algoritm care utilizează operații pe biți (ȘI, SAU, NU). ) pentru a răspunde la interogarea de căutare.
Indexuri bitmap în Go: căutare cu viteză sălbatică
Ni s-a spus că indexurile bitmap sunt cele mai potrivite și foarte performante pentru cazurile în care există căutări care combină interogări pe mai multe coloane cu cardinalitate scăzută (gândiți-vă la „culoarea ochilor” sau „starea civilă” față de ceva de genul „distanța față de centrul orașului”). Dar voi arăta mai târziu că funcționează foarte bine și pentru coloanele cu cardinalitate mare.

Să ne uităm la cel mai simplu exemplu de index bitmap.
Indexuri bitmap în Go: căutare cu viteză sălbatică
Imaginați-vă că avem o listă de restaurante din Moscova cu proprietăți binare precum acestea:

  • aproape de metrou;
  • există parcare privată;
  • există o verandă (are terasă);
  • puteți rezerva o masă (acceptă rezervări);
  • potrivit pentru vegetarieni (vegan friendly);
  • scump (scump).

Indexuri bitmap în Go: căutare cu viteză sălbatică
Să dăm fiecărui restaurant un număr de secvență începând de la 0 și să alocăm memorie pentru 6 bitmap-uri (câte unul pentru fiecare caracteristică). Vom popula apoi aceste bitmap-uri în funcție de dacă restaurantul are această proprietate sau nu. Dacă restaurantul 4 are o verandă, atunci bitmap-ul nr. 4 din bitmap „are verandă” va fi setat la 1 (dacă nu există verandă, atunci la 0).
Indexuri bitmap în Go: căutare cu viteză sălbatică
Acum avem cel mai simplu index bitmap posibil și îl putem folosi pentru a răspunde la întrebări precum:

  • „Arată-mi restaurante prietenoase cu vegetarieni”;
  • „Arată-mi restaurante ieftine cu verandă unde poți rezerva o masă.”

Indexuri bitmap în Go: căutare cu viteză sălbatică
Indexuri bitmap în Go: căutare cu viteză sălbatică
Cum? Să aruncăm o privire. Prima cerere este foarte simplă. Tot ce trebuie să facem este să luăm bitmap-ul „vegetarian friendly” și să îl transformăm într-o listă de restaurante ale căror părți sunt expuse.
Indexuri bitmap în Go: căutare cu viteză sălbatică
Indexuri bitmap în Go: căutare cu viteză sălbatică
A doua cerere este puțin mai complicată. Trebuie să folosim bitmap-ul NOT pe bitmap-ul „scump” pentru a obține o listă de restaurante ieftine, apoi AND cu bitmap-ul „pot rezerva o masă” și ȘI rezultatul cu bitmap-ul „există o verandă”. Bitmap-ul rezultat va conține o listă de unități care îndeplinesc toate criteriile noastre. În acest exemplu, acesta este doar restaurantul Yunost.
Indexuri bitmap în Go: căutare cu viteză sălbatică
Indexuri bitmap în Go: căutare cu viteză sălbatică
Este multă teorie implicată, dar nu vă faceți griji, vom vedea codul foarte curând.

Unde sunt folosiți indexurile bitmap?

Indexuri bitmap în Go: căutare cu viteză sălbatică
Dacă indexați pe Google bitmap, 90% dintre răspunsuri vor fi legate de Oracle DB într-un fel sau altul. Dar probabil că și alte SGBD-uri suportă un lucru atât de grozav, nu? Nu chiar.

Să trecem prin lista principalilor suspecți.
Indexuri bitmap în Go: căutare cu viteză sălbatică
MySQL nu acceptă încă indexuri bitmap, dar există o propunere care sugerează adăugarea acestei opțiuni (https://dev.mysql.com/worklog/task/?id=1524).

PostgreSQL nu acceptă indexuri bitmap, dar folosește bitmap simple și operații pe biți pentru a combina rezultatele căutării în mai mulți alți indici.

Tarantool are indecși de set de biți și acceptă căutări simple pe aceștia.

Redis are câmpuri de biți simple (https://redis.io/commands/bitfield) fără capacitatea de a le căuta.

MongoDB nu acceptă încă indexuri bitmap, dar există și o propunere care sugerează adăugarea acestei opțiuni https://jira.mongodb.org/browse/SERVER-1723

Elasticsearch folosește hărți de bit în interior (https://www.elastic.co/blog/frame-of-reference-and-roaring-bitmaps).

Indexuri bitmap în Go: căutare cu viteză sălbatică

  • Dar în casa noastră a apărut un nou vecin: Pilosa. Aceasta este o nouă bază de date non-relațională scrisă în Go. Conține numai indici bitmap și bazează totul pe ei. Vom vorbi despre asta puțin mai târziu.

Implementarea în Go

Dar de ce sunt folosiți atât de rar indicii bitmap? Înainte de a răspunde la această întrebare, aș dori să vă arăt cum să implementați un index bitmap foarte simplu în Go.
Indexuri bitmap în Go: căutare cu viteză sălbatică
Bitmaps-urile sunt în esență doar bucăți de date. În Go, să folosim felii de octeți pentru asta.

Avem un bitmap pentru o caracteristică de restaurant și fiecare bit din bitmap indică dacă un anumit restaurant are această proprietate sau nu.
Indexuri bitmap în Go: căutare cu viteză sălbatică
Vom avea nevoie de două funcții de ajutor. Unul va fi folosit pentru a umple hărțile noastre de bit cu date aleatorii. Aleatoriu, dar cu o anumită probabilitate ca restaurantul să aibă fiecare proprietate. De exemplu, cred că sunt foarte puține restaurante în Moscova unde nu poți rezerva o masă și mi se pare că aproximativ 20% dintre unități sunt potrivite pentru vegetarieni.

A doua funcție va converti bitmap-ul într-o listă de restaurante.
Indexuri bitmap în Go: căutare cu viteză sălbatică
Indexuri bitmap în Go: căutare cu viteză sălbatică
Pentru a răspunde la întrebarea „Arătați-mi restaurante ieftine care au o terasă și pot face rezervări”, avem nevoie de operațiuni pe doi biți: NOT și AND.

Ne putem simplifica puțin codul utilizând operatorul mai complex AND NOT.

Avem funcții pentru fiecare dintre aceste operațiuni. Ambele trec prin felii, iau elementele corespunzătoare din fiecare, le combină cu o operațiune de biți și pun rezultatul în felia rezultată.
Indexuri bitmap în Go: căutare cu viteză sălbatică
Și acum putem folosi bitmap-urile și funcțiile noastre pentru a răspunde la interogarea de căutare.
Indexuri bitmap în Go: căutare cu viteză sălbatică
Performanța nu este atât de ridicată, deși funcțiile sunt foarte simple și am economisit o mulțime de bani prin faptul că nu returnăm o nouă felie rezultată de fiecare dată când funcția a fost apelată.

După ce am făcut un pic de profilare cu pprof, am observat că compilatorului Go lipsea o optimizare foarte simplă, dar foarte importantă: function inlining.
Indexuri bitmap în Go: căutare cu viteză sălbatică
Faptul este că compilatorul Go se teme teribil de buclele care trec prin felii și refuză categoric să inline funcțiile care conțin astfel de bucle.
Indexuri bitmap în Go: căutare cu viteză sălbatică
Dar nu mi-e frică și pot păcăli compilatorul folosind goto în loc de buclă, ca în vremurile bune.

Indexuri bitmap în Go: căutare cu viteză sălbatică
Indexuri bitmap în Go: căutare cu viteză sălbatică

Și, după cum puteți vedea, acum compilatorul va alinia cu plăcere funcția noastră! Drept urmare, reușim să economisim aproximativ 2 microsecunde. Nu-i rău!

Indexuri bitmap în Go: căutare cu viteză sălbatică

Al doilea blocaj este ușor de observat dacă te uiți cu atenție la ieșirea ansamblului. Compilatorul a adăugat o verificare a limitei secțiunii chiar în cadrul celei mai bune bucle ale noastre. Faptul este că Go este un limbaj sigur, compilatorului îi este teamă că cele trei argumente ale mele (trei felii) sunt de dimensiuni diferite. La urma urmei, atunci va exista o posibilitate teoretică de apariție a așa-numitului depășire a tamponului.

Să liniștim compilatorul arătându-i că toate feliile au aceeași dimensiune. Putem face acest lucru adăugând o verificare simplă la începutul funcției noastre.
Indexuri bitmap în Go: căutare cu viteză sălbatică
Văzând asta, compilatorul omite cu bucurie verificarea și ajungem să economisim încă 500 de nanosecunde.

Bucuri mari

Bine, am reușit să strângem o anumită performanță din implementarea noastră simplă, dar acest rezultat este de fapt mult mai rău decât este posibil cu hardware-ul actual.

Tot ce facem sunt operațiuni de bază pe biți, iar procesoarele noastre le execută foarte eficient. Dar, din păcate, ne „alimentăm” procesorul cu lucrări foarte mici. Funcțiile noastre efectuează operațiuni pe o bază octet cu octet. Putem modifica foarte ușor codul nostru pentru a funcționa cu bucăți de 8 octeți folosind secțiuni UInt64.

Indexuri bitmap în Go: căutare cu viteză sălbatică

După cum puteți vedea, această mică modificare a accelerat programul nostru de opt ori, mărind dimensiunea lotului de opt ori. Se poate spune că câștigul este liniar.

Indexuri bitmap în Go: căutare cu viteză sălbatică

Implementare în asamblator

Indexuri bitmap în Go: căutare cu viteză sălbatică
Dar acesta nu este sfârșitul. Procesoarele noastre pot lucra cu bucăți de 16, 32 și chiar 64 de octeți. Astfel de operațiuni „large” sunt numite date multiple cu o singură instrucțiune (SIMD; o instrucțiune, multe date), iar procesul de transformare a codului astfel încât să folosească astfel de operații se numește vectorizare.

Din păcate, compilatorul Go este departe de a fi excelent la vectorizare. În prezent, singura modalitate de a vectoriza codul Go este să luați și să puneți aceste operații manual folosind Go assembler.

Indexuri bitmap în Go: căutare cu viteză sălbatică

Go assembler este o fiară ciudată. Probabil știți că limbajul de asamblare este ceva care este strâns legat de arhitectura computerului pentru care scrieți, dar nu este cazul în Go. Go assembler seamănă mai mult cu un IRL (limbaj de reprezentare intermediar) sau un limbaj intermediar: practic este independent de platformă. Rob Pike a dat o performanță excelentă raport pe acest subiect în urmă cu câțiva ani la GopherCon din Denver.

În plus, Go utilizează un format neobișnuit Plan 9, care diferă de formatele general acceptate AT&T și Intel.
Indexuri bitmap în Go: căutare cu viteză sălbatică
Este sigur să spunem că scrierea manuală a Go assembler nu este cea mai distractivă.

Dar, din fericire, există deja două instrumente de nivel înalt care ne ajută să scriem Go assembler: PeachPy și avo. Ambele utilitare generează asamblatorul Go din codul de nivel superior scris în Python și, respectiv, Go.
Indexuri bitmap în Go: căutare cu viteză sălbatică
Aceste utilitare simplifică lucruri precum alocarea de registre, bucle de scriere și, în general, simplifică procesul de intrare în lumea programării de asamblare în Go.

Vom folosi avo, așa că programele noastre vor fi programe Go aproape obișnuite.
Indexuri bitmap în Go: căutare cu viteză sălbatică
Așa arată cel mai simplu exemplu de program avo. Avem o funcție main(), care definește în ea însăși funcția Add(), al cărei sens este să adunăm două numere. Există funcții de ajutor aici pentru a obține parametrii după nume și pentru a obține unul dintre registrele de procesor gratuite și adecvate. Fiecare operație de procesor are o funcție corespunzătoare pe avo, așa cum se vede în ADDQ. În cele din urmă, vedem o funcție de ajutor pentru stocarea valorii rezultate.
Indexuri bitmap în Go: căutare cu viteză sălbatică
Apelând go generate, vom executa programul pe avo și, ca urmare, vor fi generate două fișiere:

  • add.s cu codul rezultat în Go assembler;
  • stub.go cu anteturi de funcții pentru a conecta cele două lumi: Go și assembler.

Indexuri bitmap în Go: căutare cu viteză sălbatică
Acum că am văzut ce face avo și cum, să ne uităm la funcțiile noastre. Am implementat ambele versiuni scalare și vectoriale (SIMD) ale funcțiilor.

Să ne uităm mai întâi la versiunile scalare.
Indexuri bitmap în Go: căutare cu viteză sălbatică
Ca și în exemplul anterior, solicităm un registru de uz general gratuit și valid, nu trebuie să calculăm compensații și dimensiuni pentru argumente. avo face toate acestea pentru noi.
Indexuri bitmap în Go: căutare cu viteză sălbatică
Obișnuiam să folosim etichete și goto (sau salturi) pentru a îmbunătăți performanța și a păcăli compilatorul Go, dar acum o facem de la început. Ideea este că ciclurile sunt un concept de nivel superior. În assembler, avem doar etichete și salturi.
Indexuri bitmap în Go: căutare cu viteză sălbatică
Codul rămas ar trebui să fie deja familiar și ușor de înțeles. Emulăm o buclă cu etichete și salturi, luăm o mică bucată de date din cele două felii ale noastre, le combinăm cu o operațiune pe bit (ȘI NU în acest caz) și apoi punem rezultatul în felia rezultată. Toate.
Indexuri bitmap în Go: căutare cu viteză sălbatică
Așa arată codul de asamblare final. Nu a fost nevoie să calculăm decalaje și dimensiuni (evidențiate cu verde) sau să ținem evidența registrelor utilizate (evidențiate cu roșu).
Indexuri bitmap în Go: căutare cu viteză sălbatică
Dacă comparăm performanța implementării limbajului de asamblare cu performanța celei mai bune implementări din Go, vom vedea că este la fel. Și acest lucru este de așteptat. La urma urmei, nu am făcut nimic special - am reprodus doar ceea ce ar face un compilator Go.

Din păcate, nu putem forța compilatorul să integreze funcțiile noastre scrise în limbaj de asamblare. Compilatorul Go nu are în prezent o astfel de caracteristică, deși a existat o solicitare de a o adăuga de ceva timp.

Acesta este motivul pentru care este imposibil să obțineți niciun beneficiu din funcțiile mici în limbajul de asamblare. Trebuie fie să scriem funcții mari, fie să folosim noul pachet matematic/biți, fie să ocolim limbajul de asamblare.

Să ne uităm acum la versiunile vectoriale ale funcțiilor noastre.
Indexuri bitmap în Go: căutare cu viteză sălbatică
Pentru acest exemplu, am decis să folosesc AVX2, așa că vom folosi operațiuni care operează pe bucăți de 32 de octeți. Structura codului este foarte asemănătoare cu versiunea scalară: încărcarea parametrilor, solicitarea unui registru partajat gratuit etc.
Indexuri bitmap în Go: căutare cu viteză sălbatică
O inovație este că operațiunile cu vectori mai largi utilizează registre largi speciale. În cazul bucăților de 32 de octeți, acestea sunt registre prefixate cu Y. Acesta este motivul pentru care vedeți funcția YMM() în cod. Dacă aș folosi AVX-512 cu bucăți de 64 de biți, prefixul ar fi Z.

A doua inovație este că am decis să folosesc o optimizare numită loop unrolling, ceea ce înseamnă să faci opt operații de buclă manual înainte de a sări la începutul buclei. Această optimizare reduce numărul de ramuri din cod și este limitată de numărul de registre gratuite disponibile.
Indexuri bitmap în Go: căutare cu viteză sălbatică
Ei bine, ce zici de performanță? E frumoasă! Am obținut o accelerare de aproximativ șapte ori în comparație cu cea mai bună soluție Go. Impresionant, nu?
Indexuri bitmap în Go: căutare cu viteză sălbatică
Dar chiar și această implementare ar putea fi accelerată prin utilizarea AVX-512, preîncărcarea sau un JIT (compilator just-in-time) pentru planificatorul de interogări. Dar acesta este cu siguranță un subiect pentru un raport separat.

Probleme cu indexurile bitmap

Acum că ne-am uitat deja la o implementare simplă a unui index bitmap în Go și una mult mai productivă în limbajul de asamblare, să vorbim în sfârșit despre de ce indexurile bitmap sunt atât de rar folosiți.
Indexuri bitmap în Go: căutare cu viteză sălbatică
Lucrările mai vechi menționează trei probleme cu indexurile bitmap, dar lucrările mai noi și eu susțin că nu mai sunt relevante. Nu ne vom scufunda adânc în fiecare dintre aceste probleme, ci le vom privi superficial.

Problema cardinalității înalte

Așadar, ni se spune că indicii bitmap sunt potriviți doar pentru câmpurile cu cardinalitate scăzută, adică cele care au puține valori (de exemplu, genul sau culoarea ochilor), iar motivul este că reprezentarea obișnuită a unor astfel de câmpuri (una bit per valoare) în cazul cardinalității mari, va ocupa prea mult spațiu și, în plus, acești indici bitmap vor fi umpluți prost (rar).
Indexuri bitmap în Go: căutare cu viteză sălbatică
Indexuri bitmap în Go: căutare cu viteză sălbatică
Uneori putem folosi o reprezentare diferită, cum ar fi cea standard pe care o folosim pentru a reprezenta numere. Dar apariția algoritmilor de compresie a schimbat totul. În ultimele decenii, oamenii de știință și cercetătorii au venit cu un număr mare de algoritmi de compresie pentru bitmaps. Principalul lor avantaj este că nu este nevoie să decomprimați hărțile de biți pentru a efectua operații pe biți - putem efectua operații pe biți direct pe hărțile de biți comprimate.
Indexuri bitmap în Go: căutare cu viteză sălbatică
Recent, au început să apară abordări hibride, cum ar fi hărțile de bit zgomotoase. Ei folosesc simultan trei reprezentări diferite pentru hărți de biți - hărți de biți în sine, matrice și așa-numitele rulări de biți - și echilibrează între ele pentru a maximiza performanța și a minimiza consumul de memorie.

Puteți găsi hărți de bit în cele mai populare aplicații. Există deja un număr mare de implementări pentru o mare varietate de limbaje de programare, inclusiv mai mult de trei implementări pentru Go.
Indexuri bitmap în Go: căutare cu viteză sălbatică
O altă abordare care ne poate ajuta să facem față cu cardinalitatea ridicată se numește binning. Imaginați-vă că aveți un câmp care reprezintă înălțimea unei persoane. Înălțimea este un număr în virgulă mobilă, dar noi, oamenii, nu o gândim așa. Pentru noi nu există nicio diferență între înălțimea 185,2 cm și 185,3 cm.

Se pare că putem grupa valori similare în grupuri în decurs de 1 cm.

Și dacă știm, de asemenea, că foarte puțini oameni sunt mai mici de 50 cm și mai înalți de 250 cm, atunci putem, în esență, să transformăm un câmp cu cardinalitate infinită într-un câmp cu o cardinalitate de aproximativ 200 de valori.

Desigur, dacă este necesar, putem face filtrare suplimentară ulterior.

Problemă cu lățimea de bandă mare

Următoarea problemă cu indexurile bitmap este că actualizarea acestora poate fi foarte costisitoare.

Bazele de date trebuie să poată actualiza datele în timp ce sute de alte interogări caută datele. Avem nevoie de blocări pentru a evita problemele legate de accesul simultan la date sau alte probleme de partajare. Și acolo unde există o încuietoare mare, există o problemă - disputa de blocare, când această lacăt devine un blocaj.
Indexuri bitmap în Go: căutare cu viteză sălbatică
Această problemă poate fi rezolvată sau ocolită prin utilizarea sharding-ului sau a indicilor versionați.

Sharding-ul este un lucru simplu și binecunoscut. Puteți fragmenta un index bitmap la fel ca orice alte date. În loc de o lacăt mare, veți obține o grămadă de lacăte mici și, astfel, veți scăpa de conflictul de lacăt.

A doua modalitate de a rezolva problema este utilizarea indecșilor versionați. Puteți avea o copie a indexului pe care o utilizați pentru căutare sau citire și una pe care o utilizați pentru scriere sau actualizare. Și o dată într-o anumită perioadă de timp (de exemplu, o dată la 100 ms sau 500 ms) le dublezi și le schimbi. Desigur, această abordare este aplicabilă numai în cazurile în care aplicația dvs. poate gestiona un index de căutare ușor întârziat.

Aceste două abordări pot fi utilizate simultan: puteți avea un index cu versiuni fragmentate.

Interogări mai complexe

Ultima problemă cu indexurile bitmap este că ni se spune că nu sunt potrivite pentru tipuri mai complexe de interogări, cum ar fi interogările span.

Într-adevăr, dacă vă gândiți bine, operațiunile pe biți precum AND, OR etc. nu sunt foarte potrivite pentru întrebări de genul „Arătați-mi hoteluri cu tarife între 200 și 300 de dolari pe noapte”.
Indexuri bitmap în Go: căutare cu viteză sălbatică
O soluție naivă și foarte neînțeleaptă ar fi să luați rezultatele pentru fiecare valoare în dolari și să le combinați cu o operație OR pe biți.
Indexuri bitmap în Go: căutare cu viteză sălbatică
O soluție puțin mai bună ar fi să folosiți gruparea. De exemplu, în grupuri de 50 de dolari. Acest lucru ne-ar accelera procesul de 50 de ori.

Dar problema se rezolvă cu ușurință și folosind o vizualizare creată special pentru acest tip de solicitare. În lucrările științifice se numește hărți de biți codificate în intervale.
Indexuri bitmap în Go: căutare cu viteză sălbatică
În această reprezentare, nu setăm doar un bit pentru o anumită valoare (de exemplu, 200), ci setăm această valoare și totul mai mare. 200 și mai sus. Același lucru pentru 300: 300 și mai sus. Și așa mai departe.

Folosind această reprezentare, putem răspunde la acest tip de interogare de căutare parcurgând indexul de doar două ori. În primul rând, vom obține o listă de hoteluri în care camera costă mai puțin sau 300 USD, iar apoi le vom elimina pe cele în care costul camerei este mai mic sau 199 USD. Gata.
Indexuri bitmap în Go: căutare cu viteză sălbatică
Veți fi surprins, dar chiar și geointerogări sunt posibile folosind indecși bitmap. Trucul este să folosești o reprezentare geometrică care înconjoară coordonatele tale cu o figură geometrică. De exemplu, S2 de la Google. Figura ar trebui să poată fi reprezentată sub forma a trei sau mai multe linii care se intersectează care pot fi numerotate. În acest fel, ne putem transforma geoquery în mai multe interogări „de-a lungul intervalului” (de-a lungul acestor linii numerotate).

Soluții gata

Sper că te-am interesat puțin și acum ai un alt instrument util în arsenalul tău. Dacă trebuie vreodată să faci așa ceva, vei ști în ce direcție să arăți.

Cu toate acestea, nu toată lumea are timpul, răbdarea sau resursele pentru a crea indici bitmap de la zero. Mai ales cele mai avansate, folosind SIMD, de exemplu.

Din fericire, există mai multe soluții gata făcute pentru a vă ajuta.
Indexuri bitmap în Go: căutare cu viteză sălbatică

Bitmap-uri grozave

În primul rând, există aceeași bibliotecă de bitmap-uri de care am vorbit deja. Conține toate containerele necesare și operațiunile de biți de care veți avea nevoie pentru a crea un index de bitmap cu drepturi depline.
Indexuri bitmap în Go: căutare cu viteză sălbatică
Din păcate, în acest moment, niciuna dintre implementările Go nu utilizează SIMD, ceea ce înseamnă că implementările Go sunt mai puțin performante decât implementările C, de exemplu.

păros

Un alt produs care te poate ajuta este DBMS-ul Pilosa, care, de fapt, are doar indici bitmap. Aceasta este o soluție relativ nouă, dar câștigă inimi cu mare viteză.
Indexuri bitmap în Go: căutare cu viteză sălbatică
Pilosa folosește în interior bitmap-uri roaring și vă oferă posibilitatea de a le folosi, simplifică și explică toate lucrurile despre care am vorbit mai sus: grupare, bitmap-uri codificate în interval, conceptul de câmp etc.

Să aruncăm o privire rapidă la un exemplu de utilizare a Pilosa pentru a răspunde la o întrebare cu care ești deja familiarizat.
Indexuri bitmap în Go: căutare cu viteză sălbatică
Exemplul este foarte asemănător cu ceea ce ați văzut înainte. Creăm un client pentru serverul Pilosa, creăm un index și câmpurile necesare, apoi umplem câmpurile noastre cu date aleatorii cu probabilități și, în final, executăm interogarea familiară.

După aceea, folosim NOT pe câmpul „scump”, apoi intersectăm rezultatul (sau AND) cu câmpul „terasa” și cu câmpul „rezervări”. Și, în sfârșit, obținem rezultatul final.
Indexuri bitmap în Go: căutare cu viteză sălbatică
Sper din tot sufletul ca în viitorul apropiat acest nou tip de index să apară și în DBMS-uri precum MySQL și PostgreSQL - indexuri bitmap.
Indexuri bitmap în Go: căutare cu viteză sălbatică

Concluzie

Indexuri bitmap în Go: căutare cu viteză sălbatică
Dacă nu ai adormit încă, mulțumesc. A trebuit să abordez pe scurt multe subiecte din cauza timpului limitat, dar sper că discursul a fost util și poate chiar motivant.

Este bine să știți despre indicii bitmap, chiar dacă nu aveți nevoie de ei acum. Lasă-le să fie un alt instrument în cutia ta de instrumente.

Am analizat diverse trucuri de performanță pentru Go și lucruri pe care compilatorul Go nu le gestionează încă foarte bine. Dar acest lucru este absolut util pentru fiecare programator Go să știe.

Atât am vrut să-ți spun. Mulțumesc!

Sursa: www.habr.com

Adauga un comentariu