Indici bitmap in Go: ricerca a tutta velocità

Indici bitmap in Go: ricerca a tutta velocità

discorso aperto

Ho presentato questo rapporto in inglese alla conferenza GopherCon Russia 2019 a Mosca e in russo durante un incontro a Nizhny Novgorod. Stiamo parlando di un indice bitmap, meno comune del B-tree, ma non per questo meno interessante. Condivisione record interventi al convegno in inglese e trascrizioni dei testi in russo.

Vedremo come funziona un indice bitmap, quando è migliore, quando è peggiore di altri indici e in quali casi è significativamente più veloce di loro; Vediamo quali popolari DBMS dispongono già di indici bitmap; Proviamo a scrivere il nostro in Go. E "per dessert" utilizzeremo librerie già pronte per creare il nostro database specializzato superveloce.

Spero davvero che i miei lavori ti siano utili e interessanti. Andare!

Introduzione


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

Ciao a tutti! Sono le sei di sera e siamo tutti stanchissimi. Ottimo momento per parlare della noiosa teoria dell'indice dei database, giusto? Non preoccuparti, avrò un paio di righe di codice sorgente qua e là. 🙂

Scherzi a parte, il rapporto è pieno zeppo di informazioni e non abbiamo molto tempo. Quindi iniziamo.
Indici bitmap in Go: ricerca a tutta velocità
Oggi parlerò di quanto segue:

  • cosa sono gli indici;
  • cos'è un indice bitmap;
  • dove viene utilizzato e dove NON viene utilizzato e perché;
  • semplice implementazione in Go e qualche difficoltà con il compilatore;
  • implementazione leggermente meno semplice, ma molto più produttiva nell'assemblatore Go;
  • “problemi” degli indici bitmap;
  • implementazioni esistenti.

Allora cosa sono gli indici?

Indici bitmap in Go: ricerca a tutta velocità

L'indice è una struttura dati separata che manteniamo e aggiorniamo in aggiunta ai dati principali. Serve per velocizzare la ricerca. Senza indici, la ricerca richiederebbe l'analisi completa dei dati (un processo chiamato scansione completa) e questo processo ha una complessità algoritmica lineare. Ma i database solitamente contengono enormi quantità di dati e la complessità lineare è troppo lenta. Idealmente, otterremmo uno logaritmico o costante.

Si tratta di un argomento estremamente complesso, pieno di sottigliezze e compromessi, ma dopo aver esaminato decenni di sviluppo e ricerca di database, sono disposto a dire che esistono solo pochi approcci ampiamente utilizzati per creare indici di database.

Indici bitmap in Go: ricerca a tutta velocità

Il primo approccio consiste nel ridurre gerarchicamente lo spazio di ricerca, dividendolo in parti più piccole.

Di solito lo facciamo utilizzando diversi tipi di alberi. Un esempio potrebbe essere una grande scatola di materiali nel tuo armadio che contiene scatole più piccole di materiali divisi in diversi argomenti. Se hai bisogno di materiali, probabilmente li cercherai in una casella con la dicitura "Materiali" anziché in una con la dicitura "Cookie", giusto?

Indici bitmap in Go: ricerca a tutta velocità

Il secondo approccio consiste nel selezionare immediatamente l'elemento o il gruppo di elementi desiderati. Lo facciamo in mappe hash o indici inversi. L'uso delle mappe hash è molto simile all'esempio precedente, ma invece di una scatola di scatole, hai un mucchio di piccole scatole di oggetti finali nel tuo armadio.

Indici bitmap in Go: ricerca a tutta velocità

Il terzo approccio consiste nell'eliminare la necessità di ricerca. Lo facciamo utilizzando i filtri Bloom o i filtri cuculo. I primi danno una risposta istantanea, evitandoti di dover cercare.

Indici bitmap in Go: ricerca a tutta velocità

L'ultimo approccio consiste nel sfruttare appieno tutta la potenza offerta dall'hardware moderno. Questo è esattamente ciò che facciamo negli indici bitmap. Sì, quando li utilizziamo a volte dobbiamo scorrere l'intero indice, ma lo facciamo in modo estremamente efficiente.

Come ho detto, il tema degli indici dei database è vasto e pieno di compromessi. Ciò significa che a volte possiamo utilizzare più approcci contemporaneamente: se abbiamo bisogno di velocizzare ancora di più la ricerca o se dobbiamo coprire tutti i possibili tipi di ricerca.

Oggi parlerò dell'approccio meno conosciuto di questi: gli indici bitmap.

Chi sono io per parlare di questo argomento?

Indici bitmap in Go: ricerca a tutta velocità

Lavoro come team leader presso Badoo (forse hai più familiarità con l'altro nostro prodotto, Bumble). Abbiamo già più di 400 milioni di utenti in tutto il mondo e molte funzionalità che selezionano la soluzione migliore per loro. Lo facciamo utilizzando servizi personalizzati, inclusi gli indici bitmap.

Allora, cos'è un indice bitmap?

Indici bitmap in Go: ricerca a tutta velocità
Gli indici bitmap, come suggerisce il nome, utilizzano bitmap o bitset per implementare un indice di ricerca. Da una vista a volo d'uccello, questo indice è costituito da una o più bitmap che rappresentano qualsiasi entità (come le persone) e le loro proprietà o parametri (età, colore degli occhi, ecc.) e un algoritmo che utilizza operazioni bit (AND, OR, NOT ) per rispondere alla query di ricerca.
Indici bitmap in Go: ricerca a tutta velocità
Ci è stato detto che gli indici bitmap sono più adatti e molto performanti per i casi in cui sono presenti ricerche che combinano query su molte colonne con cardinalità bassa (si pensi al "colore degli occhi" o allo "stato civile" rispetto a qualcosa come "distanza dal centro città"). Ma in seguito mostrerò che funzionano perfettamente anche per le colonne ad alta cardinalità.

Diamo un'occhiata all'esempio più semplice di un indice bitmap.
Indici bitmap in Go: ricerca a tutta velocità
Immagina di avere un elenco di ristoranti di Mosca con proprietà binarie come queste:

  • vicino alla metropolitana;
  • c'è un parcheggio privato;
  • c'è una veranda (ha una terrazza);
  • è possibile riservare un tavolo (accetta prenotazioni);
  • adatto ai vegetariani (vegan friendly);
  • costoso (costoso).

Indici bitmap in Go: ricerca a tutta velocità
Assegnamo ad ogni ristorante un numero progressivo partendo da 0 e assegniamo memoria per 6 bitmap (una per ogni caratteristica). Popoleremo quindi queste bitmap a seconda che il ristorante disponga o meno di questa proprietà. Se il ristorante 4 ha una veranda, allora il bit n. 4 nella bitmap “ha una veranda” verrà impostato a 1 (se non c'è veranda, a 0).
Indici bitmap in Go: ricerca a tutta velocità
Ora abbiamo l'indice bitmap più semplice possibile e possiamo usarlo per rispondere a domande come:

  • “Mostrami ristoranti vegetariani”;
  • "Mostrami ristoranti economici con veranda dove puoi prenotare un tavolo."

Indici bitmap in Go: ricerca a tutta velocità
Indici bitmap in Go: ricerca a tutta velocità
Come? Diamo un'occhiata. La prima richiesta è molto semplice. Tutto quello che dobbiamo fare è prendere la bitmap “vegetarian friendly” e trasformarla in un elenco di ristoranti i cui pezzi sono esposti.
Indici bitmap in Go: ricerca a tutta velocità
Indici bitmap in Go: ricerca a tutta velocità
La seconda richiesta è un po’ più complicata. Dobbiamo utilizzare la bitmap NOT sulla bitmap “costoso” per ottenere un elenco di ristoranti economici, poi AND con la bitmap “posso prenotare un tavolo” e AND il risultato con la bitmap “c'è una veranda”. La bitmap risultante conterrà un elenco di stabilimenti che soddisfano tutti i nostri criteri. In questo esempio si tratta solo del ristorante Yunost.
Indici bitmap in Go: ricerca a tutta velocità
Indici bitmap in Go: ricerca a tutta velocità
C'è molta teoria coinvolta, ma non preoccuparti, vedremo il codice molto presto.

Dove vengono utilizzati gli indici bitmap?

Indici bitmap in Go: ricerca a tutta velocità
Se utilizzi gli indici bitmap di Google, il 90% delle risposte sarà correlato a Oracle DB in un modo o nell'altro. Ma probabilmente anche altri DBMS supportano una cosa così interessante, giusto? Non proprio.

Esaminiamo l'elenco dei principali sospettati.
Indici bitmap in Go: ricerca a tutta velocità
MySQL non supporta ancora gli indici bitmap, ma esiste una proposta che suggerisce di aggiungere questa opzione (https://dev.mysql.com/worklog/task/?id=1524).

PostgreSQL non supporta gli indici bitmap, ma utilizza semplici bitmap e operazioni bit per combinare i risultati della ricerca su più altri indici.

Tarantool dispone di indici bitset e supporta semplici ricerche su di essi.

Redis ha bitfield semplici (https://redis.io/commands/bitfield) senza la possibilità di cercarli.

MongoDB non supporta ancora gli indici bitmap, ma esiste anche una proposta che suggerisce di aggiungere questa opzione https://jira.mongodb.org/browse/SERVER-1723

Elasticsearch utilizza le bitmap internamente (https://www.elastic.co/blog/frame-of-reference-and-roaring-bitmaps).

Indici bitmap in Go: ricerca a tutta velocità

  • Ma in casa nostra è comparsa una nuova vicina: Pilosa. Questo è un nuovo database non relazionale scritto in Go. Contiene solo indici bitmap e basa tutto su di essi. Ne parleremo un po' più tardi.

Implementazione in Go

Ma perché gli indici bitmap vengono utilizzati così raramente? Prima di rispondere a questa domanda vorrei mostrarti come implementare un semplicissimo indice bitmap in Go.
Indici bitmap in Go: ricerca a tutta velocità
Le bitmap sono essenzialmente solo pezzi di dati. In Go, utilizziamo le sezioni di byte per questo.

Abbiamo una bitmap per una caratteristica del ristorante e ogni bit nella bitmap indica se un particolare ristorante ha questa proprietà o meno.
Indici bitmap in Go: ricerca a tutta velocità
Avremo bisogno di due funzioni di supporto. Uno verrà utilizzato per riempire le nostre bitmap con dati casuali. Casuale, ma con una certa probabilità che il ristorante abbia ciascuna proprietà. Ad esempio, credo che siano pochissimi i ristoranti a Mosca dove non è possibile prenotare un tavolo, e mi sembra che circa il 20% dei locali sia adatto ai vegetariani.

La seconda funzione convertirà la bitmap in un elenco di ristoranti.
Indici bitmap in Go: ricerca a tutta velocità
Indici bitmap in Go: ricerca a tutta velocità
Per rispondere alla domanda “Mostrami ristoranti economici che abbiano un patio e possano prenotare”, abbiamo bisogno di operazioni a due bit: NOT e AND.

Possiamo semplificare un po' il nostro codice utilizzando l'operatore più complesso AND NOT.

Abbiamo funzioni per ciascuna di queste operazioni. Entrambi esaminano le sezioni, prendono gli elementi corrispondenti da ciascuna, li combinano con una piccola operazione e inseriscono il risultato nella sezione risultante.
Indici bitmap in Go: ricerca a tutta velocità
E ora possiamo utilizzare le nostre bitmap e funzioni per rispondere alla query di ricerca.
Indici bitmap in Go: ricerca a tutta velocità
Le prestazioni non sono così elevate, anche se le funzioni sono molto semplici e abbiamo risparmiato un sacco di soldi non restituendo una nuova fetta risultante ogni volta che la funzione veniva chiamata.

Dopo aver fatto un po' di profilazione con pprof, ho notato che al compilatore Go mancava un'ottimizzazione molto semplice ma molto importante: l'inlining della funzione.
Indici bitmap in Go: ricerca a tutta velocità
Il fatto è che il compilatore Go ha una paura terribile dei loop che attraversano le sezioni e rifiuta categoricamente di incorporare funzioni che contengono tali loop.
Indici bitmap in Go: ricerca a tutta velocità
Ma non ho paura e posso ingannare il compilatore usando goto invece di un loop, come ai bei vecchi tempi.

Indici bitmap in Go: ricerca a tutta velocità
Indici bitmap in Go: ricerca a tutta velocità

E, come puoi vedere, ora il compilatore inlineerà felicemente la nostra funzione! Di conseguenza, riusciamo a risparmiare circa 2 microsecondi. Non male!

Indici bitmap in Go: ricerca a tutta velocità

Il secondo collo di bottiglia è facile da vedere se si osserva attentamente l'output dell'assieme. Il compilatore ha aggiunto un controllo dei limiti della sezione direttamente all'interno del nostro ciclo più caldo. Il fatto è che Go è un linguaggio sicuro, il compilatore teme che i miei tre argomenti (tre fette) siano di dimensioni diverse. Dopotutto in tal caso esiste teoricamente la possibilità che si verifichi un cosiddetto buffer overflow.

Rassicuriamo il compilatore mostrandogli che tutte le fette hanno la stessa dimensione. Possiamo farlo aggiungendo un semplice segno di spunta all'inizio della nostra funzione.
Indici bitmap in Go: ricerca a tutta velocità
Vedendo ciò, il compilatore salta felicemente il controllo e finiamo per risparmiare altri 500 nanosecondi.

Grandi macellerie

Ok, siamo riusciti a spremere un po' di prestazioni dalla nostra semplice implementazione, ma questo risultato è in realtà molto peggiore di quanto sia possibile con l'hardware attuale.

Tutto ciò che facciamo sono operazioni di bit di base e i nostri processori le eseguono in modo molto efficiente. Ma sfortunatamente “nutriamo” il nostro processore con piccolissimi lavori. Le nostre funzioni eseguono operazioni byte per byte. Possiamo modificare molto facilmente il nostro codice per farlo funzionare con blocchi da 8 byte utilizzando le sezioni UInt64.

Indici bitmap in Go: ricerca a tutta velocità

Come puoi vedere, questa piccola modifica ha accelerato il nostro programma di otto volte aumentando la dimensione del batch di otto volte. Si può dire che il guadagno sia lineare.

Indici bitmap in Go: ricerca a tutta velocità

Implementazione in assembler

Indici bitmap in Go: ricerca a tutta velocità
Ma questa non è la fine. I nostri processori possono funzionare con blocchi da 16, 32 e anche 64 byte. Tali operazioni "ampie" sono chiamate dati multipli di istruzioni singole (SIMD; un'istruzione, molti dati) e il processo di trasformazione del codice in modo che utilizzi tali operazioni è chiamato vettorizzazione.

Sfortunatamente, il compilatore Go è tutt’altro che eccellente nella vettorizzazione. Attualmente, l'unico modo per vettorizzare il codice Go è prendere e inserire queste operazioni manualmente utilizzando l'assemblatore Go.

Indici bitmap in Go: ricerca a tutta velocità

Go assembler è una strana bestia. Probabilmente sai che il linguaggio assembly è qualcosa che è fortemente legato all'architettura del computer per cui stai scrivendo, ma non è il caso di Go. Go assembler è più simile a un IRL (linguaggio di rappresentazione intermedia) o linguaggio intermedio: è praticamente indipendente dalla piattaforma. Rob Pike ha dato una prestazione eccellente rapporto su questo argomento diversi anni fa al GopherCon di Denver.

Inoltre, Go utilizza un insolito formato Plan 9, che differisce dai formati AT&T e Intel generalmente accettati.
Indici bitmap in Go: ricerca a tutta velocità
Si può dire con certezza che scrivere Go assembler a mano non è la cosa più divertente.

Ma, fortunatamente, ci sono già due strumenti di alto livello che ci aiutano a scrivere l'assemblatore Go: PeachPy e avo. Entrambe le utilità generano l'assemblatore Go da codice di livello superiore scritto rispettivamente in Python e Go.
Indici bitmap in Go: ricerca a tutta velocità
Queste utilità semplificano cose come l'allocazione dei registri, la scrittura dei cicli e in generale semplificano il processo di accesso al mondo della programmazione assembly in Go.

Useremo evita, quindi i nostri programmi saranno quasi normali programmi Go.
Indici bitmap in Go: ricerca a tutta velocità
Questo è l'esempio più semplice di un programma evita. Abbiamo una funzione main(), che definisce al suo interno la funzione Add(), il cui significato è sommare due numeri. Qui sono presenti funzioni di supporto per ottenere i parametri per nome e ottenere uno dei registri del processore gratuiti e adatti. Ogni operazione del processore ha una funzione corrispondente su avo, come visto in ADDQ. Infine, vediamo una funzione di supporto per memorizzare il valore risultante.
Indici bitmap in Go: ricerca a tutta velocità
Chiamando go generate eseguiremo il programma su avo e di conseguenza verranno generati due file:

  • add.s con il codice risultante nell'assemblatore Go;
  • stub.go con intestazioni di funzione per connettere i due mondi: Go e assembler.

Indici bitmap in Go: ricerca a tutta velocità
Ora che abbiamo visto cosa fa avo e come, diamo un'occhiata alle nostre funzioni. Ho implementato sia la versione scalare che quella vettoriale (SIMD) delle funzioni.

Diamo prima un'occhiata alle versioni scalari.
Indici bitmap in Go: ricerca a tutta velocità
Come nell'esempio precedente, chiediamo un registro di uso generale libero e valido, non è necessario calcolare offset e dimensioni per gli argomenti. avo fa tutto questo per noi.
Indici bitmap in Go: ricerca a tutta velocità
Prima usavamo etichette e goto (o salti) per migliorare le prestazioni e ingannare il compilatore Go, ma ora lo facciamo dall'inizio. Il punto è che i cicli sono un concetto di livello superiore. Nell'assemblatore abbiamo solo etichette e salti.
Indici bitmap in Go: ricerca a tutta velocità
Il codice rimanente dovrebbe già essere familiare e comprensibile. Emuliamo un loop con etichette e salti, prendiamo una piccola porzione di dati dalle nostre due sezioni, li combiniamo con un'operazione di bit (AND NOT in questo caso) e quindi inseriamo il risultato nella sezione risultante. Tutto.
Indici bitmap in Go: ricerca a tutta velocità
Questo è l'aspetto del codice assembler finale. Non abbiamo dovuto calcolare offset e dimensioni (evidenziati in verde) né tenere traccia dei registri utilizzati (evidenziati in rosso).
Indici bitmap in Go: ricerca a tutta velocità
Se confrontiamo le prestazioni dell'implementazione del linguaggio assembly con le prestazioni della migliore implementazione in Go, vedremo che è la stessa. E questo è previsto. Dopotutto, non abbiamo fatto nulla di speciale: abbiamo semplicemente riprodotto ciò che avrebbe fatto un compilatore Go.

Sfortunatamente, non possiamo forzare il compilatore a incorporare le nostre funzioni scritte in linguaggio assembly. Il compilatore Go attualmente non dispone di tale funzionalità, anche se da tempo c'è stata una richiesta di aggiungerla.

Questo è il motivo per cui è impossibile trarre alcun vantaggio dalle piccole funzioni in linguaggio assembly. Dobbiamo scrivere funzioni di grandi dimensioni o utilizzare il nuovo pacchetto math/bits o ignorare il linguaggio assembler.

Diamo ora un'occhiata alle versioni vettoriali delle nostre funzioni.
Indici bitmap in Go: ricerca a tutta velocità
Per questo esempio, ho deciso di utilizzare AVX2, quindi utilizzeremo operazioni che operano su blocchi da 32 byte. La struttura del codice è molto simile alla versione scalare: caricamento dei parametri, richiesta di un registro condiviso gratuito, ecc.
Indici bitmap in Go: ricerca a tutta velocità
Un'innovazione è che le operazioni vettoriali più ampie utilizzano registri ampi speciali. Nel caso di blocchi da 32 byte, questi sono registri con il prefisso Y. Questo è il motivo per cui nel codice vedi la funzione YMM(). Se utilizzassi AVX-512 con blocchi a 64 bit, il prefisso sarebbe Z.

La seconda innovazione è che ho deciso di utilizzare un'ottimizzazione chiamata loop unrolling, che significa eseguire manualmente otto operazioni di loop prima di saltare all'inizio del loop. Questa ottimizzazione riduce il numero di rami nel codice ed è limitata dal numero di registri liberi disponibili.
Indici bitmap in Go: ricerca a tutta velocità
Bene, che dire delle prestazioni? Lei è bellissima! Abbiamo ottenuto una velocità di circa sette volte rispetto alla migliore soluzione Go. Impressionante, vero?
Indici bitmap in Go: ricerca a tutta velocità
Ma anche questa implementazione potrebbe essere potenzialmente accelerata utilizzando AVX-512, il prefetching o un JIT (compilatore just-in-time) per lo scheduler delle query. Ma questo è certamente un argomento per un rapporto separato.

Problemi con gli indici bitmap

Ora che abbiamo già esaminato una semplice implementazione di un indice bitmap in Go e una molto più produttiva in linguaggio assembly, parliamo finalmente del motivo per cui gli indici bitmap sono utilizzati così raramente.
Indici bitmap in Go: ricerca a tutta velocità
I documenti più vecchi menzionano tre problemi con gli indici bitmap, ma i documenti più recenti e io sosteniamo che non sono più rilevanti. Non approfondiremo ciascuno di questi problemi, ma li esamineremo superficialmente.

Il problema dell'alta cardinalità

Quindi, ci viene detto che gli indici bitmap sono adatti solo per i campi con cardinalità bassa, cioè quelli che hanno pochi valori (ad esempio, il sesso o il colore degli occhi), e il motivo è che la rappresentazione usuale di tali campi (uno bit per valore) in caso di cardinalità elevata, occuperà troppo spazio e, inoltre, questi indici bitmap saranno poco (raramente) riempiti.
Indici bitmap in Go: ricerca a tutta velocità
Indici bitmap in Go: ricerca a tutta velocità
A volte potremmo usare una rappresentazione diversa, come quella standard che usiamo per rappresentare i numeri. Ma è stato l’avvento degli algoritmi di compressione a cambiare tutto. Negli ultimi decenni, scienziati e ricercatori hanno messo a punto un gran numero di algoritmi di compressione per bitmap. Il loro vantaggio principale è che non è necessario decomprimere le bitmap per eseguire operazioni sui bit: possiamo eseguire operazioni sui bit direttamente sulle bitmap compresse.
Indici bitmap in Go: ricerca a tutta velocità
Recentemente hanno cominciato ad apparire approcci ibridi, come le ruggenti bitmap. Utilizzano contemporaneamente tre diverse rappresentazioni per le bitmap - le bitmap stesse, gli array e le cosiddette bit run - e si bilanciano tra loro per massimizzare le prestazioni e ridurre al minimo il consumo di memoria.

Puoi trovare bitmap ruggenti nelle applicazioni più popolari. Esiste già un numero enorme di implementazioni per un'ampia varietà di linguaggi di programmazione, incluse più di tre implementazioni per Go.
Indici bitmap in Go: ricerca a tutta velocità
Un altro approccio che può aiutarci a gestire la cardinalità elevata è chiamato binning. Immagina di avere un campo che rappresenta l'altezza di una persona. L'altezza è un numero in virgola mobile, ma noi esseri umani non la pensiamo in questo modo. Per noi non c'è differenza tra l'altezza 185,2 cm e 185,3 cm.

Si scopre che possiamo raggruppare valori simili in gruppi entro 1 cm.

E se sappiamo anche che pochissime persone sono più basse di 50 cm e più alte di 250 cm, allora possiamo essenzialmente trasformare un campo con cardinalità infinita in un campo con una cardinalità di circa 200 valori.

Naturalmente, se necessario, possiamo applicare successivamente ulteriori filtri.

Problema di larghezza di banda elevata

Il problema successivo con gli indici bitmap è che aggiornarli può essere molto costoso.

I database devono essere in grado di aggiornare i dati mentre potenzialmente centinaia di altre query stanno cercando i dati. Abbiamo bisogno di blocchi per evitare problemi con l'accesso simultaneo ai dati o altri problemi di condivisione. E dove c'è un grosso blocco, c'è un problema: la contesa tra i blocchi, quando questo blocco diventa un collo di bottiglia.
Indici bitmap in Go: ricerca a tutta velocità
Questo problema può essere risolto o aggirato utilizzando lo sharding o utilizzando indici con versione.

Lo sharding è una cosa semplice e ben nota. Puoi partizionare un indice bitmap come faresti con qualsiasi altro dato. Invece di un blocco grande, otterrai un gruppo di blocchi piccoli e quindi eliminerai la contesa dei blocchi.

Il secondo modo per risolvere il problema è utilizzare indici con versione. Puoi avere una copia dell'indice da utilizzare per la ricerca o la lettura e un'altra da utilizzare per la scrittura o l'aggiornamento. E una volta in un certo periodo di tempo (ad esempio, una volta ogni 100 ms o 500 ms) li duplichi e li scambi. Naturalmente, questo approccio è applicabile solo nei casi in cui l'applicazione è in grado di gestire un indice di ricerca leggermente ritardato.

Questi due approcci possono essere utilizzati contemporaneamente: è possibile avere un indice con versione partizionata.

Query più complesse

L'ultimo problema con gli indici bitmap è che ci viene detto che non sono adatti per tipi di query più complessi, come le query span.

In effetti, se ci pensi, le operazioni di bit come AND, OR, ecc. non sono molto adatte per domande del tipo “Mostrami hotel con tariffe da 200 a 300 dollari a notte”.
Indici bitmap in Go: ricerca a tutta velocità
Una soluzione ingenua e poco saggia sarebbe quella di prendere i risultati per ciascun valore in dollari e combinarli con un'operazione OR bit a bit.
Indici bitmap in Go: ricerca a tutta velocità
Una soluzione leggermente migliore sarebbe utilizzare il raggruppamento. Ad esempio, in gruppi da 50 dollari. Ciò accelererebbe il nostro processo di 50 volte.

Ma il problema è facilmente risolvibile anche utilizzando una vista creata appositamente per questo tipo di richiesta. Negli articoli scientifici si chiama bitmap con codifica di intervallo.
Indici bitmap in Go: ricerca a tutta velocità
In questa rappresentazione, non impostiamo solo un bit per un valore (ad esempio, 200), ma impostiamo questo valore e tutto a un valore più alto. 200 e oltre. Lo stesso per 300: 300 e superiori. E così via.

Utilizzando questa rappresentazione, possiamo rispondere a questo tipo di query di ricerca attraversando l'indice solo due volte. Per prima cosa otterremo un elenco di hotel in cui la camera costa meno o $ 300, quindi rimuoveremo da esso quelli in cui il costo della camera è inferiore o $ 199. Pronto.
Indici bitmap in Go: ricerca a tutta velocità
Rimarrai sorpreso, ma anche le geoquery sono possibili utilizzando gli indici bitmap. Il trucco sta nell'utilizzare una rappresentazione geometrica che circonda le tue coordinate con una figura geometrica. Ad esempio, S2 di Google. La figura dovrebbe essere rappresentabile sotto forma di tre o più linee intersecanti che possono essere numerate. In questo modo possiamo trasformare la nostra geoquery in diverse query “lungo il gap” (lungo queste linee numerate).

Soluzioni pronte

Spero di averti interessato un po' e che ora hai un altro strumento utile nel tuo arsenale. Se mai avessi bisogno di fare qualcosa del genere, saprai da che parte guardare.

Tuttavia, non tutti hanno il tempo, la pazienza o le risorse per creare indici bitmap da zero. Soprattutto quelli più avanzati, che utilizzano SIMD, ad esempio.

Fortunatamente, ci sono diverse soluzioni già pronte per aiutarti.
Indici bitmap in Go: ricerca a tutta velocità

Bitmap ruggenti

Innanzitutto c'è quella stessa fantastica libreria di bitmap di cui ho già parlato. Contiene tutti i contenitori e le operazioni bit necessari per creare un indice bitmap completo.
Indici bitmap in Go: ricerca a tutta velocità
Sfortunatamente, al momento, nessuna delle implementazioni Go utilizza SIMD, il che significa che le implementazioni Go sono meno performanti rispetto, ad esempio, alle implementazioni C.

pilosa

Un altro prodotto che può aiutarti è il DBMS Pilosa, che, infatti, dispone solo di indici bitmap. Si tratta di una soluzione relativamente nuova, ma sta conquistando i cuori a grande velocità.
Indici bitmap in Go: ricerca a tutta velocità
Pilosa utilizza internamente bitmap ruggenti e ti dà la possibilità di usarle, semplifica e spiega tutte le cose di cui ho parlato sopra: raggruppamento, bitmap codificate in intervalli, il concetto di campo, ecc.

Diamo una rapida occhiata a un esempio di utilizzo di Pilosa per rispondere a una domanda con cui hai già familiarità.
Indici bitmap in Go: ricerca a tutta velocità
L'esempio è molto simile a quello che hai visto prima. Creiamo un client sul server Pilosa, creiamo un indice e i campi necessari, quindi riempiamo i nostri campi con dati casuali con probabilità e, infine, eseguiamo la query familiare.

Successivamente utilizziamo NOT sul campo "costoso", quindi intersechiamo il risultato (o AND) con il campo "terrazza" e con il campo "prenotazioni". E infine, otteniamo il risultato finale.
Indici bitmap in Go: ricerca a tutta velocità
Spero davvero che nel prossimo futuro questo nuovo tipo di indice appaia anche nei DBMS come MySQL e PostgreSQL: indici bitmap.
Indici bitmap in Go: ricerca a tutta velocità

conclusione

Indici bitmap in Go: ricerca a tutta velocità
Se non ti sei ancora addormentato, grazie. Ho dovuto toccare brevemente molti argomenti a causa del poco tempo a disposizione, ma spero che il discorso sia stato utile e magari anche motivante.

È utile conoscere gli indici bitmap, anche se non ti servono in questo momento. Lascia che siano un altro strumento nella tua cassetta degli attrezzi.

Abbiamo esaminato vari trucchi prestazionali per Go e cose che il compilatore Go non gestisce ancora molto bene. Ma questo è assolutamente utile per ogni programmatore Go saperlo.

Questo è tutto quello che volevo dirti. Grazie!

Fonte: habr.com

Aggiungi un commento