Cassandra Indexing: The Good, The Bad and the Ugly

All’interno di NoSQL, le operazioni di indicizzazione, recupero e ricerca di informazioni sono intimamente legate ai meccanismi di archiviazione fisica. È importante ricordare che le righe sono memorizzate tra gli host, ma una singola riga è memorizzata su un singolo host. (con repliche) Le famiglie di colonne sono memorizzate in ordine ordinato, il che rende efficiente l’interrogazione di un set di colonne (a condizione che si estendano le righe).

Il cattivo : Partizionamento

Una delle cose difficili a cui abituarsi all’inizio è che senza alcun indice le query che si estendono su righe possono (molto) essere cattive. Ripensando al nostro modello di archiviazione, tuttavia, ciò non sorprende. La strategia che Cassandra utilizza per distribuire le righe tra gli host è chiamata Partizionamento.

Il partizionamento è l’atto di ritagliare l’intervallo di rowkey assegnandoli nel “token ring”, che assegna anche la responsabilità di un segmento (cioè partizione) dell’intervallo rowkey a ciascun host. Probabilmente l’hai visto quando hai inizializzato il tuo cluster con un “token”. Il token fornisce all’host una posizione lungo il token ring, che assegna la responsabilità per una sezione dell’intervallo di token. Il partizionamento è l’atto di mappare la rowkey nell’intervallo token.

Ci sono due partizionatori primari: Random e Order Preserving. Essi sono opportunamente nominati. Il RandomPartitioner hash i rowkeys in token. Con il RandomPartitioner, il token è un hash della rowkey. Questo fa un buon lavoro di distribuire uniformemente i dati su un insieme di nodi, ma rende incredibilmente difficile interrogare un intervallo dello spazio rowkey. Da solo un valore “start rowkey” e un valore “end rowkey”, Cassandra non può determinare quale intervallo dello spazio token è necessario. Essenzialmente deve eseguire una ” scansione della tabella “per rispondere alla query e una” scansione della tabella ” in Cassandra è errata perché deve andare su ogni macchina (molto probabilmente TUTTE le macchine se si dispone di una buona funzione hash) per rispondere alla query.

Ora, al grande costo della distribuzione uniforme dei dati, è possibile utilizzare OrderPreservingPartitioner (OPP). Sono* non * giù con OPP. L’OPP conserva l’ordine in quanto traduce rowkeys in token. Ora, dato un valore rowkey iniziale e un valore rowkey finale, Cassandra * può * determinare esattamente quali host hanno i dati che stai cercando. Calcola il valore iniziale in un token il valore finale in un token e seleziona e restituisce semplicemente tutto il resto. MA, preservando l’ordine, a meno che i tuoi rowkey non siano distribuiti uniformemente nello spazio, i tuoi token non lo saranno e otterrai un cluster sbilenco, che aumenta notevolmente il costo di configurazione e amministrazione del cluster. (non ne vale la pena)

The Good: Secondary Indexes

Cassandra fornisce un meccanismo di indicizzazione nativo negli indici secondari. Gli indici secondari funzionano fuori dei valori delle colonne. Si dichiara un indice secondario su una famiglia di colonne. Datastax ha una buona documentazione sull’utilizzo. Sotto il cofano, Cassandra mantiene una” famiglia di colonne nascoste ” come indice. (Vedere la presentazione di Ed Anuff per le specifiche) Poiché Cassandra non mantiene le informazioni sul valore della colonna in nessun nodo e gli indici secondari sono sul valore delle colonne (piuttosto che sulle chiavi di riga), una query deve ancora essere inviata a tutti i nodi. Inoltre, gli indici secondari non sono raccomandati per i set di alta cardinalità. Non ho ancora guardato, ma presumo che questo sia dovuto al modello di dati utilizzato all’interno della “famiglia di colonne nascoste”. Se la famiglia di colonne nascoste memorizza una riga per valore univoco (con rowkeys come colonne), significherebbe scansionare le righe per determinare se si trovano all’interno dell’intervallo nella query.
Dalla presentazione di Ed:

  • Non raccomandato per valori di cardinalità elevati(ad esempio timestamp,date di nascita,parole chiave,ecc.)
  • Richiede almeno un confronto di uguaglianza in una query–non di minore/maggiore/range query
  • Unsorted – i risultati sono in token ordine, non al valore di query ordine
  • Limited ricerca sui tipi di dati, Cassandra nativamente capisce

Con tutto ciò detto, gli indici secondari out of the box e abbiamo avuto un buon successo al loro utilizzo su valori semplici.

Il brutto: Fai da te (fai da te) / Righe larghe

Ora, la bellezza è negli occhi di chi guarda. Una delle cose belle di NoSQL è la semplicità. I costrutti sono semplici: spazi delle chiavi, famiglie di colonne, righe e colonne. Mantenerlo semplice tuttavia significa a volte è necessario prendere le cose nelle proprie mani.

Questo è il caso degli indici a righe larghe. Utilizzando il modello di archiviazione di Cassandra, è facile creare i propri indici in cui ogni chiave di riga diventa una colonna nell’indice. Questo a volte è difficile da capire, ma immaginiamo di avere un caso in cui vogliamo selezionare tutti gli utenti in un codice postale. La famiglia di colonne degli utenti principali è digitata su userid, il codice postale è una colonna su ogni riga utente. Potremmo usare indici secondari, ma ci sono alcuni codici di avviamento postale. Invece potremmo mantenere una famiglia di colonne con una singola riga chiamata “idx_zipcode”. Potremmo quindi scrivere colonne in questa riga del modulo “zipcode_userid”. Poiché le colonne sono memorizzate in ordine ordinato, è veloce interrogare tutte le colonne che iniziano con “18964” (ad esempio, potremmo usare 18964_ e 18964_ZZZZZZ come valori iniziali e finali).

Uno svantaggio ovvio di questo approccio è che le righe sono autonome su un host. (ancora una volta ad eccezione delle repliche) Ciò significa che tutte le query colpiranno un singolo nodo. Non ho ancora trovato una buona risposta per questo.

Inoltre, e IMHO, la parte più brutta dell’indicizzazione a file larghe fai-da-te è dal punto di vista del cliente. Nella nostra implementazione, abbiamo fatto del nostro meglio per essere agnostici del linguaggio sul lato client, consentendo alle persone di scegliere lo strumento migliore per il lavoro per interagire con i dati in Cassandra. Con questa mentalità, gli indici fai da te presentano qualche problema. Le righe larghe usano spesso chiavi composite (immagina se avessi un idx_state_zip, che ti permetterebbe di interrogare per stato e quindi zip). Sebbene esista un supporto “nativo” per le chiavi composite, tutte le librerie client implementano la propria versione di esse (Hector, Astyanax e Thrift). Ciò significa che il client che deve interrogare i dati deve avere la logica aggiunta per interrogare prima l’indice e inoltre tutti i client devono costruire la chiave composita nello stesso modo.

Per migliorarlo…

Proprio per questo motivo, abbiamo deciso di rilasciare due progetti open source che aiutano a spingere questa logica sul lato server. Il primo progetto è Cassandra-Trigger. Ciò consente di associare attività asincrone alle scritture in Cassandra. (una di queste attività potrebbe essere l’indicizzazione) Abbiamo anche rilasciato l’indicizzazione di Cassandra. Supporta solo UT8Types nell’indice), ma l’intento è quello di fornire un meccanismo generico lato server che indicizzi i dati come scritti su Cassandra. Utilizzando la stessa tecnica lato server che abbiamo usato nell’indicizzazione Cassandra, è sufficiente configurare le colonne che si desidera indicizzare e il codice AOP fa il resto mentre si scrive sul CF di destinazione. Come sempre, domande, commenti e pensieri sono i benvenuti. (soprattutto se sono fuori base da qualche parte)

Lascia un commento

Il tuo indirizzo email non sarà pubblicato.