2013-03-13 10 views
11

Ho un database (couchDB) con circa 90k di documenti al suo interno. I documenti sono molto semplici come questo:filtri molto lenti con couchDB anche con erlang

{ 
    "_id": "1894496e-1c9e-4b40-9ba6-65ffeaca2ccf", 
    "_rev": "1-2d978d19-3651-4af9-a8d5-b70759655e6a", 
    "productName": "Cola" 
} 

ora voglio un giorno per sincronizzare questo database con un dispositivo mobile. Ovviamente i documenti 90k non dovrebbero andare al telefono tutti in una volta. Questo è il motivo per cui ho scritto funzioni di filtro. Questi dovrebbero filtrare per "productName". Inizialmente in Javascript successivamente in Erlang per ottenere prestazioni. Queste funzioni di filtro simile a questa in JavaScript:

{ 
    "_id": "_design/local_filters", 
    "_rev": "11-57abe842a82c9835d63597be2b05117d", 
    "filters": { 
     "by_fanta": "function(doc, req){ if(doc.productName == 'Fanta'){ return doc;}}", 
     "by_wasser": "function(doc, req){if(doc.productName == 'Wasser'){ return doc;}}", 
     "by_sprite": "function(doc, req){if(doc.productName == 'Sprite'){ return doc;}}" 
    } 
} 

e come questo in Erlang:

{ 
    "_id": "_design/erlang_filter", 
    "_rev": "74-f537ec4b6508cee1995baacfddffa6d4", 
    "language": "erlang", 
    "filters": { 
     "by_fanta": "fun({Doc}, {Req}) -> case proplists:get_value(<<\"productName\">>, Doc) of <<\"Fanta\">> -> true; _ -> false end end.", 
     "by_wasser": "fun({Doc}, {Req}) -> case proplists:get_value(<<\"productName\">>, Doc) of <<\"Wasser\">> -> true; _ -> false end end.", 
     "by_sprite": "fun({Doc}, {Req}) -> case proplists:get_value(<<\"productName\">>, Doc) of <<\"Sprite\">> -> true; _ -> false end end."  
    } 
} 

di mantenerlo semplice non v'è ancora alcuna richiesta, ma una stringa "hardcoded". Il filtro funziona tutto. Il problema è che sono un modo per rallentare. Ho scritto un programma di prova prima in Java in seguito in Perl per testare il tempo necessario per filtrare i documenti. Ecco uno dei miei script Perl:

$dt = DBIx::Class::TimeStamp->get_timestamp(); 

$content = get("http://127.0.0.1:5984/mobile_product_test/_changes?filter=local_filters/by_sprite"); 

$dy = DBIx::Class::TimeStamp->get_timestamp() - $dt; 
$dm = $dy->minutes(); 
$dz = $dy->seconds(); 

@contArr = split("\n", $content); 

$arraysz = @contArr; 
$arraysz = $arraysz - 3; 

$\="\n"; 
print($dm.':'.$dz.' with '.$arraysz.' Elements (JavaScript)'); 

E ora la parte triste. Questi sono i tempi che ottengo:

2:35 with 2 Elements (Erlang) 
2:40 with 10000 Elements (Erlang) 
2:38 with 30000 Elements (Erlang) 
2:31 with 2 Elements (JavaScript) 
2:40 with 10000 Elements (JavaScript) 
2:51 with 30000 Elements (JavaScript) 

btw questi sono minuti: secondi. Il numero è il numero di elementi restituiti dal filtro e il database contiene 90k Elementi. La grande sorpresa è stata che il filtro Erlang non era affatto più veloce.

Per richiedere tutti gli elementi richiede solo 9 secondi. E la creazione di viste su 15. Ma non è possibile per il mio uso su un telefono per trasferire tutti i documenti (motivi di volume e di sicurezza).

C'è un modo per filtrare su una vista per ottenere un aumento delle prestazioni? Oppure c'è qualcosa di sbagliato nelle mie funzioni di filtro di erlang (non sono sorpreso dai tempi dei filtri JavaScript).

MODIFICA: Come sottolineato da pgras, il motivo per cui questo è lento viene pubblicato nella risposta a this Domanda. Per fare in modo che i filtri di erlang girino più velocemente ho bisogno di fare un "livello" sotto e programmare l'erlang direttamente nel database e non come un documento _design. Ma non so davvero da dove cominciare e come farlo. Qualsiasi suggerimento sarebbe utile.

+0

È possibile utilizzare _changes senza un parametro since, quindi verrà filtrato tutto il contenuto del DB, una volta la prima sincronizzazione hai intenzione di utilizzare il parametro since per le sincronizzazioni successive? Hai menzionato l'utilizzo di viste, sarebbe -fast- visualizzare con una chiave = "Fanta" risolvere i tuoi altri bisogni? – pgras

+0

Voglio usare il filtro in seguito per replicare il database su un dispositivo mobile, ho usato la richiesta http solo per testare il filtro. Ho provato a utilizzare il filtro per una replica e ho ottenuto risultati simili. Non credo che una vista sarebbe d'aiuto perché in seguito si suppone che ci sia una query con il filtro. –

+0

Passando attraverso la stessa cosa. Ho ottenuto un miglioramento del 55% quando uso Erlang per la replica filtrata, ma è ancora dolorosamente lento. – ryan1234

risposta

3

Questo è stato un po 'da quando ho fatto questa domanda. Ma ho pensato di tornare su di esso e condividere ciò che abbiamo fatto per risolvere questo problema.

Quindi la risposta breve è la velocità del filtro non può davvero essere migliorata.

Il motivo è il modo in cui i filtri funzionano. Se controlli le modifiche al tuo database. Sono qui:

Questo documento contiene tutte le modifiche che appartengono al vostro database. Se fai qualcosa nel tuo database, vengono aggiunte nuove linee. Quando ora si desidera utilizzare un filtro, il filtro viene analizzato da json alla lingua specificata e utilizzato per ogni riga in questo file. Per essere chiari, per quanto ne so, l'analisi viene eseguita anche per ogni riga. Questo non è molto efficiente e non può essere modificato.

Quindi personalmente penso che per la maggior parte dei casi i filtri di utilizzo rallentino e non possano essere utilizzati. Ciò significa che dobbiamo trovare un modo per aggirare questo. Non intendo che io abbia una soluzione generale. Posso solo dire che per noi qui era possibile utilizzare le viste anziché il filtro. Le viste generano alberi internamente e sono veloci quanto la luce rispetto al filtro. Un filtro semplice viene memorizzato anche nel documento di progettazione e potrebbe essere la seguente:

{ 
"_id": "_design/all", 
"language": "javascript", 
"views": { 
    "fantaView": { 
     "map": "function(doc) { \n if (doc.productName == 'Fanta') \n emit(doc.locale, doc)\n} " 
    } 
} 
} 

Dove fantaView è il nome per la vista. Immagino che la funzione sia auto-esplicativa. Quindi questo è quello che abbiamo fatto spero che aiuti qualcuno se si imbatte in un problema simile.

0

Posso sbagliarmi ma le funzioni di filtro dovrebbe restituire valori booleani quindi cercate di cambiare uno a:

function(doc, req){ return doc.productName === 'Fanta';} 

Può risolvere il tuo problema di prestazioni ...

Edit:

Here è una spiegazione sul perché è lento (almeno con JavaScript) ...

Una soluzione consiste nell'utilizzare una vista su selezionare gli ID dei documenti da sincronizzare e quindi avviare la sincronizzazione specificando doc_ids da sincronizzare.

Per esempio la vista sarebbe:

function(doc){ 
    emit(doc.productName, doc._id) 
} 

si potrebbe chiamare la vista con _design/docs/_Vedi/by_producName?key = "Fanta"

e quindi avviare la replica con gli ID doc trovata ...

+0

beh questa è solo la funzione javaScript, l'erlang one restituisce valori booleani. –

+0

@ArneFischer, sì l'ho visto dopo aver risposto ... Ho alcune domande ma chiedo nei commenti della domanda iniziale ... – pgras

+0

Usare una vista per creare l'elenco dei documenti e quindi passare alla replica è quello che facciamo. Sembra goffo, ma funziona bene. Qualcuno ha fatto qualche test di velocità su questo approccio? –

0

In generale i filtri couchDB sono lenti. Altri hanno già spiegato perché sono lenti. Quello che ho trovato è che l'unico modo ragionevole per usare i filtri è usare "since". Altrimenti in un database ragionevolmente grande (il mio ha 47k documenti, e sono documenti complessi) i filtri non funzionano. Lo abbiamo imparato nel modo più duro migrando da dev a prod [poche centinaia di documenti a ~ 47k documenti]. Abbiamo anche modificato il design in base a una query e abbiamo utilizzato Spring's @Scheduled