Non scrivere mai la tua soluzione di memorizzazione nella cache

Un altro modo per velocizzare il lavoro con il database è memorizzare nella cache gli oggetti che abbiamo già richiesto in precedenza.

Importante! Non scrivere mai la tua soluzione di memorizzazione nella cache. Questo compito ha così tante insidie ​​che non ti saresti mai sognato.

Problema 1: svuotamento della cache . A volte gli eventi si verificano quando un oggetto deve essere rimosso dalla cache o aggiornato nella cache. L'unico modo per farlo in modo competente è passare tutte le richieste al database tramite il motore della cache. Altrimenti, ogni volta dovrai dire esplicitamente alla cache quali oggetti devono essere cancellati o aggiornati.

Problema 2 - mancanza di memoria . La memorizzazione nella cache sembra un'ottima idea fino a quando non scopri che gli oggetti in memoria occupano molto spazio. Sono necessarie ulteriori decine di gigabyte di memoria affinché la cache dell'applicazione server funzioni in modo efficace.

E poiché c'è sempre una carenza di memoria, è necessaria una strategia efficace per eliminare gli oggetti dalla cache. Questo è in qualche modo simile al Garbage Collector in Java. E come ricorderete, per decenni le migliori menti hanno inventato vari modi di contrassegnare gli oggetti per generazioni, ecc.

Problema 3 - strategie diverse . Come dimostra la pratica, strategie diverse per l'archiviazione e l'aggiornamento nella cache sono efficaci per oggetti diversi. Un efficiente sistema di memorizzazione nella cache non può eseguire una sola strategia per tutti gli oggetti.

Problema 4 - Archiviazione efficiente di file . Non puoi semplicemente memorizzare oggetti nella cache. Gli oggetti troppo spesso contengono riferimenti ad altri oggetti e così via.A questo ritmo, non avrai bisogno di un Garbage Collector: semplicemente non avrà nulla da rimuovere.

Pertanto, invece di memorizzare gli oggetti stessi, a volte è molto più efficiente memorizzare i valori dei loro campi primitivi. E sistemi per costruire rapidamente oggetti basati su di essi.

Di conseguenza, otterrai un intero DBMS virtuale in memoria, che dovrebbe funzionare rapidamente e consumare poca memoria.

Cache del database

Oltre alla memorizzazione nella cache direttamente in un programma Java, la memorizzazione nella cache è spesso organizzata direttamente nel database.

Ci sono quattro grandi approcci:

Il primo approccio consiste nel denormalizzare il database . Il server SQL archivia i dati in memoria in modo diverso da come vengono archiviati nelle tabelle.

Quando i dati vengono archiviati su disco nelle tabelle, molto spesso gli sviluppatori cercano di evitare il più possibile la duplicazione dei dati: questo processo è chiamato normalizzazione del database. Quindi, per accelerare il lavoro con i dati in memoria, viene eseguito il processo inverso: la denormalizzazione del database. Un gruppo di tabelle correlate può già essere memorizzato in una forma combinata, sotto forma di enormi tabelle, ecc.

Il secondo approccio è il caching delle query . E i risultati della query.

Il DBMS vede che molto spesso gli arrivano richieste uguali o simili. Quindi inizia semplicemente a memorizzare nella cache queste richieste e le loro risposte. Ma allo stesso tempo, devi assicurarti che le righe che sono state modificate nel database vengano rimosse dalla cache in modo tempestivo.

Questo approccio può essere molto efficace con un essere umano che può analizzare le query e aiutare il DBMS a capire come memorizzarle al meglio nella cache.

Il terzo approccio è un database in memoria .

Un altro approccio comunemente usato. Un altro database è posto tra il server e il DBMS, che memorizza tutti i suoi dati solo in memoria. È anche chiamato In-Memory-DB. Se hai molti server diversi che accedono allo stesso database, utilizzando In-Memory-DB puoi organizzare la memorizzazione nella cache in base al tipo di un particolare server.

Esempio:

Approccio a 4 cluster di database . Diverse basi di sola lettura.

Un'altra soluzione è utilizzare un cluster: diversi DBMS dello stesso tipo contengono dati identici. Allo stesso tempo, puoi leggere i dati da tutti i database e scrivere solo su uno. Che viene quindi sincronizzato con il resto dei database.

Questa è un'ottima soluzione perché è facile da configurare e funziona nella pratica. Di solito, per una richiesta al database di modificare i dati, arrivano 10-100 richieste di lettura dei dati.

Tipi di memorizzazione nella cache in Hibernate

Hibernate supporta tre livelli di memorizzazione nella cache:

  • Caching a livello di sessione (sessione)
  • Caching a livello di SessionFactory
  • Richieste di memorizzazione nella cache (e relativi risultati)

Puoi provare a rappresentare questo sistema sotto forma di una tale figura:

Il tipo più semplice di memorizzazione nella cache (chiamato anche cache di primo livello ) è implementato a livello di sessione di ibernazione. Hibernate utilizza sempre questa cache per impostazione predefinita e non può essere disattivata .

Consideriamo subito il seguente esempio:

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

assertTrue(director1 == director2);

Può sembrare che qui vengano eseguite due query al database, ma non è così. Dopo la prima richiesta al database, l'oggetto Employee verrà memorizzato nella cache. E se interroghi nuovamente l'oggetto nella stessa sessione, Hibernate restituirà lo stesso oggetto Java.

Lo stesso oggetto significa che anche i riferimenti agli oggetti saranno identici. È davvero lo stesso oggetto.

I metodi save() , update() , saveOrUpdate() , load() , get() , list() , iterate() e scroll() useranno sempre la cache di primo livello. In realtà non c'è altro da aggiungere.

Cache di secondo livello

Se la cache di primo livello è associata all'oggetto sessione, la cache di secondo livello è associata all'oggetto sessione.SessionFactory. Ciò significa che la visibilità degli oggetti in questa cache è molto più ampia rispetto alla cache di primo livello.

Esempio:

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));

In questo esempio, verranno effettuate due query al database. Hibernate restituirà oggetti identici, ma non sarà lo stesso oggetto: avranno riferimenti diversi.

La memorizzazione nella cache di secondo livello è disabilitata per impostazione predefinita . Pertanto, abbiamo due query al database invece di una.

Per abilitarlo è necessario scrivere le seguenti righe nel file 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"/>

Dopo aver abilitato la memorizzazione nella cache di secondo livello, il comportamento di Hibernate cambierà leggermente:

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 dopo tutte queste manipolazioni verrà abilitata la cache di secondo livello e, nell'esempio precedente, verrà eseguita solo una query al database.