Almacenamiento en caché de resultados paginados, purga al actualizar: ¿cómo solucionarlo?

StackOverflow https://stackoverflow.com/questions/109480

Pregunta

Creé un foro y estamos implementando una solución de almacenamiento en caché de apc y memcache para ahorrarle algo de trabajo a la base de datos.

Comencé a implementar la capa de caché con claves como "Categorías::getAll", y si tenía datos específicos del usuario, agregaba a las claves cosas como el ID de usuario, para que obtuvieras "User::getFavoriteThreads|1471".Cuando un usuario agregaba un nuevo hilo favorito, eliminaba la clave de caché y recreaba la entrada.

Sin embargo, y aquí viene el problema:

Quería almacenar en caché los hilos en un foro.Bastante simple, "Foro::getThreads|$iForumId".Pero...Con la paginación, tendría que dividir esto en varias entradas de caché, por ejemplo

"Forum::getThreads|$iForumId|$iLimit|$iOffset".

Lo cual está bien, hasta que alguien publique un hilo nuevo en el foro.Ahora tendré que eliminar todas las claves debajo "Forum::getThreads|$iForumId", sin importar cuál sea el límite y la compensación.

¿Cuál sería una buena manera de resolver este problema?Realmente prefiero no recorrer todos los límites y compensaciones posibles hasta encontrar algo que ya no coincida.

Gracias.

¿Fue útil?

Solución

Es posible que también desee echar un vistazo al costo de almacenar los datos de la caché, en términos de su esfuerzo y costo de CPU, en comparación con lo que le comprará la caché.

Si descubre que el 80% de las vistas de su foro se dirigen a la primera página de los hilos, entonces podría decidir almacenar en caché solo esa página.Eso significaría que tanto las lecturas como las escrituras de caché son mucho más sencillas de implementar.

Lo mismo ocurre con la lista de hilos favoritos de un usuario.Si esto es algo que cada persona visita raramente, es posible que el caché no mejore demasiado el rendimiento.

Otros consejos

Sólo una actualización:Decidí que el punto de Josh sobre el uso de datos era muy bueno.Es poco probable que la gente siga viendo la página 50 de un foro.

Basado en este modelo, decidí almacenar en caché los 90 hilos más recientes en cada foro.En la función de recuperación, verifico el límite y el desplazamiento para ver si el segmento de subprocesos especificado está dentro del caché o no.Si está dentro del límite de caché, uso array_slice() para recuperar la parte correcta y devolverla.

De esta manera, puedo usar una única clave de caché por foro, y me lleva muy poco esfuerzo borrar/actualizar el caché :-)

También me gustaría señalar que en otras consultas con más recursos, elegí el modelo de flungabunga, almacenando las relaciones entre claves.Desafortunadamente, Stack Overflow no me deja aceptar dos respuestas.

¡Gracias!

He logrado resolver esto extendiendo el memcache clase con una clase personalizada (digamos ExtendedMemcache) que tiene una propiedad protegida que contendrá una tabla hash de grupo a valores clave.

El ExtendedMemcache->set El método acepta 3 argumentos ($strGroup,$strKey, $strValue) Cuando llame al conjunto, almacenará la relación entre $strGroup, y $strKey, en la propiedad protegida y luego pasar a almacenar el $strKey a $strValue relación en memcache.

Luego puede agregar un nuevo método al ExtendedMemcache clase llamada "deleteGroup", que, cuando se le pasa una cadena, encontrará las claves asociadas a ese grupo y purgará cada clave por turno.

Sería algo como esto:http://pastebin.com/f566e913bEspero que todo esto tenga sentido y funcione para ti.

PD.Supongo que si quisieras utilizar llamadas estáticas, la propiedad protegida podría guardarse en memcache mismo bajo su propia clave.Solo un pensamiento.

Básicamente, estás intentando almacenar en caché una vista, lo que siempre será complicado.En su lugar, debería intentar almacenar en caché únicamente los datos, porque los datos rara vez cambian.No almacene en caché un foro, almacene en caché las filas del hilo.Entonces su llamada a la base de datos debería devolver una lista de identificadores, que ya tiene en su caché.La llamada a la base de datos será muy rápida en cualquier tabla MyISAM, y luego no tendrá que realizar una gran unión, lo que consume memoria de la base de datos.

Una posible solución es no paginar el caché de los hilos en un foro, sino poner la información del hilo en Forum::getThreads|$iForumId.Luego, en su código PHP, solo extraiga los que desee para esa página determinada, por ejemplo.

$page = 2;
$threads_per_page = 25;
$start_thread = $page * $threads_per_page;

// Pull threads from cache (assuming $cache class for memcache interface..)
$threads = $cache->get("Forum::getThreads|$iForumId");

// Only take the ones we need
for($i=$start_thread; $i<=$start_thread+$threads_per_page; $i++)
{
    // Thread display logic here...
    showThread($threads[$i]);
}

Esto significa que tiene un poco más de trabajo que hacer para extraerlos en cada página, pero ahora solo tiene que preocuparse por invalidar el caché en un lugar al actualizar/agregar un nuevo hilo.

flungabunga:Tu solución se acerca mucho a lo que estoy buscando.Lo único que me impide hacer esto es tener que almacenar las relaciones en Memcache después de cada solicitud y volver a cargarlas.

No estoy seguro de cuánto afectaría esto al rendimiento, pero parece un poco ineficiente.Haré algunas pruebas y veré cómo resulta.Gracias por una sugerencia estructurada (y algo de código para mostrar, ¡gracias!).

Tenga mucho cuidado al realizar este tipo de optimización sin tener datos concretos con los que comparar.

La mayoría de las bases de datos tienen varios niveles de cachés.Si se ajustan correctamente, la base de datos probablemente hará un trabajo de almacenamiento en caché mucho mejor que el que usted mismo puede hacer.

En respuesta a flungabunga:

Otra forma de implementar la agrupación es poner el nombre del grupo más un número de secuencia en las propias claves e incrementar el número de secuencia para "borrar" el grupo.Almacena el número de secuencia válido actual para cada grupo en su propia clave.

p.ej.

get seqno_mygroup
23

get mygroup23_mykey
<mykeydata...>
get mygroup23_mykey2
<mykey2data...>

Luego para "eliminar" el grupo simplemente:

incr seqno_mygroup

Listo:

get seqno_mygroup
24

get mygroup24_mykey
...empty

etc..

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top