2012-05-22 13 views
7

Abbiamo un calcolo molto costoso che vorremmo memorizzare nella cache. Così facciamo qualcosa di simile a:Caching & avoiding Cache Stampedes - più calcoli simultanei

my $result = $cache->get($key); 

unless ($result) { 
    $result = calculate($key); 
    $cache->set($key, $result, '10 minutes'); 
} 

return $result; 

Ora, durante calculate($key), prima di memorizzare il risultato in cache, numerose altre richieste arrivano, che iniziano in corso anche calculate($key), e le prestazioni del sistema soffre perché molti processi sono tutti calcolando la stessa cosa.

Idea: Consente di inserire nella cache un flag per il calcolo di un valore, in modo che le altre richieste attenderanno il completamento di quel calcolo, in modo che tutti lo usino. Qualcosa di simile:

my $result = $cache->get($key); 

if ($result) { 
    while ($result =~ /Wait, \d+ is running calculate../) { 
     sleep 0.5; 
     $result = $cache->get($key); 
    } 
} else { 
    $cache->set($key, "Wait, $$ is running calculate()", '10 minutes'); 
    $result = calculate($key); 
    $cache->set($key, $result, '10 minutes'); 
} 


return $result; 

Ora che apre una lattina di vermi completamente nuova. Cosa succede se $$ muore prima di impostare la cache. Che cosa succede se, se ... Tutti loro risolvibile, ma poiché non v'è nulla in CPAN che fa questo (c'è qualcosa in CPAN per tutto), comincio a chiedermi:

Esiste un approccio migliore? C'è una ragione particolare, ad es. Le classi Perl Cache e Cache::Cache non forniscono alcun meccanismo come questo? C'è un modello provato e vero che potrei usare invece?

ideale sarebbe un modulo CPAN con un pacchetto debian già in compressione o un momento eureka, dove vedo l'errore dei miei modi ... :-)

EDIT: allora ho imparato che questo si chiama a Cache stampede e hanno aggiornato il titolo della domanda.

+0

[IPC :: ShareLite] (http://search.cpan.org/~andya/IPC-ShareLite-0.17/lib/IPC/ShareLite.pm) fornisce l'interfaccia OO per SysV memoria condivisa. È simile a ** Cache ** che fornisce un lock esclusivo. – tuxuday

+0

Esiste un [Cache stampede - articolo di Wikipedia] (https://en.wikipedia.org/wiki/Cache_stampede) e un [Perl-Cache Discuss> che evita l'argomento Stampedes] (https://groups.google.com/d/topic/perl-cache-discuss/jDdBQliwlP4/discussione) sulle strategie per questo. –

+0

E c'è una strategia [djangosnippets: MintCache] (https://www.djangosnippets.org/snippets/155/). –

risposta

2

flock() it.

Poiché i processi di lavoro si trovano tutti sullo stesso sistema, è possibile utilizzare un buon blocco di file vecchio stile per serializzare i costosi ioni calculate(). Come bonus, questa tecnica appare in molti dei documenti di base.

use Fcntl qw(:DEFAULT :flock); # warning: this code not tested 

use constant LOCKFILE => 'you/customize/this/please'; 

my $result = $cache->get($key); 

unless ($result) { 
    # Get an exclusive lock 
    my $lock; 
    sysopen($lock, LOCKFILE, O_WRONLY|O_CREAT) or die; 
    flock($lock, LOCK_EX) or die; 

    # Did someone update the cache while we were waiting? 
    $result = $cache->get($key); 

    unless ($result) { 
     $result = calculate($key); 
     $cache->set($key, $result, '10 minutes'); 
    } 

    # Exclusive lock released here as $lock goes out of scope 
} 

return $result; 

Beneficio: la morte dei lavoratori rilascerà immediatamente lo $lock.

Rischio: LOCK_EX può bloccare per sempre, e questo è un tempo lungo. Evita SIGSTOPs, forse mettiti comodo con alarm().

Estensione: se non si desidera serializzare tutte calculate() chiamate, ma si limita a tutte le chiamate per lo stesso $key o qualche mazzo di chiavi, i vostri lavoratori possono flock()/some/lockfile.$key_or_a_hash_of_the_key.

+0

Ero preoccupato per ciò che accadrebbe, ad es. durante la morte del lavoratore. Il blocco "# Esclusivo rilasciato qui come $ lock va fuori campo" è bello! Grazie! –

+0

D'accordo, dove 'flock()' è appropriato è abbastanza elegante. Tutta la magia è nel descrittore di file sottostante, ed entrambi processano la morte e la distruzione dell'ultimo handle di file di riferimento per perl fatto da perl fa esattamente ciò che si vuole. – pilcrow

1

Utilizzare il blocco? O forse sarebbe eccessivo? O se è possibile, precalcolare il risultato offline e poi usarlo online?

1

Anche se potrebbe essere (o non essere) eccessivo per il tuo caso d'uso, hai considerato l'utilizzo di una coda messaggi per l'elaborazione? RabbitMQ al momento sembra essere una scelta popolare nella comunità Perl ed è supportato dal modulo AnyEvent::RabbitMQ.

La strategia di base in questo caso sarebbe quella di inviare una richiesta alla coda dei messaggi ogni volta che è necessario calculate una nuova chiave. La coda può quindi essere impostata su calculate solo una chiave alla volta (nell'ordine richiesto) se è tutto ciò che è possibile gestire in modo affidabile. In alternativa, se è possibile calcolare in modo sicuro più chiavi contemporaneamente, la coda può anche essere utilizzata per consolidare più richieste per la stessa chiave, calcolandola una volta e restituendo il risultato a tutti i client che hanno richiesto tale chiave.

Ovviamente, ciò aggiungerebbe un po 'di complessità e AnyEvent richiede uno stile di programmazione un po' diverso da quello a cui si potrebbe essere abituati (vorrei offrire un esempio, ma non ho mai avuto il tempo di farlo da solo), ma può offrire guadagni sufficienti in termini di efficienza e affidabilità per rendere tali costi utili.

+0

Sicuramente anche una strada con merito. Ma deve essere inserito in un quadro più ampio, penso, e non siamo ancora pronti per questo. Grazie del promemoria. –

-1

Sono d'accordo generalmente con l'approccio di pilcrow sopra. Aggiungerei una cosa: Esaminare l'uso della funzione memoize() per accelerare potenzialmente l'operazione calculate() nel codice.

Vedi http://perldoc.perl.org/Memoize.html per i dettagli

+0

Memoize funziona bene in un unico processo. Come menzionato nell'OP, questo problema riguarda più processi che vogliono tutti calcolare la stessa cosa. A meno che non abbia frainteso qualcosa, Memoize non rende i valori memorizzati nella cache disponibili per processi diversi, e quindi non serve a nulla in questo contesto. Sì, può usare gli hash legati, ma poi immagino che sperimenterà esattamente il problema dell'OP. –