Bitmap index in Go: searching at wild speed

Bitmap index in Go: searching at wild speed

Introduction

I presented this report in English at the GopherCon Russia 2019 conference in Moscow and in Russian at the meetup in Nizhny Novgorod. It's about the bitmap index, which is less common than the B-tree but equally interesting. I'm sharing the recording of the conference presentation in English and a textual transcript in Russian.

We will explore how the bitmap index is structured, when it is superior and when it is inferior to other indexes, and in what cases it is significantly faster; we will see which popular DBMS already have bitmap indexes; and we'll attempt to write our own in Go. As a ‘dessert’, we’ll use existing libraries to create our super-fast specialized database.

I truly hope that my efforts will be useful and interesting for you. Let’s go!

Introduzione

Riproduci video

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

Hello everyone! It's six in the evening, and we are all super tired. A wonderful time to talk about the boring theory of database indexes, right? Don’t worry, I’ll sprinkle in a few lines of code here and there. 🙂

Jokes aside, the presentation is packed with information, and we don’t have much time. So let’s get started.
Bitmap index in Go: searching at wild speed
Oggi parlerò dei seguenti argomenti:

  • cosa sono gli indici;
  • cosa è un bitmap index;
  • dove viene utilizzato e dove NON viene utilizzato e perché;
  • una semplice implementazione in Go e un po' di lotta con il compilatore;
  • un'implementazione un po' meno semplice, ma molto più performante in Go-assembly;
  • i "problemi" dei bitmap index;
  • implementazioni esistenti.

Allora, cosa sono gli indici?

Bitmap index in Go: searching at wild speed

Un indice è una struttura dati separata che manteniamo e aggiorniamo in aggiunta ai dati principali. Viene utilizzata per accelerare le ricerche. Senza indici, la ricerca richiederebbe un passaggio completo sui dati (un processo chiamato full scan) e questo processo ha una complessità algoritmica lineare. Ma i database contengono generalmente un'enorme quantità di dati e la complessità lineare è troppo lenta. Idealmente, vorremmo avere una complessità logaritmica o costante.

È un argomento enorme e complesso, pieno di sfumature e compromessi, ma guardando a decenni di sviluppo e ricerca su vari database, sono pronto a sostenere che esistono solo pochi approcci ampiamente utilizzati per la creazione di indici nei database.

Bitmap index in Go: searching at wild speed

Il primo approccio consiste nella riduzione gerarchica dell'area di ricerca, suddividendo l'area in parti più piccole.

Di solito lo facciamo utilizzando vari tipi di alberi. Un esempio potrebbe essere una grande scatola con materiali nel tuo armadio, che contiene scatole più piccole con materiali suddivisi per diversi temi. Se hai bisogno di materiali, è probabile che cercherai nella scatola con l'etichetta «Materiali», e non in quella con scritto «Biscotti», giusto?

Bitmap index in Go: searching at wild speed

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

Bitmap index in Go: searching at wild speed

Il terzo approccio è quello di eliminare la necessità di ricerca. Questo lo facciamo tramite Bloom filter o cuckoo filter. I primi forniscono una risposta immediata, liberandoti dalla necessità di effettuare ricerche.

Bitmap index in Go: searching at wild speed

L'approccio finale consiste nell'utilizzo completo di tutte le potenzialità offerte dall'hardware moderno. È esattamente quello che facciamo con gli indici bitmap. Sì, talvolta è necessario scorrere tutto l'indice, ma lo facciamo in modo super efficiente.

Come ho già detto, il tema degli indici del database è vasto e pieno di compromessi. Questo significa che a volte possiamo utilizzare più approcci contemporaneamente: se abbiamo bisogno di accelerare ulteriormente la ricerca o se è necessario coprire tutti i possibili tipi di ricerca.

Oggi parlerò dell'approccio meno conosciuto tra quelli menzionati: gli indici bitmap.

Chi sono io per parlare di questo argomento?

Bitmap index in Go: searching at wild speed

Lavoro come team leader in Badoo (forse conosci meglio un altro nostro prodotto: Bumble). Abbiamo già oltre 400 milioni di utenti in tutto il mondo e molte funzionalità che si occupano di trovare per loro la migliore corrispondenza. Lo facciamo tramite servizi personalizzati che utilizzano anche gli indici bitmap.

Allora, cosa sono gli indici bitmap?

Bitmap index in Go: searching at wild speed
Gli indici bitmap, come suggerisce il nome, utilizzano bitmap o bitset per implementare un indice di ricerca. Da un punto di vista panoramico, questo indice è composto da uno o più di questi bitmap, rappresentanti entità (come persone) e le loro proprietà o parametri (età, colore degli occhi, ecc.), e da un algoritmo che utilizza operazioni bitwise (AND, OR, NOT) per rispondere a una query di ricerca.
Bitmap index in Go: searching at wild speed
Si dice che gli indici bitmap siano i più adatti e molto performanti nei casi in cui ci sia ricerca che combina query su molte colonne con bassa cardinalità (immaginate «colore degli occhi» o «stato civile» rispetto a qualcosa come «distanza dal centro città»). Ma più avanti mostrerò che funzionano egregiamente anche per colonne con alta cardinalità.

Consideriamo un esempio semplice di indice bitmap.
Bitmap index in Go: searching at wild speed
Immaginate di avere un elenco di ristoranti di Mosca con proprietà binarie come queste:

  • vicino alla metropolitana (near metro);
  • con parcheggio privato (has private parking);
  • con terrazza (has terrace);
  • che accetta prenotazioni (accepts reservations);
  • adatto ai vegetariani (vegan friendly);
  • costoso (expensive).

Bitmap index in Go: searching at wild speed
Assegniamo un numero a ogni ristorante a partire da 0 e riserviamo memoria per 6 bitmap (uno per ciascuna caratteristica). Poi popoleremo questi bitmap a seconda che il ristorante abbia o meno tale caratteristica. Se il ristorante 4 ha una veranda, il bit n°4 nel bitmap "ha veranda" sarà impostato su 1 (se non ha veranda, sarà 0).
Bitmap index in Go: searching at wild speed
Ora abbiamo l'indice bitmap più semplice possibile e possiamo utilizzarlo per rispondere a richieste come:

  • "Mostrami i ristoranti adatti ai vegetariani";
  • "Mostrami ristoranti economici con veranda dove si può prenotare un tavolo."

Bitmap index in Go: searching at wild speed
Bitmap index in Go: searching at wild speed
Come? Vediamo. La prima richiesta è molto semplice. Tutto ciò che dobbiamo fare è prendere il bitmap "adatto ai vegetariani" e trasformarlo in un elenco di ristoranti i cui bit sono attivi.
Bitmap index in Go: searching at wild speed
Bitmap index in Go: searching at wild speed
La seconda richiesta è un po' più complessa. Dobbiamo utilizzare l'operazione bitwise NOT sulla bitmap 'costoso' per ottenere un elenco di ristoranti economici, poi eseguire un AND con la bitmap 'prenotazione disponibile' e un altro AND con la bitmap 'con veranda'. La bitmap risultante conterrà un elenco di attività che soddisfano tutti i nostri criteri. In questo esempio, è solo il ristorante 'Gioventù'.
Bitmap index in Go: searching at wild speed
Bitmap index in Go: searching at wild speed
Qui c'è molta teoria, ma non preoccupatevi, vedremo il codice molto presto.

Dove vengono utilizzati gli indici bitmap?

Bitmap index in Go: searching at wild speed
Se fate una ricerca su Google per indici bitmap, il 90% delle risposte sarà in qualche modo collegato a Oracle DB. Ma le altre DBMS probabilmente supportano anche questa funzionalità, giusto? Non esattamente.

Diamo un'occhiata alla lista dei principali sospetti.
Bitmap index in Go: searching at wild speed
MySQL non supporta ancora gli indici bitmap, ma esiste una proposta per aggiungere questa opzione (https://dev.mysql.com/worklog/task/?id=1524).

PostgreSQL non supporta gli indici bitmap, ma utilizza semplici bitmap e operazioni bitwise per unire i risultati della ricerca su altri indici.

Tarantool ha indici bitset e supporta la ricerca semplice su di essi.

Redis ha semplici campi bitari (https://redis.io/commands/bitfield) senza possibilità di ricerca su di essi.

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

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

Bitmap index in Go: searching at wild speed

  • Ma nella nostra casa è arrivato un nuovo vicino: Pilosa. È un nuovo database non relazionale, scritto in Go. Contiene solo indici bitmap e basa tutto su di essi. Ne parleremo più avanti.

Implementazione in Go

Ma perché gli indici bitmap sono così raramente utilizzati? Prima di rispondere a questa domanda, vorrei mostrarvi l'implementazione di un semplice indice bitmap in Go.
Bitmap index in Go: searching at wild speed
I bitmap sono, in sostanza, semplicemente pezzi di dati. In Go, utilizzeremo per questo degli slice di byte.

Abbiamo un bitmap per ogni caratteristica del ristorante, e ogni bit nel bitmap indica se un determinato ristorante ha quella proprietà o meno.
Bitmap index in Go: searching at wild speed
Avremo bisogno di due funzioni ausiliarie. Una sarà utilizzata per popolare i nostri bitmap con dati casuali. Casuali, ma con una certa probabilità che il ristorante possieda ciascuna caratteristica. Ad esempio, considero che a Mosca ci siano molto pochi ristoranti in cui non sia possibile prenotare un tavolo, e mi sembra che circa il 20% degli esercizi si adatti ai vegetariani.

La seconda funzione convertirà il bitmap in un elenco di ristoranti.
Bitmap index in Go: searching at wild speed
Bitmap index in Go: searching at wild speed
Per rispondere alla richiesta 'Mostrami ristoranti economici che hanno una veranda e dove è possibile prenotare un tavolo', avremo bisogno di due operazioni bitwise: NOT e AND.

Possiamo semplificare un po' il nostro codice utilizzando un'operazione più complessa AND NOT.

Abbiamo funzioni per ciascuna di queste operazioni. Entrambe attraversano gli slice, prendono gli elementi corrispondenti da ognuno, li uniscono con un'operazione bitwise e mettono il risultato nello slice finale.
Bitmap index in Go: searching at wild speed
E ora possiamo utilizzare i nostri bitmap e funzioni per rispondere alla richiesta di ricerca.
Bitmap index in Go: searching at wild speed
Le prestazioni non sono così elevate, nonostante le funzioni siano molto semplici e abbiamo risparmiato notevolmente non restituendo un nuovo slice risultante ad ogni chiamata della funzione.

Dopo aver fatto un po' di profiling con pprof, ho notato che il compilatore Go ha trascurato un'ottimizzazione molto semplice, ma fondamentale: l'inlining delle funzioni.
Bitmap index in Go: searching at wild speed
Il fatto è che il compilatore Go ha una pessima paura dei cicli che attraversano gli slice e si rifiuta categoricamente di inlineare funzioni che contengono tali cicli.
Bitmap index in Go: searching at wild speed
Ma io non ho paura e posso ingannare il compilatore utilizzando goto invece di un ciclo, proprio come ai vecchi tempi.

Bitmap index in Go: searching at wild speed
Bitmap index in Go: searching at wild speed

E, come potete vedere, ora il compilatore è felice di inlineare la nostra funzione! Alla fine, riusciamo a risparmiare circa 2 microsecondi. Non male!

Bitmap index in Go: searching at wild speed

Il secondo collo di bottiglia è facilmente visibile se si esamina attentamente l'output dell'assemblatore. Il compilatore ha aggiunto un controllo sui limiti dello slice direttamente all'interno del nostro ciclo più critico. Il punto è che Go è un linguaggio sicuro e il compilatore teme che i miei tre argomenti (tre slice) abbiano dimensioni diverse. Questo comporterebbe la possibilità teorica di un cosiddetto overflow del buffer.

Calmiamo quindi il compilatore, mostrandogli che tutti gli slice hanno la stessa dimensione. Possiamo farlo aggiungendo un semplice controllo all'inizio della nostra funzione.
Bitmap index in Go: searching at wild speed
Vedendo questo, il compilatore passa volentieri il controllo e risparmiamo così altri 500 nanosecondi.

Batch di grandi dimensioni

Ok, siamo riusciti a ottenere qualche prestazione dalla nostra semplice implementazione, ma questo risultato è, in realtà, molto peggiore di quanto potrebbe essere con l'hardware attuale.

Tutto ciò che facciamo sono operazioni bit a bit di base, e i nostri processori le eseguono in modo molto efficiente. Purtroppo, però, 'nutriamo' il nostro processore con pezzi di lavoro molto piccoli. Le nostre funzioni eseguono operazioni byte per byte. Possiamo facilmente ottimizzare il nostro codice affinché lavori con pezzi da 8 byte, utilizzando slice di UInt64.

Bitmap index in Go: searching at wild speed

Come potete vedere, questa piccola modifica ha accelerato il nostro programma di otto volte grazie all'aumento della dimensione del batch di otto volte. I guadagni sono, si potrebbe dire, lineari.

Bitmap index in Go: searching at wild speed

Implementazione in assembler

Bitmap index in Go: searching at wild speed
Ma non è finita qui. I nostri processori possono lavorare con pezzi di 16, 32 e persino 64 byte. Queste 'operazioni larghe' vengono chiamate single instruction multiple data (SIMD; una istruzione, molti dati), e il processo di trasformazione del codice in modo che utilizzi queste operazioni è chiamato vettorializzazione.

Sfortunatamente, il compilatore Go non è affatto un campione nella vettorializzazione. Attualmente, l'unico modo per vettorizzare il codice in Go è prendere e imballare i dati delle operazioni manualmente utilizzando l'assembler Go.

Bitmap index in Go: searching at wild speed

L'assemblatore Go è una bestia strana. Sai sicuramente che un assemblatore è qualcosa di fortemente legato all'architettura del computer per cui scrivi, ma in Go non è così. L'assemblatore Go è più simile a un IRL (linguaggio di rappresentazione intermedia): è praticamente indipendente dalla piattaforma. Rob Pike ha fatto un'ottima relazione presentazione su questo argomento alcuni anni fa al GopherCon di Denver.

In aggiunta, Go utilizza un formato insolito chiamato Plan 9, diverso dai formati comunemente riconosciuti AT&T e Intel.
Bitmap index in Go: searching at wild speed
Si può dire con certezza che scrivere a mano l'assemblatore Go non è l'attività più divertente.

Ma, per fortuna, ci sono già due strumenti di alto livello che ci aiutano a scrivere assemblatore Go: PeachPy e avo. Entruti utilità generano assemblatore Go da codice di livello superiore, scritto rispettivamente in Python e Go.
Bitmap index in Go: searching at wild speed
Questi strumenti semplificano cose come l'allocazione dei registri (scelta dei registri della CPU), la scrittura di cicli e, in generale, rendono più facile entrare nel mondo della programmazione assemblativa in Go.

Useremo avo, quindi i nostri programmi saranno quasi programmi normali in Go.
Bitmap index in Go: searching at wild speed
Ecco un esempio molto semplice di un programma Avo. Abbiamo una funzione main() che definisce al suo interno la funzione Add(), la cui funzione è quella di sommare due numeri. Qui ci sono funzioni ausiliarie per ottenere i parametri per nome e per ottenere uno dei registri di processore liberi e adatti. Ogni operazione del processore ha una corrispondente funzione in Avo, come si può vedere in ADDQ. Infine, vediamo una funzione ausiliaria per salvare il valore risultante.
Bitmap index in Go: searching at wild speed
Chiamando go generate, eseguiremo il programma in Avo e alla fine verranno generati due file:

  • add.s con il codice risultante in Go Assembly;
  • stub.go con le intestazioni delle funzioni per collegare i due mondi: Go e Assembly.

Bitmap index in Go: searching at wild speed
Ora che abbiamo visto cosa fa e come funziona Avo, guardiamo le nostre funzioni. Ho implementato sia le versioni scalari che quelle vettoriali (SIMD) delle funzioni.

Iniziamo con le versioni scalari.
Bitmap index in Go: searching at wild speed
Come nel precedente esempio, chiediamo di fornire un registro generale libero e corretto, non abbiamo bisogno di calcolare offset e dimensioni per gli argomenti. Tutto questo Avo lo fa per noi.
Bitmap index in Go: searching at wild speed
In passato utilizzavamo etichette e salti (o goto) per migliorare le prestazioni e ingannare il compilatore Go, ma ora lo facciamo fin dall'inizio. Il fatto è che i cicli sono un concetto di livello più alto. Nell'assembly abbiamo solo etichette e salti.
Bitmap index in Go: searching at wild speed
Il resto del codice dovrebbe già esserti familiare e comprensibile. Emuliamo un ciclo usando etichette e salti, prendiamo una piccola parte dei dati da due dei nostri slice, uniamo i dati con un'operazione bitwise (AND NOT in questo caso) e poi mettiamo il risultato nel nostro slice di output. Tutto qui.
Bitmap index in Go: searching at wild speed
Ecco come appare il codice finale in assembly. Non è stato necessario calcolare offset e dimensioni (evidenziato in verde) né controllare i registri utilizzati (evidenziato in rosso).
Bitmap index in Go: searching at wild speed
Se confrontiamo le prestazioni dell'implementazione in assembly con quelle della migliore implementazione in Go, vedremo che sono identiche. E questo è prevedibile. Infatti, non abbiamo fatto nulla di speciale: abbiamo semplicemente riprodotto ciò che avrebbe fatto il compilatore Go.

Sfortunatamente, non possiamo costringere il compilatore a inlinare le nostre funzioni scritte in assembly. Al momento, il compilatore Go non ha questa capacità, anche se la richiesta di aggiungerla è in circolazione da tempo.

Ecco perché non è possibile ottenere vantaggi da piccole funzioni in assembly. Dobbiamo scrivere funzioni più grandi, utilizzare il nuovo pacchetto math/bits, oppure evitare l'assembly del tutto.

Ora diamo un'occhiata alle versioni vettoriali delle nostre funzioni.
Bitmap index in Go: searching at wild speed
Per questo esempio ho deciso di applicare AVX2, quindi utilizzeremo operazioni che lavorano con pezzi da 32 byte. La struttura del codice è molto simile a quella della versione scalare: caricamento dei parametri, richiesta di un registro generale libero, e così via.
Bitmap index in Go: searching at wild speed
Una delle novità riguarda il fatto che le operazioni vettoriali più ampie utilizzano registri speciali più larghi. Nel caso di pezzi da 32 byte, si tratta dei registri con il prefisso Y. Ecco perché vedi la funzione YMM() nel codice. Se avessi utilizzato AVX-512 con pezzi da 64 bit, il prefisso sarebbe stato Z.

La seconda novità riguarda la mia decisione di utilizzare un'ottimizzazione chiamata loop unrolling, ovvero eseguire manualmente otto operazioni di ciclo prima di tornare all'inizio del ciclo. Questa ottimizzazione riduce il numero di branch nel codice ed è limitata al numero di registri liberi disponibili.
Bitmap index in Go: searching at wild speed
E per quanto riguarda le prestazioni? Sono fantastiche! Abbiamo ottenuto un'accelerazione di circa sette volte rispetto alla migliore soluzione in Go. Impressive, vero?
Bitmap index in Go: searching at wild speed
Tuttavia, anche questa implementazione potrebbe essere ulteriormente velocizzata utilizzando AVX-512, prefetching o JIT (just-in-time compiler) per il pianificatore delle richieste. Ma questa è senza dubbio una questione per un'altra presentazione.

Problemi degli indici bitmap

Ora che abbiamo esaminato la semplice implementazione dell'indice bitmap in Go e quella molto più performante in assembly, parliamo finalmente del perché gli indici bitmap siano così poco utilizzati.
Bitmap index in Go: searching at wild speed
Nei lavori scientifici più vecchi vengono citati tre problemi degli indici bitmap, ma i lavori scientifici più recenti e io affermiamo che non sono più rilevanti. Non scenderemo troppo nel dettaglio di 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 campi con bassa cardinalità, cioè quelli con pochi valori (ad esempio, sesso o colore degli occhi), e la ragione è che la rappresentazione normale di tali campi (un bit per valore) occuperebbe troppo spazio in caso di alta cardinalità e, inoltre, questi indici bitmap sarebbero scarsamente (raramente) popolati.
Bitmap index in Go: searching at wild speed
Bitmap index in Go: searching at wild speed
A volte possiamo utilizzare un'altra rappresentazione, ad esempio quella standard che usiamo per rappresentare i numeri. Ma è stata l'emergere di algoritmi di compressione a cambiare tutto. Negli ultimi decenni, scienziati e ricercatori hanno ideato un gran numero di algoritmi di compressione per i bitmap. Il loro principale vantaggio è che non è necessario decomprimere i bitmap per eseguire operazioni bit a bit: possiamo effettuare operazioni bit a bit direttamente sui bitmap compressi.
Bitmap index in Go: searching at wild speed
Recentemente, sono emersi approcci ibridi, come ad esempio i roaring bitmaps. Questi utilizzano contemporaneamente tre rappresentazioni diverse per i bitmaps: i veri e propri bitmaps, gli array e i cosiddetti bit runs, bilanciando tra di loro per massimizzare le prestazioni e minimizzare il consumo di memoria.

Puoi incontrare i roaring bitmaps nelle applicazioni più popolari. Esistono già un numero enorme di implementazioni per tanti linguaggi di programmazione, comprese più di tre implementazioni per Go.
Bitmap index in Go: searching at wild speed
Un altro approccio che può aiutarci a gestire l'alta cardinalità è la raggruppamento (binning). Immagina di avere un campo che rappresenta l'altezza di una persona. L'altezza è un numero con la virgola mobile, ma noi, esseri umani, non la vediamo in questo modo. Per noi non c'è differenza tra un'altezza di 185,2 cm e una di 185,3 cm.

Di conseguenza, possiamo raggruppare i valori simili in intervalli di 1 cm.

E se sappiamo che molto poche persone hanno un'altezza inferiore a 50 cm e superiore a 250 cm, possiamo, di fatto, trasformare un campo con cardinalità infinita in un campo con circa 200 valori.

Certo, se necessario, possiamo effettuare ulteriori filtri anche in un secondo momento.

Problema di ampia larghezza di banda

Il problema successivo degli indici bitmap è che il loro aggiornamento può essere molto costoso.

I database devono consentire l'aggiornamento dei dati nel momento in cui potenzialmente centinaia di altre richieste stanno cercando quei dati. Abbiamo bisogno di lock per evitare problemi di accesso concorrente o altri problemi di condivisione. E dove c'è un grande lock, c'è un problema — il lock contention, quando quel lock diventa un collo di bottiglia.
Bitmap index in Go: searching at wild speed
Questo problema può essere risolto o aggirato utilizzando lo sharding o indici versionati.

Lo sharding è un concetto semplice e ben noto. Puoi shardingare un indice bitmap proprio come faresti con qualsiasi altro dato. Invece di un grande lock, avrai un mucchio di piccoli lock e in questo modo eliminerai il lock contention.

Un secondo modo per risolvere il problema è utilizzare indici versionati. Puoi avere una copia dell'indice che usi per cercare o leggere, e una per scrivere o aggiornare. E a intervalli di tempo prestabiliti (ad esempio, ogni 100 ms o 500 ms) li duplichi e li scambi. Naturalmente, questo approccio è applicabile solo quando la tua applicazione può lavorare con un indice di ricerca leggermente obsoleto.

Questi due approcci possono essere utilizzati contemporaneamente: puoi avere un indice versionato sharded.

Richieste più complesse

L'ultimo problema degli indici bitmap è che, come ci dicono, non sono adatti per tipologie di richieste più complesse, come le richieste 'per intervallo'.

E in effetti, se ci pensi, le operazioni bit a bit come AND, OR ecc. non sono molto adatte per richieste del tipo 'Mostrami gli hotel con tariffe comprese tra 200 e 300 dollari a notte'.
Bitmap index in Go: searching at wild speed
Una soluzione semplice e molto poco saggia sarebbe quella di prendere i risultati per ogni valore in dollari e combinarli con l'operazione bit a bit OR.
Bitmap index in Go: searching at wild speed
Una soluzione leggermente più corretta sarebbe stata quella di utilizzare il raggruppamento. Ad esempio, in gruppi da 50 dollari. Questo accelererebbe il nostro processo di 50 volte.

Ma il problema può essere facilmente risolto utilizzando una rappresentazione creata appositamente per questo tipo di query. Nelle pubblicazioni scientifiche viene chiamata bitmap a intervallo codificato.
Bitmap index in Go: searching at wild speed
In questa rappresentazione, non assegniamo semplicemente un bit a un valore specifico (ad esempio, 200), ma segnaliamo quel valore e tutto ciò che viene dopo. 200 e oltre. Lo stesso per 300: 300 e oltre. E così via.

Utilizzando questa rappresentazione, possiamo rispondere a questo tipo di query di ricerca passando attraverso l'indice solo due volte. Prima otteniamo un elenco di hotel dove il prezzo è inferiore a 300 dollari, e poi eliminiamo quelli dove il prezzo è inferiore a 199 dollari. Fatto.
Bitmap index in Go: searching at wild speed
Potreste rimanere sorpresi, ma anche le query geografiche sono possibili utilizzando gli indici bitmap. Il trucco consiste nell'utilizzare una rappresentazione geospaziale che circonda le vostre coordinate con una figura geometrica. Ad esempio, S2 di Google. La figura dovrebbe essere rappresentabile tramite tre o più linee incrociate, che possono essere numerate. In questo modo, possiamo trasformare la nostra query geospaziale in più richieste "per intervallo" (su queste linee numerate).

Soluzioni pronte

Spero di aver suscitato un po' il vostro interesse e che ora abbiate uno strumento utile nel vostro arsenale. Se mai un giorno vi servirà fare qualcosa di simile, saprete dove guardare.

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

Fortunatamente, ci sono alcune soluzioni pronte che possono aiutarvi.
Bitmap index in Go: searching at wild speed

Bitmap Roaring

Innanzitutto, c'è quella famosissima libreria roaring bitmaps di cui ho già parlato. Essa include tutti i contenitori e le operazioni sui bit necessari per creare un indice bitmap completo.
Bitmap index in Go: searching at wild speed
Sfortunatamente, al momento nessuna delle implementazioni in Go utilizza SIMD, il che significa che le implementazioni in Go sono meno performanti rispetto a quelle in C, ad esempio.

Pilosa

Un altro prodotto che può esserti utile è il database Pilosa, che sostanzialmente si basa esclusivamente su indici bitmap. È una soluzione relativamente nuova, ma sta rapidamente conquistando il mercato.
Bitmap index in Go: searching at wild speed
Pilosa utilizza bitmap roaring al suo interno e ti permette di utilizzarli, semplificando e chiarendo tutte quelle cose di cui ho parlato sopra: raggruppamento, bitmap codificati in intervalli, concetto di campo, e così via.

Diamo rapidamente un'occhiata a un esempio di utilizzo di Pilosa per rispondere a una domanda che già conosci.
Bitmap index in Go: searching at wild speed
L'esempio è molto simile a quello che hai visto in precedenza. Creiamo un client per il server Pilosa, creiamo un indice e i campi necessari, quindi popolare i nostri campi con dati casuali secondo probabilità e, infine, eseguiamo una richiesta familiare.

Dopo di ciò, utilizziamo NOT sul campo "expensive", quindi incrociamo il risultato (o facciamo un AND) con il campo "terrace" e con il campo "reservations". Infine, otteniamo il risultato finale.
Bitmap index in Go: searching at wild speed
Spero davvero che in un futuro prossimo anche nei database come MySQL e PostgreSQL arrivi questo nuovo tipo di indici: gli indici bitmap.
Bitmap index in Go: searching at wild speed

Conclusione

Bitmap index in Go: searching at wild speed
Se non vi siete ancora addormentati, grazie. Ho dovuto toccare molti argomenti per motivi di tempo limitato, ma spero che la presentazione sia stata utile e forse anche motivante.

È bene conoscere gli indici bitmap, anche se al momento non vi servono. Sono un ulteriore strumento nella vostra cassetta degli attrezzi.

Abbiamo esaminato vari trucchi per migliorare le prestazioni in Go e le cose con cui il compilatore Go non riesce ancora a gestire bene. Questa è sicuramente una conoscenza utile per ogni programmatore Go.

Questo è tutto ciò che volevo condividere. Grazie!

Fonte: habr.com

Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server 🔥 Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server | ProHoster