Contesto
Questa domanda riguarda i dettagli di implementazione di basso livello degli indici in entrambi i sistemi di database SQL e NoSQL. La struttura effettiva dell'indice (albero B +, hash, SSTable, ecc.) È irrilevante in quanto la domanda riguarda specificamente le chiavi memorizzate all'interno di un singolo nodo di una di queste implementazioni.
sfondo
Nei database SQL (ad es. MySQL) e NoSQL (CouchDB, MongoDB, ecc.), Quando si crea un indice su una colonna o su un campo di dati del documento JSON, ciò che si sta effettivamente facendo fare al database è creare essenzialmente un elenco ordinato di tutti quei valori insieme a un offset di file nel file di dati principale in cui risiede il record relativo a quel valore.
(Per semplicità, potrei allontanare a mano altri dettagli esoterici di impls specifici)
Esempio SQL classico semplice
Consideriamo una tabella SQL standard che ha una semplice chiave primaria int a 32 bit su cui creiamo un indice, finiremo con un indice su disco delle chiavi intere ordinate e associate con un offset a 64 bit nel file di dati in cui il disco vive, ad esempio:
id | offset
--------------
1 | 1375
2 | 1413
3 | 1786
La rappresentazione su disco delle chiavi nell'indice è simile alla seguente:
[4-bytes][8-bytes] --> 12 bytes for each indexed value
Attenendosi alle regole empiriche standard sull'ottimizzazione dell'I / O del disco con filesystem e sistemi di database, diciamo che memorizzi le chiavi in blocchi 4KB su disco, il che significa:
4096 bytes / 12 bytes per key = 341 keys per block
Ignorando la struttura generale dell'indice (albero B +, hash, elenco ordinato, ecc.) Leggiamo e scriviamo blocchi di 341 chiavi alla volta in memoria e torniamo sul disco quando necessario.
Query di esempio
Usando le informazioni della sezione precedente, supponiamo che arrivi una query per "id = 2", la classica ricerca dell'indice DB procede come segue:
- Leggi la radice dell'indice (in questo caso, 1 blocco)
- Ricerca binaria nel blocco ordinato per trovare la chiave
- Ottieni l'offset del file di dati dal valore
- Cerca il record nel file di dati usando l'offset
- Restituire i dati al chiamante
Impostazione della domanda ...
Ok, ecco dove si pone la domanda ...
Il passaggio n. 2 è la parte più importante che consente l'esecuzione di queste query in tempo O (logn) ... le informazioni devono essere ordinate, MA devi essere in grado di attraversare l'elenco in modo rapido ... altro in particolare, devi essere in grado di saltare a offset ben definiti a piacimento per leggere il valore della chiave di indice in quella posizione.
Dopo aver letto nel blocco, devi essere in grado di saltare immediatamente alla 170a posizione, leggere il valore chiave e vedere se quello che stai cercando è GT o LT quella posizione (e così via e così via ...)
L'unico modo in cui potresti saltare i dati nel blocco in questo modo è se le dimensioni dei valori chiave fossero tutte ben definite, come il nostro esempio sopra (4 byte quindi 8 byte per chiave).
DOMANDA
Ok, quindi qui è dove mi sto bloccando con una progettazione dell'indice efficiente ... per colonne varchar nei database SQL o, più specificamente, campi in formato totalmente libero in database di documenti come CouchDB o NoSQL, dove qualsiasi campo che si desidera indicizzare può essere qualsiasi lunghezza come si implementano i valori chiave che si trovano all'interno dei blocchi della struttura dell'indice da cui si costruiscono gli indici?
Ad esempio, supponiamo che utilizzi un contatore sequenziale per un ID in CouchDB e che stai indicizzando i tweet ... avrai valori che vanno da "1" a "100.000.000.000" dopo alcuni mesi.
Diciamo che costruisci l'indice sul database il primo giorno, quando ci sono solo 4 tweet nel database, CouchDB potrebbe essere tentato di usare il seguente costrutto per i valori chiave all'interno dei blocchi di indice:
[1-byte][8-bytes] <-- 9 bytes
4096 / 9 = 455 keys per block
Ad un certo punto questo si interrompe e è necessario un numero variabile di byte per memorizzare il valore chiave negli indici.
Il punto è ancora più evidente se decidi di indicizzare un campo di lunghezza variabile come un "tweet_message" o qualcosa del genere.
Dato che la chiave stessa ha una lunghezza totalmente variabile e che il database non ha modo di indovinare in modo intelligente una "dimensione massima della chiave" quando l'indice viene creato e aggiornato, come vengono effettivamente archiviate queste chiavi all'interno dei blocchi che rappresentano segmenti degli indici in questi database ?
Ovviamente se le tue chiavi sono di dimensioni variabili e leggi in un blocco di chiavi, non solo non hai idea di quante chiavi siano effettivamente nel blocco, ma non hai idea di come saltare in mezzo all'elenco per fare un binario cercali.
È qui che mi fanno inciampare.
Con i campi di tipo statico nei classici database SQL (come bool, int, char, ecc.), Capisco che l'indice può semplicemente pre-definire la lunghezza della chiave e attenersi ad essa ... ma in questo mondo di archivi di dati di documenti, sono perplesso su come stanno modellando efficacemente questi dati su disco in modo tale che possano ancora essere scansionati in tempo O (logn) e apprezzerebbero qualsiasi chiarimento qui.
Per favore fatemi sapere se sono necessari chiarimenti!
Aggiornamento (risposta di Greg)
Si prega di vedere i miei commenti allegati alla risposta di Greg. Dopo una settimana di ricerche in più, penso che si sia davvero imbattuto in un suggerimento meravigliosamente semplice e performante che in pratica è estremamente facile da implementare e utilizzare, fornendo grandi vittorie nell'evitare la deserializzazione dei valori chiave che non ti interessano.
Ho esaminato 3 implementazioni DBMS separate (CouchDB, kivaloo e InnoDB) e tutte gestiscono questo problema deserializzando l'intero blocco nella struttura interna dei dati prima di cercare i valori all'interno del loro ambiente di esecuzione (erlang / C).
Questo è ciò che penso sia così geniale nel suggerimento di Greg; una normale dimensione del blocco di 2048 avrebbe normalmente 50 o meno offset, risultando in un blocco di numeri molto piccolo che dovrebbe essere letto.
Aggiornamento (potenziali svantaggi del suggerimento di Greg)
Per continuare al meglio questo dialogo con me stesso, ho realizzato i seguenti svantaggi di questo ...
Se ogni "blocco" è diretto con dati di offset, non è possibile consentire che le dimensioni del blocco vengano modificate nella configurazione in un secondo momento lungo la strada poiché si potrebbe finire per leggere i dati che non sono iniziati correttamente con un'intestazione o un blocco che conteneva più intestazioni.
Se stai indicizzando valori chiave enormi (supponi che qualcuno stia cercando di indicizzare una colonna di carattere (8192) o BLOB (8192)), è possibile che le chiavi non si adattino in un singolo blocco e debbano essere traboccate su due blocchi affiancati . Ciò significa che il tuo primo blocco dovrebbe avere un'intestazione offset e il secondo blocco inizierà immediatamente con i dati chiave.
La soluzione a tutto questo è avere una dimensione di blocco del database fissa che non è regolabile e sviluppare strutture di dati di blocchi di intestazione attorno ad esso ... ad esempio, si fissano tutte le dimensioni di blocco su 4KB (in genere il più ottimale comunque) e si scrive un valore molto piccolo intestazione di blocco che include il "tipo di blocco" all'inizio. Se è un blocco normale, immediatamente dopo l'intestazione del blocco dovrebbe essere l'intestazione degli offset. Se si tratta di un tipo "overflow", immediatamente dopo l'intestazione del blocco sono dati chiave grezzi.
Aggiornamento (potenziale eccezionale)
Dopo che il blocco viene letto come una serie di byte e gli offset decodificati; tecnicamente potresti semplicemente codificare la chiave che stai cercando in byte grezzi e quindi fare confronti diretti sul flusso di byte.
Una volta trovata la chiave che stai cercando, il puntatore può essere decodificato e seguito.
Un altro fantastico effetto collaterale dell'idea di Greg! Il potenziale per l'ottimizzazione del tempo della CPU qui è abbastanza grande che l'impostazione di una dimensione di blocco fissa potrebbe valere la pena solo per ottenere tutto questo.