Gestione dei documenti di progettazione
L'archivio scalabile di dati JSON per IBM Cloudant dispone di diversi meccanismi di interrogazione, tutti generano indici che vengono creati e mantenuti separatamente dai dati principali.
Articolo contribuito da Glynn Bird, Developer Advocate di IBM Cloudant, glynn@cloudant.com.
L'indicizzazione non viene eseguita immediatamente quando un documento viene salvato. Invece, l'indicizzazione viene programmata in un secondo momento, fornendo un throughput di scrittura più veloce e di scrittura non bloccante.
- MapReduce le viste sono indici nel set di dati con coppie chiave-valore che vengono memorizzate in un BTree per un recupero efficiente in base alla chiave o all'intervallo di chiavi.
- Gli indici di ricerca sono costruiti utilizzando Apache Lucene per consentire la ricerca a testo libero, il faceting e complesse query ad hoc.
Le viste degli indici di ricerca e MapReduce di IBM® Cloudant® for IBM Cloud®vengono configurate aggiungendo documenti di progetto a un database. I documenti di progettazione sono documenti JSON che contengono le istruzioni su come deve essere costruita la vista o l'indice. Facciamo un semplice esempio. Si supponga di avere una semplice raccolta di documenti di dati, simile al seguente esempio.
Si veda un esempio di un semplice documento di dati:
{
"_id": "23966717-5A6F-E581-AF79-BB55D6BBB613",
"_rev": "1-96daf2e7c7c0c277d0a63c49b57919bc",
"doc_name": "Markdown Reference",
"body": "Lorem Ipsum",
"ts": 1422358827
}
Ogni documento di dati include un nome, un corpo e una data/ora. Si crea una vista MapReduce per ordinare i propri documenti in base alla data/ora.
È possibile ordinare i documenti per data/ora creando una funzione di associazione.
Vedere una funzione di mappa di esempio che restituisce un campo data / ora del documento, se presente:
function(doc) {
if (doc.ts) {
emit( doc.ts, null);
}
}
La funzione emette la data / ora del documento in modo che sia possibile utilizzarla come chiave dell'indice. Poiché non siamo interessati al valore nell'indice,
null viene emesso. L'effetto è quello di fornire un indice ordinato per ora nella serie di documenti.
Chiameremo questa vista by_ts e la inseriremo in un documento di progettazione denominato fetch.
Si veda un esempio di documento di progettazione che definisce una vista utilizzando una funzione mappa:
{
"_id": "_design/fetch",
"views": {
"by_ts": {
"map": "function(doc) {
if (doc.ts) {
emit( doc.ts, null);
}
}"
}
},
"language": "javascript"
}
Il risultato è che il codice della mappa viene trasformato in una stringa compatibile con JSON, e incluso in un documento di progettazione.
Una volta salvato il documento di progettazione, IBM Cloudant attiva i processi lato server per creare la vista fetch/by_ts. Crea questa vista iterando su ogni documento del database, e inviando ciascuno di essi alla funzione JavaScript
map. La funzione restituisce la coppia emessa key-value. Man mano che l'iterazione prosegue, ogni coppia key-value viene memorizzata in un indice B-Tree. Dopo la creazione dell'indice per la prima volta, la successiva
reindicizzazione viene eseguita solo per i documenti nuovi e aggiornati. I documenti eliminati vengono deindicizzati. Questo processo di risparmio di tempo è noto come incrementale MapReduce, come mostrato nel diagramma seguente:
Vale la pena ricordare i punti seguenti:
- La costruzione di un indice avviene in modo asincrono: IBM Cloudant conferma che il documento di progetto è stato salvato. Per controllare l'avanzamento della costruzione dell'indice, devi eseguire il polling dell'endpoint
_active_tasksdi IBM Cloudant. - Più dati sono presenti, più tempo ci vorrà prima che l'indice sia pronto.
- Mentre è in corso la creazione dell'indice iniziale, le query effettuate sull'indice sono bloccate.
- L'interrogazione di una vista attiva la "mappatura" di tutti i documenti che non sono indicizzati in modo incrementale. Questa pratica garantisce una visione aggiornata dei dati. Si veda la seguente discussione sui parametri di
staleper le eccezioni a questa regola.
Più viste nello stesso documento di progettazione
Se si definiscono più viste nello stesso documento di progettazione, vengono costruite in modo efficiente nello stesso momento. Ogni documento viene letto una sola volta, e viene passato attraverso la funzione Map di ogni vista. Se si utilizza questo approccio, tenere presente che la modifica di un documento di progettazione invalida tutte le viste MapReduce esistenti definite nel documento. Questo processo invalida le viste MapReduce anche se alcune delle viste rimangono inalterate.
Se le viste MapReduce devono essere modificate indipendentemente l'una dall'altra, inserisci le loro definizioni in documenti di progettazione separati.
Questo comportamento non si applica agli indici di ricerca Lucene. Questi possono essere modificati all'interno dello stesso documento di progettazione senza invalidare altri indici non modificati nello stesso documento.
Gestione delle modifiche a un documento di progettazione
Immaginiamo che a un certo punto del futuro si decida di cambiare il design della vista. Ora, invece di restituire il risultato effettivo del timestamp, siamo interessati solo al conteggio di quanti documenti corrispondono ai criteri. Per ottenere
questo conteggio, la funzione mappa rimane la stessa, ma ora si utilizza un reduce di _count.
Si veda un esempio di documento di progettazione che utilizza una funzione reduce:
{
"_id": "_design/fetch",
"_rev": "2-a2324c9e74a76d2a16179c56f5315dba",
"views": {
"by_ts": {
"map": "function(doc) {
if (doc.ts) {
emit( doc.ts, null);
}
}
}",
"reduce": "_count"
},
"language": "javascript"
}
Quando questo documento di progettazione viene salvato, IBM Cloudant invalida completamente il vecchio indice e inizia a costruire il nuovo indice da zero, iterando su ogni documento a turno. Come per la build originale, il tempo necessario dipende dal numero di documenti presenti nel database. La build inoltre blocca le query in entrata su tale vista fino al completamento.
Ma c'è un problema...
Se si dispone di un'applicazione che accede a questa vista in tempo reale, è possibile che si verifichi un dilemma di distribuzione:
- Versione 1 del codice, che si basava sul documento di progettazione originale, potrebbe non funzionare più perché la vecchia vista è stata invalidata.
- La versione 2 del codice utilizza il nuovo documento di progettazione. Questa versione non può essere rilasciata immediatamente perché la nuova vista non ha ancora terminato la creazione. Tenere presente che il processo di creazione impiega più tempo se il database include molti documenti.
- Un problema più sottile che influisce sul codice è che le versioni 1 e 2 prevedono dati di risultato differenti dalla vista: La versione 1 prevede un elenco di documenti corrispondenti, mentre la versione 2 prevede un numero 'ridotto' di risultati.
Coordinamento delle modifiche ai documenti di progettazione
È possibile gestire questo problema di controllo della modifica in due modi.
Documenti di progettazione forniti di versione
Una soluzione consiste nell'utilizzare nomi di documenti di progettazione forniti di versione:
- Il codice è inizialmente scritto per utilizzare una vista chiamata
_design/fetchv1. - Quando si rilascia una nuova versione, si crea una nuova vista denominata
_design/fetchv2e si esegue una query della vista per assicurarsi che venga creata. - IBM Cloudant
_active_tasksfino al completamento del lavoro di costruzione del nuovo indice. - Ora si è pronti a rilasciare il codice che dipende dalla seconda vista.
- Elimina
_design/fetchv1quando siamo sicuri che non è più necessario.
L'uso di documenti di progetto con versioni è un modo semplice per gestire il controllo delle modifiche nei documenti di progetto, ma è necessario ricordarsi di rimuovere le versioni precedenti in un secondo momento.
Move and switch Documenti di progettazione
Un altro approccio si basa sul fatto che IBM Cloudant riconosce quando ha due documenti di progettazione identici, e non spreca tempo e risorse per ricostruire le viste che ha già. In altre parole, se si prende il documento di progettazione
_design/fetch e si crea un duplicato esatto _design/fetch_OLD, entrambi gli endpoint funzionerebbero in modo intercambiabile senza attivare alcuna reindicizzazione.
Per passare alla nuova vista, effettuare le seguenti operazioni:
- Creare un duplicato del documento di progettazione che si desidera modificare, ad esempio aggiungendo
_OLDal suo nome:_design/fetch_OLD. - Inserire il documento di progettazione nuovo o "in arrivo" nel database utilizzando un nome con il suffisso
_NEW:_design/fetch_NEW. - Interrogare la vista
fetch_NEWper assicurarsi che inizi a costruire. - Eseguire il polling dell'endpoint
_active_taskse attendere che l'indice finisca di essere costruito. - Inserisci una copia duplicata del nuovo documento di progettazione in
_design/fetch. - Elimina il documento di progettazione
_design/fetch_NEW. - Elimina il documento di progettazione
_design/fetch_OLD.
Move and switch utensili
Lo script della riga di comando, Node.js, couchmigrate automatizza la procedura Move and switch. Può essere installato utilizzando il seguente comando:
npm install -g couchmigrate
Per utilizzare lo script couchmigrate, definire prima l'istanza URL di CouchDB/{{site. data.keyword.cloudant_short_notm }} impostando una variabile d'ambiente chiamata COUCH_URL. Eseguire il seguente comando per definire
il sito URL per l'istanza IBM Cloudant:
export COUCH_URL=https://127.0.0.1:5984
L'indirizzo URL deve iniziare con https:// e può includere le credenziali di autenticazione. Eseguire il seguente comando per definire il sito URL dell'istanza IBM Cloudant con le credenziali di autenticazione:
export COUCH_URL="https://$ACCOUNT:$PASSWORD@$HOST.cloudant.com"
Se si presume che si disponga di un documento di progetto in formato JSON, memorizzato in un file, è possibile eseguire il comando di migrazione.
In questo esempio,
db specifica il nome del database da modificare, e dd specifica il percorso del file del documento di progettazione. Eseguire il comando couchmigrate:
couchmigrate --db mydb --dd /path/to/my/dd.json
Lo script coordina la procedura Move and switch, aspettando che la vista sia costruita prima di tornare. Se il documento di progettazione in entrata è uguale a quello precedente, allora lo script restituisce immediatamente il risultato.
Il codice sorgente dello script è disponibile qui:
couchmigrate.
Parametro 'stale'
Se un indice è completo ma nel database vengono aggiunti nuovi record, l'indice viene programmato per l'aggiornamento in background. Lo stato del database è mostrato nel diagramma seguente:
Quando si esegue la query della vista, sono disponibili le seguenti opzioni.
- Il comportamento predefinito è quello di garantire che l'indice sia aggiornato, con gli ultimi documenti presenti nel database, prima di restituire la risposta. Quando si esegue la query della vista, IBM Cloudant prima indicizza i 250 nuovi documenti e poi restituisce la risposta.
- Un'alternativa consiste nell'aggiungere il parametro
stale=okalla chiamata API. Questo parametro significa,return me the data that is already indexed. I don't care about the latest updates.In altre parole, quando si interroga la vista constale=ok, IBM Cloudant restituisce la risposta immediatamente, senza alcuna reindicizzazione aggiuntiva. - Una seconda alternativa è quella di aggiungere il parametro
stale=update_afteralla chiamata API. Questo parametro significa,return me the data that is already indexed, and then reindex any new documents.In altre parole, quando si interroga la vista constale=update_after, IBM Cloudant restituisce la risposta immediatamente e pianifica quindi un'attività in background per indicizzare i nuovi dati.
L'aggiunta di stale=ok o stale=update_after può essere un buon modo per ottenere risposte più rapidamente da una vista, ma a scapito della freschezza.
Il comportamento predefinito distribuisce il carico in modo uniforme tra i nodi del cluster IBM Cloudant. Se si utilizzano le opzioni alternative stale=ok o stale=update_after, queste opzioni potrebbero favorire un
sottoinsieme di nodi del cluster per restituire risultati coerenti da tutto l'insieme coerente. Il parametro stale non è una soluzione perfetta per tutti i casi di utilizzo. Tuttavia, può fornire risposte tempestive su set di
dati in rapida evoluzione se l'applicazione accetta risultati non aggiornati. Se la velocità di modifica dei dati è piccola, l'aggiunta di stale=ok o stale=update_after non apporta un vantaggio sulle prestazioni e
potrebbe distribuire in modo non uniforme il carico su cluster più grandi.
Evitare stale=ok o stale=update_after quando possibile perché il comportamento predefinito fornisce i dati più aggiornati e distribuisce i dati all'interno del cluster. Puoi rendere un'applicazione client consapevole
che è in corso un'attività di elaborazione dati di grandi dimensioni (ad esempio, durante un normale aggiornamento di massa dei dati) passando temporaneamente a stale=ok durante questi periodi. L'app può tornare al comportamento
predefinito in seguito.
L'opzione stale è ancora disponibile, ma sono disponibili anche le opzioni più utili stable e update che devono essere utilizzate al suo posto. Per ulteriori informazioni, vedi Accesso a una vista obsoleta.