Wstęp
Postanowiłem sprawdzić w jakim stopniu zastosowanie pamięci podręcznej drugiego poziomu (second level cache – slc) wpływa na poprawę wydajności aplikacji. Ponieważ temat jest obszerny, więc w tym wpisie zająłem się wykorzystaniem slc dla instancji klas. Kod źródłowy zarówno aplikacji jak i testów jest dostępny w repozytorium git pod adresem http://github.com/tlempart/grails-slc.
Wersja bez slc
Aplikacja
Zacząłem od utworzenia aplikacji:
grails create-app grails-slc
i klasy domenowej Book, której instancje nie będą umieszczane w slc:
grails create-domain-class com.example.Book
z jednym atrybutem tytuł:
package com.example
class Book {
String title
}
Aby umożliwić testy wydajnościowe utworzyłem kontroler:
grails create-controller com.example.Book
z standardową akcją list:
package com.example
class BookController {
def list = {
render {
1000.times {
Book.get(it)
}
}
}
}
Uwaga w kontrolerze wykorzystałem metodę get, gdyż jest to jedna z kilku metod GORM, która potrafi pobrać instancje klas z slc, inne metody to:
Metody find* lub list* nie korzystają z slc tylko z query cache.
Pozostało tylko uzupełnić Bootstrap:
class BootStrap {
def init = { servletContext ->
1000.times {
new com.example.Book(title: "Tytuł {it}").save()
}
}
def destroy = {
}
}
Testy
Aby zmierzyć wydajność akcji list użyłem narzędzia jmeter.
Po uruchomieniu jmeter utworzyłem:
- grupę wątków (Thread Group):
- kontroler pętli (Loop Controller):
- żądanie HTTP (HTTP Request):
- oraz wyniki w postaci wykresu graficznego (Graph Results).
Projekt zapisałem w pliku test/jmeter/book.jmx
Przed uruchomieniem testu wystartowałem aplikację:
grails run-app
Wynik uruchomienia testu dał średnią 208 a medianę 184.
Wersja z slc
Aplikacja
Dodałem klasę domenową CachedBook podobną do Book, ale z domknięciem statycznym mapping zawierającym aktywację pamięci podręcznej drugiego poziomu dla instancji tejże klasy:
package com.example
class CachedBook {
String title
static mapping = {
cache true
}
}
co według dokumentacji grails (sekcja 5.5.2.2. Caching Strategy) powinno skutkować umieszczaniem instancji klasy CachedBook w slc.
Uwaga, zapis
static mapping = {
cache: true
}
jest nieprawidłowy. Instancje klasy domenowej nie będą zapisywane w pamięci podręcznej drugiego poziomu a co więcej grails nie wypisze żadnego ostrzeżenia.
Do kontrolera BookController dodałem nową akcję, która pozwoli na test wydajności odczytu instancji klasy CachedBook:
def list = {
render {
1000.times {
CachedBook.get(it)
}
}
}
Na koniec uzupełniłem Bootstrap:
class BootStrap {
def init = { servletContext ->
1000.times {
new com.example.Book(title: "Tytuł {it}").save()
}
1000.times {
new com.example.CachedBook(title: "Tytuł {it}").save()
}
}
def destroy = {
}
}
Pamięć podręczna jest domyślnie skonfigurowana i włączona w aplikacji grails, więc nie należało nic więcej robić.
Testy
Skopiowałem projekt book.jmx do bookCached.jmx i zmieniłem żądanie HTTP (HTTP Request) na następujące:

Ponowne uruchomienie testu dało średnią 140 a medianę 124:
Baza danych w osobnym procesie
Ponieważ powyższe testy były przeprowadzane dla bazy HSQLDB w trybie mem, więc postanowiłem przeprowadzić je dla dwóch różnych baz danych uruchomionych w osobnym procesie. Pierwszą z nich była wcześniej już użyta HSQLDB a drugą PostgreSQL. Rozpocząłem od uzupełnienia konfiguracji dostępu do bazy danych w pliku grails-app/conf/DataSource.groovy:
environments {
development {
dataSource {
dbCreate = "create-drop" // one of 'create', 'create-drop','update'
url = "jdbc:hsqldb:mem:devDB"
}
}
development2 {
dataSource {
dbCreate = "create-drop" // one of 'create', 'create-drop','update'
url = "jdbc:hsqldb:hsql://localhost/test;shutdown=true"
}
}
development3 {
dataSource {
pooled = true
driverClassName = "org.postgresql.Driver"
username = "test"
password = "test"
dbCreate = "create-drop" // one of 'create', 'create-drop','update'
url = "jdbc:postgresql:test"
}
}
...
Testy dla HSQLDB wymagały uruchomienia aplikacji dla środowiska development2 :
grails -Dgrails.env=development2 run-app
Uruchomienie testów dla aplikacji bez slc dało średnią 893 medianę 861, a z slc średnią 406 medianę 379.
Analogicznie testy dla PostgreSQL wymagały uruchomienia aplikacji dla środowiska development3:
grails -Dgrails.env=development3 run-app
Uruchomienie testów dla aplikacji bez slc dało średnią 603 medianę 576, a z slc średnią 398 medianę 369.
Wnioski
W poniższej tabeli zebrałem wyniki wszystkich przeprowadzonych testów:
| HSQLDB (mem) | HSQLDB (standalone) | PostgreSQL | |
|---|---|---|---|
| bez slc | 208/184 | 893/861 | 603/576 |
| z slc | 140/124 | 406/379 | 398/369 |
Analizując wyniki testów dla aplikacji bez użycia slc można zauważyć dwie rzeczy:
- brak narzutów na połączenia sieciowe w przypadku bazy danych HSQLDB (mem) robi bardzo dużą różnice,
- silnik bazy danych PostgreSQL jest wydajniejszy niż silnik bazy danych HSQLDB (standalone)
Analizując wyniki testów dla aplikacji z użyciem slc można zauważyć również dwie rzeczy:
- brak narzutów na połączenia sieciowe w przypadku bazy danych HSQLDB (mem) robi mniejszą różnicę niż w przypadku braku slc, ale nadal dużą
- silnik bazy danych w przypadku baz danych uruchamianych w osobnym procesie nie ma wielkiego znaczenia, wyniki w przypadku baz danych HSQLDB (standalone) i PostgreSQL są zbliżone
Ze względu na obszerność tematu nie rozbijałem testów na poszczególne strategie slc (read-write, read-only, lazy, non-lazy). Świadomie pominąłem również wpływ aktualizacji slc na wydajność aplikacji.



Opublikował/a Tomasz Lempart