2012-05-22 8 views
7

Tenemos un cálculo muy caro que nos gustaría almacenar en caché. Por lo que hacemos algo similar a:Almacenamiento en caché y prevención de estampillas de caché: múltiples cálculos simultáneos

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

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

return $result; 

Ahora, durante calculate($key), antes de almacenar el resultado en la memoria caché, varias otras solicitudes de entrar, para que también empiezan a correr calculate($key), y el rendimiento del sistema debido a que muchos procesos están calculando la misma cosa.

Idea: Permite poner un indicador en el caché de que se está calculando un valor, por lo que las demás solicitudes simplemente esperan a que termine ese cálculo, para que todos lo usen. Algo así como:

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; 

Ahora que se abre toda una nueva lata de gusanos. Qué sucede si $$ muere antes de establecer la caché. ¿Qué pasa si, ¿qué pasaría si ... Todos ellos solucionable, pero ya que no hay nada en CPAN que hace esto (hay algo en CPAN para todo ), que comienza a preguntarse:

¿Hay un mejor enfoque? ¿Hay alguna razón en particular, por ejemplo, Las clases Cache y Cache::Cache de Perl no proporcionan algún mecanismo como este? ¿Hay un patrón probado y verdadero que podría usar en su lugar?

ideal sería un módulo de CPAN con un paquete Debian que ya están en contracción o un momento eureka, donde veo el error de mis maneras ... :-)

EDIT: he sabido que esto se llama a Cache stampede y ha actualizado el título de la pregunta.

+0

[IPC :: ShareLite] (http://search.cpan.org/~andya/IPC-ShareLite-0.17/lib/IPC/ShareLite.pm) proporciona interfaz OO para SysV memoria compartida. Es similar a ** Cache ** que proporciona un bloqueo exclusivo. Hay una – tuxuday

+0

[estampida caché - artículo de Wikipedia] (https://en.wikipedia.org/wiki/Cache_stampede) y una [Perl-caché Discuta> evitar estampidas tema] (https://groups.google.com/d/topic/perl-cache-discuss/jDdBQliwlP4/discussion) sobre estrategias para esto. –

+0

Y hay una [djangosnippets: MintCache] estrategia (https://www.djangosnippets.org/snippets/155/). –

Respuesta

2

flock() it.

Dado que sus procesos de trabajo están todos en el mismo sistema, es probable que pueda usar un buen bloqueo de archivos anticuado para serializar los caros iones calculate(). Como beneficio adicional, esta técnica aparece en varios de los documentos básicos.

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 muerte del trabajador al instante liberar la $lock.

Riesgo: LOCK_EX puede bloquear para siempre, y que es mucho tiempo. Evite los SIGSTOP, quizás se sienta cómodo con alarm().

Extensión: si no desea realizar una serie calculate() todas las llamadas, sino que simplemente las llamadas destinadas al mismo $key o algún juego de llaves, los trabajadores pueden flock()/some/lockfile.$key_or_a_hash_of_the_key.

+0

Estaba preocupado por lo que sucedería, p. durante la muerte del trabajador. El '# Exclusive lock lanzado aquí como $ lock queda fuera del alcance' ¡es ingenioso! ¡Gracias! –

+0

De acuerdo, donde 'flock()' es apropiado es bastante ingenioso. Toda la magia está en el descriptor de archivo subyacente, y tanto la muerte del proceso como la última destrucción de perl de los manejadores de archivo hacen exactamente lo que uno quiere. – pilcrow

1

Use lock? O tal vez eso sería un exceso? O si es posible, precalcular el resultado fuera de línea y luego usarlo en línea?

1

Aunque puede (o no) ser excesivo para su caso de uso, ¿ha considerado utilizar una cola de mensajes para el procesamiento? RabbitMQ parece ser una opción popular en la comunidad de Perl en este momento y es compatible a través del módulo AnyEvent::RabbitMQ.

La estrategia básica en este caso sería enviar una solicitud a la cola de mensajes siempre que necesite calculate una nueva clave. La cola podría establecerse en calculate solo una sola tecla a la vez (en el orden solicitado) si eso es todo lo que puede manejar de manera confiable. Alternativamente, si puede calcular varias claves al mismo tiempo, la cola también se puede usar para consolidar múltiples solicitudes para la misma clave, calcularla una vez y devolver el resultado a todos los clientes que solicitaron esa clave.

Por supuesto, esto agregaría un poco de complejidad y AnyEvent llama a un estilo de programación algo diferente de lo que puede estar acostumbrado (ofrecería un ejemplo, pero nunca me he acostumbrado a eso), pero puede ofrecer ganancias suficientes en eficiencia y confiabilidad para que esos costos valgan la pena.

+0

Definitivamente también una avenida con mérito. Pero creo que debe encajar en una imagen más amplia, y todavía no estamos preparados para eso. Gracias por el recordatorio. –

-1

estoy de acuerdo en general con el enfoque de Pilcrow anteriormente. Le agregaría una cosa: investigue el uso de la función memoize() para acelerar potencialmente la operación calculate() en su código.

Ver http://perldoc.perl.org/Memoize.html para los detalles

+0

Memoize funciona bien en un solo proceso. Como se menciona en el PO, este problema se trata de múltiples procesos, todos queriendo calcular lo mismo. A menos que haya malinterpretado algo, Memoize no hace que los valores almacenados en caché estén disponibles para diferentes procesos, por lo que no sirve de nada en este contexto. Sí, puede usar hashes atados, pero supongo que experimentará exactamente el problema desde el OP. –

Cuestiones relacionadas