"Sólo hay dos problemas difíciles en la informática: invalidación de la caché y nombrar cosas".
Phil Karlton
¿Existe una solución o método general para invalidar un caché? saber cuándo una entrada está obsoleta, por lo que tiene la garantía de obtener siempre datos nuevos?
Por ejemplo, considere una función getData()
que obtiene datos de un archivo. Lo almacena en caché en función de la última hora de modificación del archivo, que verifica cada vez que se llama.
Luego agrega una segunda función transformData()
que transforma los datos y almacena en caché su resultado para la próxima vez que se llame a la función. No tiene conocimiento del archivo: ¿cómo agrega la dependencia de que si se cambia el archivo, esta caché deja de ser válida?
Puede llamar getData()
cada vez que transformData()
se llama y compararlo con el valor que se utilizó para construir la caché, pero eso podría terminar siendo muy costoso.
"The two hardest things in Computer Science are cache invalidation, naming things, and off-by-one errors."
Respuestas:
De lo que estás hablando es del encadenamiento de dependencias de por vida, que una cosa depende de otra que puede modificarse fuera de su control.
Si tiene una función idempotente desde
a
,b
haciac
dónde, sia
yb
son iguales, entoncesc
es lo mismo, pero el costo de verificaciónb
es alto, entonces usted:b
b
más rápido posibleNo puedes tener tu pastel y comértelo ...
Si puede colocar un caché adicional basado en
a
la parte superior, esto afectará el problema inicial ni un poco. Si elige 1, tiene la libertad que se dio a sí mismo y, por lo tanto, puede almacenar más en caché, pero debe recordar considerar la validez del valor en caché deb
. Si elige 2, aún debe verificarb
cada vez, pero puede recurrir a la caché para verificara
si seb
retira.Si coloca cachés en capas, debe considerar si ha violado las 'reglas' del sistema como resultado del comportamiento combinado.
Si sabe que
a
siempre tiene validezb
, entonces puede organizar su caché así (pseudocódigo):Obviamente capas sucesivas (por ejemplo
x
) es trivial, siempre que, en cada etapa de la validez de la entrada que acaba de agregar coincide con ela
:b
relación porx
:b
yx
:a
.Sin embargo, es muy posible que pueda obtener tres entradas cuya validez sea completamente independiente (o cíclica), por lo que no sería posible la superposición. Esto significaría que la línea marcada // importante tendría que cambiar a
fuente
El problema de la invalidación de caché es que las cosas cambian sin que nos demos cuenta. Entonces, en algunos casos, una solución es posible si hay alguna otra cosa que sí sabe y puede notificarnos. En el ejemplo dado, la función getData podría conectarse al sistema de archivos, que sí conoce todos los cambios en los archivos, independientemente del proceso que cambie el archivo, y este componente a su vez podría notificar al componente que transforma los datos.
No creo que haya una solución mágica general para hacer que el problema desaparezca. Pero en muchos casos prácticos puede muy bien haber oportunidades para transformar un enfoque basado en "encuestas" en uno basado en "interrupciones", lo que puede hacer que el problema simplemente desaparezca.
fuente
Si va a getData () cada vez que realiza la transformación, entonces ha eliminado todo el beneficio del caché.
Para su ejemplo, parece que una solución sería que cuando genere los datos transformados, también almacene el nombre del archivo y la última hora de modificación del archivo desde el que se generaron los datos (ya almacenó esto en cualquier estructura de datos devuelta por getData ( ), por lo que simplemente copie ese registro en la estructura de datos devuelta por transformData ()) y luego, cuando llame a transformData () nuevamente, verifique la última hora de modificación del archivo.
fuente
En mi humilde opinión, la programación reactiva funcional (FRP) es en cierto sentido una forma general de resolver la invalidación de caché.
He aquí el motivo: los datos obsoletos en la terminología de FRP se denominan fallos . Uno de los objetivos de FRP es garantizar la ausencia de fallos.
FRP se explica con más detalle en esta charla 'Esencia de FRP' y en esta respuesta SO .
En la charla, los
Cell
s representan un objeto / entidad en caché yCell
se actualiza si se actualiza una de sus dependencias.FRP oculta el código de plomería asociado con el gráfico de dependencia y se asegura de que no haya mensajes obsoletos
Cell
.Otra forma (diferente de FRP) en la que puedo pensar es envolver el valor calculado (de tipo
b
) en algún tipo de Mónada de escrituraWriter (Set (uuid)) b
dondeSet (uuid)
(notación Haskell) contiene todos los identificadores de los valores mutables de los queb
depende el valor calculado . Entonces,uuid
es una especie de identificador único que identifica el valor / variable mutable (digamos una fila en una base de datos) del cualb
depende el calculado .Combine esta idea con combinadores que operan en este tipo de escritor Monad y que podrían conducir a algún tipo de solución general de invalidación de caché si solo usa estos combinadores para calcular un nuevo
b
. Tales combinadores (digamos una versión especial defilter
) toman mónadas Writer y(uuid, a)
-s como entradas, dondea
es un dato / variable mutable, identificado poruuid
.Por lo tanto, cada vez que cambie los datos "originales"
(uuid, a)
(digamos, los datos normalizados en una base de datos a partir de la cualb
se calculó) de los queb
depende el valor de tipo calculado , puede invalidar la caché que contieneb
si muta cualquier valora
del queb
depende el valor calculado , porque según elSet (uuid)
de Writer Monad puedes saber cuándo sucede esto.Entonces, cada vez que mutes algo con un dado
uuid
, transmites esta mutación a todos los cache-s y ellos invalidan los valoresb
que dependen del valor mutable identificado con dichouuid
porque la mónada Writer en la queb
está envuelto puede decir si esob
depende de dichouuid
o no.Por supuesto, esto solo vale la pena si lee con mucha más frecuencia de la que escribe.
Un tercer enfoque práctico es usar vistas materializadas en bases de datos y usarlas como cachés. AFAIK también tienen como objetivo resolver el problema de invalidación. Por supuesto, esto limita las operaciones que conectan los datos mutables con los datos derivados.
fuente
Estoy trabajando en un enfoque en este momento basado en PostSharp y funciones de memorización . Lo pasé por alto a mi mentor, y él está de acuerdo en que es una buena implementación del almacenamiento en caché de una manera independiente del contenido.
Cada función se puede marcar con un atributo que especifica su período de caducidad. Cada función marcada de esta manera se memoriza y el resultado se almacena en la caché, con un hash de la llamada a la función y los parámetros utilizados como clave. Estoy usando Velocity para el backend, que maneja la distribución de los datos de la caché.
fuente
No, porque todos los datos son diferentes. Algunos datos pueden estar "obsoletos" después de un minuto, algunos después de una hora y algunos pueden estar bien durante días o meses.
Con respecto a su ejemplo específico, la solución más simple es tener una función de 'verificación de caché' para los archivos, a la que llama desde
getData
ytransformData
.fuente
No hay una solución general pero:
La caché puede actuar como un proxy (extracción). Suponga que su caché conoce la marca de tiempo del último cambio de origen, cuando alguien llama
getData()
, la caché le pide al origen la marca de tiempo del último cambio, si es la misma, devuelve la caché; de lo contrario, actualiza su contenido con la fuente y devuelve su contenido. (Una variación es que el cliente envíe directamente la marca de tiempo en la solicitud, la fuente solo devolvería contenido si su marca de tiempo es diferente).Aún puede usar un proceso de notificación (push), la caché observa la fuente, si la fuente cambia, envía una notificación a la caché que luego se marca como "sucia". Si alguien llama
getData()
al caché, primero se actualizará a la fuente, elimine el indicador "sucio"; luego devuelva su contenido.En general, la elección depende de:
getData()
preferirían un empujón para evitar que la fuente se inunde con una función getTimestampNota: Como el uso de la marca de tiempo es la forma tradicional en que funcionan los proxies http, otro enfoque es compartir un hash del contenido almacenado. La única forma que conozco para que 2 entidades se actualicen juntas es o te llamo (tira) o tú me llamas ... (empuja) eso es todo.
fuente
la caché es difícil porque debe tener en cuenta: 1) la caché es de varios nodos, necesita consenso para ellos 2) tiempo de invalidación 3) condición de carrera cuando ocurre la obtención / configuración múltiple
esta es una buena lectura: https://www.confluent.io/blog/turning-the-database-inside-out-with-apache-samza/
fuente
Quizás los algoritmos ajenos a la caché serían los más generales (o al menos, menos dependientes de la configuración del hardware), ya que usarán primero la caché más rápida y seguirán adelante desde allí. Aquí hay una conferencia del MIT al respecto: caché de algoritmos ajenos
fuente