Nunca escriba su solución de almacenamiento en caché

Otra forma de agilizar el trabajo con la base de datos es almacenar en caché los objetos que ya hemos solicitado anteriormente.

¡Importante! Nunca escriba su propia solución de almacenamiento en caché. Esta tarea tiene tantas trampas que nunca soñaste.

Problema 1: vaciado de caché . A veces, los eventos ocurren cuando un objeto debe eliminarse del caché o actualizarse en el caché. La única forma de hacerlo de manera competente es pasar todas las solicitudes a la base de datos a través del motor de caché. De lo contrario, cada vez tendrá que decirle explícitamente al caché qué objetos deben eliminarse o actualizarse.

Problema 2: falta de memoria . El almacenamiento en caché parece una gran idea hasta que descubre que los objetos en la memoria ocupan mucho espacio. Necesita decenas de gigabytes de memoria adicionales para que la memoria caché de la aplicación del servidor funcione de manera efectiva.

Y dado que siempre hay escasez de memoria, se necesita una estrategia efectiva para eliminar objetos del caché. Esto es algo similar al recolector de basura en Java. Y como recordarán, durante décadas las mejores mentes han estado inventando varias formas de marcar objetos por generaciones, etc.

Problema 3 - Diferentes estrategias . Como muestra la práctica, las diferentes estrategias para almacenar y actualizar en la memoria caché son efectivas para diferentes objetos. Un sistema de almacenamiento en caché eficiente no puede hacer una sola estrategia para todos los objetos.

Problema 4: almacenamiento eficiente de archivos . No puede simplemente almacenar objetos en el caché. Con demasiada frecuencia, los objetos contienen referencias a otros objetos, etc. A este ritmo, no necesitará un recolector de basura: simplemente no tendrá nada que eliminar.

Por lo tanto, en lugar de almacenar los propios objetos, a veces es mucho más eficiente almacenar los valores de sus campos primitivos. Y sistemas para la construcción rápida de objetos a partir de ellos.

Como resultado, obtendrá un DBMS virtual completo en la memoria, que debería funcionar rápidamente y consumir poca memoria.

Almacenamiento en caché de la base de datos

Además del almacenamiento en caché directamente en un programa Java, el almacenamiento en caché a menudo se organiza directamente en la base de datos.

Hay cuatro grandes enfoques:

El primer enfoque es desnormalizar la base de datos . El servidor SQL almacena datos en la memoria de forma diferente a como se almacenan en tablas.

Cuando los datos se almacenan en el disco en tablas, muy a menudo los desarrolladores intentan evitar la duplicación de datos tanto como sea posible; este proceso se denomina normalización de la base de datos. Entonces, para acelerar el trabajo con datos en la memoria, se realiza el proceso inverso: desnormalización de la base de datos. Un montón de tablas relacionadas ya se pueden almacenar en forma combinada, en forma de tablas enormes, etc.

El segundo enfoque es el almacenamiento en caché de consultas . Y consultar los resultados.

El DBMS ve que muy a menudo le llegan solicitudes iguales o similares. Luego, simplemente comienza a almacenar en caché estas solicitudes y sus respuestas. Pero al mismo tiempo, debe asegurarse de que las filas que han cambiado en la base de datos se eliminen de la caché de manera oportuna.

Este enfoque puede ser muy efectivo con un ser humano que puede analizar consultas y ayudar al DBMS a descubrir la mejor manera de almacenarlas en caché.

El tercer enfoque es una base de datos en memoria .

Otro enfoque comúnmente utilizado. Se coloca otra base de datos entre el servidor y el DBMS, que almacena todos sus datos solo en la memoria. También se llama In-Memory-DB. Si tiene muchos servidores diferentes que acceden a la misma base de datos, al usar In-Memory-DB puede organizar el almacenamiento en caché según el tipo de servidor en particular.

Ejemplo:

Enfoque de 4 grupos de bases de datos . Varias bases de solo lectura.

Otra solución es utilizar un clúster: varios DBMS del mismo tipo contienen datos idénticos. Al mismo tiempo, puede leer datos de todas las bases de datos y escribir en una sola. Que luego se sincroniza con el resto de bases de datos.

Esta es una muy buena solución porque es fácil de configurar y funciona en la práctica. Por lo general, para una solicitud a la base de datos para cambiar datos, llegan de 10 a 100 solicitudes de lectura de datos.

Tipos de almacenamiento en caché en Hibernate

Hibernate admite tres niveles de almacenamiento en caché:

  • Almacenamiento en caché a nivel de sesión (Session)
  • Almacenamiento en caché a nivel de SessionFactory
  • Solicitudes de almacenamiento en caché (y sus resultados)

Puede intentar representar este sistema en forma de una figura de este tipo:

El tipo más simple de almacenamiento en caché (también llamado caché de primer nivel ) se implementa en el nivel de sesión de Hibernate. Hibernate siempre usa este caché por defecto y no se puede deshabilitar .

Consideremos inmediatamente el siguiente ejemplo:

Employee director1 = session.get(Employee.class, 4);
Employee director2 = session.get(Employee.class, 4);

assertTrue(director1 == director2);

Puede parecer que aquí se ejecutarán dos consultas a la base de datos, pero no es así. Después de la primera solicitud a la base de datos, el objeto Empleado se almacenará en caché. Y si vuelve a consultar el objeto en la misma sesión, Hibernate devolverá el mismo objeto Java.

El mismo objeto significa que incluso las referencias a objetos serán idénticas. Es realmente el mismo objeto.

Los métodos save() , update() , saveOrUpdate() , load() , get() , list() , iterate() y scroll() siempre usarán el caché de primer nivel. En realidad, no hay nada más que añadir.

Almacenamiento en caché de segundo nivel

Si el caché de primer nivel está vinculado al objeto de sesión, el caché de segundo nivel está vinculado al objeto de sesión.SessionFactory. Lo que significa que la visibilidad de los objetos en este caché es mucho más amplia que en el caché de primer nivel.

Ejemplo:

Session session = factory.openSession();
Employee director1 = session.get(Employee.class, 4);
session.close();

Session session = factory.openSession();
Employee director2 = session.get(Employee.class, 4);
session.close();

assertTrue(director1 != director2);
assertTrue(director1.equals(director2));

En este ejemplo, se realizarán dos consultas a la base de datos. Hibernate devolverá objetos idénticos, pero no será el mismo objeto, tendrán referencias diferentes.

El almacenamiento en caché de segundo nivel está deshabilitado de forma predeterminada . Por lo tanto, tenemos dos consultas a la base de datos en lugar de una.

Para habilitarlo, debe escribir las siguientes líneas en el archivo hibernate.cfg.xml:

<property name="hibernate.cache.provider_class" value="net.sf.ehcache.hibernate.SingletEhCacheProvider"/>
<property name="hibernate.cache.use_second_level_cache" value="true"/>

Después de habilitar el almacenamiento en caché de segundo nivel, el comportamiento de Hibernate cambiará un poco:

Session session = factory.openSession();
Employee director1 = session.get(Employee.class, 4);
session.close();

Session session = factory.openSession();
Employee director2 = session.get(Employee.class, 4);
session.close();

assertTrue(director1 == director2);

Solo después de todas estas manipulaciones, se habilitará el caché de segundo nivel y, en el ejemplo anterior, solo se ejecutará una consulta a la base de datos.