Tecnologie

Dettaglio dell'articolo:
domenica 18 febbraio 2007

Cache e Hibernate

Una applicazione dovrebbe essere in grado di mantenere una cache contenente i dati già caricati dal database, e consultare il database solo quando i dati sono stati modificati.
In questo caso, le modifiche devono invalidare la cache.

Hibernate implementa due livelli di cache:

  1. Cache di primo livello: si tratta di una cache a livello di sessione. Questa cache è usata in primo luogo per ottimizzare le query SQL generate da Hibernate. Non è necessario configurare questo tipo di cache, visto che è gestita direttamente dalle librerie di Hibernate.
  2. Cache di secondo livello: si tratta del tipo di cache discusso in questo articolo, che verrà di seguito semplicemente denominata "cache".
Iniziamo ad utilizzare Hibernate senza cache, come mostrato in Figura 1. I dati sono scambiati tra Hibernate ed il database, e le transazioni sono gestite dal database. Hibernate assume che i dati in memoria devono essere aggiornati ad ogni accesso (una assunzione ragionevole, specialmente se Hibernate non ha accesso esclusivo al database).

Figura 1


Figura 1. Hibernate senza nessuna Cache






La Figura 2 mostra Hibernate che opera con una singola JVM cache usata per minimizzare il traffico tra Hibernate ed il database. Questo accrescerà le performance dell'applicazione e minimizzerà il carico sul database, ma a costo di una maggiore complessità nella configurazione (come descritto di seguito) ed utilizzo della memoria.

Figura 2


Figura 2. Hibernate con una Cache




Figura 3 illustra il problema che può sorgere quando si usa una cache.Se l'applicazione non ha accesso esclusivo al database, la cache associata può facilmente diventare "out of sync". Se l'applicazione legacy modifica un record sul database che è presente sulla cache, non avviene nessuna segnalazione che il dato non è aggiornato, e di conseguenza i dati nella cache non saranno corretti.

Figura 3




Figura 3. Hibernate e un sistema legacy




Sessioni Multiple

Una cache su singola jvm, come descritto sin qui, è una cache a livello di sessione. Se c'è la necessità di costruire un sistema che si basi su pi๠di una jvm, sarà necessario utilizzare una cache distribuita.

Sfortunatamente non esiste una soluzione ideale al problema di una cache distribuita in collegamento con un sistema legacy.Se la tua applicazione che utilizza hibernate accede in modo read-only sul database, potrà essere possibile configurare delle cache che scadono periodicamente.

Se sei in grado di controllare tutti gli accessi ad un particolare istanza del database, puoi usare una cache distribuita per assicurare che il traffico dei dati è propriamente sincronizzato. Un esempio di questo è mostrato in Figura 4. Fare attenzione che l'overhead del traffico dovuto alla sincronizzazione della cache non sia maggiore dei vantaggi dati dai dati tenuti in cache.

Figura 4



Figura 4. Hibernate e cache distribuita






Come annotazione finale, va tenuto conto che una cache distribuita è solo una delle possibili soluzioni ai problemi di performance.Alcuni database, per esempio, implementano un sistema interno di distribuzione, facendo in modo che la complessità della distribuzione dei dati sia presa in carico dal database stesso (consentendo all'applicazione di trattare il database multiplo con una singola sorgente di dati).

Configurazione di una cache

Applicazioni che effettuano un grande numero di letture in relazione al numero di operazioni di scrittura in genere traggono massimo beneficio dall'aggiunta di una cache.

Il tipo di cache che si adatta meglio dipende da fattori come l'uso di JTA, requisiti di transaction isolation-level, ed utilizzo di cluster. A causa di queste ampie casistiche di utilizzo, Hibernate non implementa cache, ma si basa su librerie configurabili di terze parti.

Tabella 1. Cache Supportate

Cache

Tipo

URL

EHCache (Easy Hibernate Cache)

In Process

http://ehcache.sourceforge.net/

OSCache (Open Symphony)

In Process OR Cluster

http://www.opensymphony.com/oscache/

SwarmCache

Cluster

http://swarmcache.sourceforge.net/

JBoss TreeCache

Cluster

http://jboss.org/wiki/Wiki.jsp?page=JBossCache


Cache standard

In aggiunta alle cache open-source descritte sopra, può essere utile adottare una soluzione commerciale quale Tangosol Coherence. Per maggiori informazioni consultare http://hibernate.org/132.html e http://tangosol.com/.

La Tabella 2 mostra i dettagli necessari per impostare la proprietà hibernate.cache.provider_class che deve essere impostate nel file hibernate.properties per abilitare l'uso di una cache.

Ogni tipo di cache offre differenti possibilità in termini di memoria e disco e una ampia varietà di possibili configurazioni.

Tabella 2. Specificare un tipo di cache

Cache

Property Value

EHCache

net.sf.ehcache.hibernate.Provider (default) (Easy Hibernate Cache)

OSCache (Open Symphony)

net.sf.hibernate.cache.OSCacheProvider

SwarmCache

net.sf.hibernate.cache.Swarm CacheProvider

JBoss TreeCache

net.sf.hibernate.cache.TreeCache Provider

Custom (User-Defined)

Fully qualified class name pointing to a net.sf .hibernate.cache.CacheProvider implementation


Indipendentemente da quale cache viene scelta, sarà necessario dire a Hibernate che tipo di regole per la cache devono essere applicate ai tuoi dati. Questo viene definito utilizzando il tag cache. Puoi impostare il tag cache nel tuo file *.hbm.xml o nel file hibernate.cfg.xml. E' anche possibile configurare le impostazioni della cache da programma utilizzando l'oggetto Configuration. La Tabella 3 mostra i valori consentiti per il tag cache.

Tabella 3. Opzioni per la cache

Option

Comment

read-only

Utile solo se l'applicazione accede in sola lettura ai dati (ma non effettua modifiche). Particolarmente utile se il cache provider supporta l'expiration automatica della cache.Deve essere anche impostato a mutable=false il tag per la parent class/collection.

read-write

Questa strategia mantiene lo stato della cache "read committed", utilizzando un meccanismo di timestamping ed è diponibile solo in ambienti non clustered.
Se non è utilizzato JTA, assicurarsi che Session.close() or Session.disconnect() venga utilizzato in tutte le transazioni.

nonstrict-read-write

Non verifica che due transazioni modifichino gli stessi dati; questo è lasciato all'applicazione.
Se non è utilizzato JTA, assicurarsi che Session.close() or Session.disconnect() venga utilizzato in tutte le transazioni.

transactional

Cache a transazioni distribuite.


Alcune librerie non supportano tutte le opzioni per la cache. La Tabella 4 quali opzioni supportano i diversi provider.

Tabella 4. Opzioni cache supportate dai provider

Cache

read-only

nonstrict-read-write

read-write

transactional

EHCache

Yes

Yes

Yes


OSCache

Yes

Yes

Yes


SwarmCache

Yes

Yes



JBoss TreeCache

Yes



Yes


Query Cache

I risultati di una query possono essere salvati in cache. Per utilizzare la query cache, essa deve essere abilitata:

hibernate.cache.use_query_cache true

Questa impostazione non è però sufficiente per abilitare la cache all'interno di una applicazione.
E' infatti necessario, ogni volta che viene eseguita una query, richiamare il metodo:

Query.setCacheable(true)

Hibernate salva i risultati di una query nella "cache region" in un formato ridotto, registrando soltanto i riferimenti.
Quindi quando l'applicazione avrà la necessità di leggere i dati contenuti nel risultato della query, essa dovrà effettuuare un accesso al database,
a meno che non sisa stata abilitata la cache di secondo livello.
In sostanza per poter evitare di accedere al database avendo tutti i dati in cache, è necessario:

1) abilitare la cache di secondo livello per tutti i dati collegati alla query;
2) abilitare la query cache e impostare la properietà cacheable dell'oggetto query a true.

Java Transaction API (JTA)

Seguendo la documentazione Sun, JTA "definisce le interfacce Java standard tra un transaction manager e tutte le componenti coinvolte in un distributed transaction system: resource manager, application server, e applicazioni transazionali." In altre parole, JTA riguarda le transazioni che coinvolgono pi๠application server. Per ulteriori dettagli consultare (http://java.sun.com/products/jta/).


Un esempio pratico

Supponiamo di dover utilizzare quanto detto in una applicazione che utilizzi un oggetto Category e degli oggetti Item, ovviamente associati alla propria categoria. Tali oggetti Item sono inoltri associati a delle Bid, che rappresentano offerte di acquisto relative all'oggetto. L'applicazione utilizza inoltre la classe User per definire gli utenti.

Iniziamo ad aggiungere l'elemento di mappatura necessario per dire ad Hibernate come gestire istanze della classe Category.

<class name="Category" table="CATEGORY"> <cache usage="read-write"></cache>
<id ...>
</id>
</class>

Viene usata l'opzione "read-write" invece di "nonstrict-read-write" perchè Category è una classe che può essere condivisa tra molte transazioni concorrenti. (E' chiaro che un isolation level "read committed" può andar bene) Un "nonstrict-read-write" si baserebbe su cache expiration (timeout), ma è preferibile che le modifiche alle categoria siano visibili immediatamente.

Questa mappatura è sufficiente per dire a Hibernate di mette in cache tutti i valori della Category, ma non lo stato di entità o collections. Le collections hanno bisogno del loro tag <cache>.</cache>

<class name="auction.model.Category" table="CATEGORY">
<cache usage="read-write"/>
<id ...>
</id>
<set name="items">
<cache usage="read-write"></cache>
<key ...>
</key>
</set>
</class>


Se è necessario che le istanze degli Item siano messi in cache,devi abilitare il caching della classe Item. Una startegia "read-write" è specialmente appropriata.

Andiamo avanti nell'esempio e consideriamo una collezione di bid associate ad un certo Item. Una Bid è immutabile, ma la collezione delle bid è mutabile, e moduli concorrenti devono vedere ogni aggiunta e rimozione di un elemento senza ritardi:

<class name="Item" table="ITEM">
<cache usage="read-write"></cache>
<id ...>
</id>
<set name="bids">
<cache usage="read-write"></cache>
<key ...>
</key>
</set>
</class>


Alla classe Bid può essere applicata una strategia "read-only":

<class name="Bid" table="BID" mutable="false">
<cache usage="read-only"></cache>
<id ...>
</id>
</class>


User è un esempio di classe che può essere messa in cache utilizzando la strategia "nonstrict-read-write", ammesso che abbia senso il caching degli utenti.



Commenti:

Scrivi un commento:


Archivio