mastering jpa performance - java forum stuttgart · das problem 2 6. juli 2017 java forum stuttgart...
TRANSCRIPT
BASEL BERN BRUGG DÜSSELDORF FRANKFURT A.M. FREIBURG I.BR. GENEVA
HAMBURG COPENHAGEN LAUSANNE MUNICH STUTTGART VIENNA ZURICH
Mastering JPA Performance
Thomas BröllPrincipal ConsultantTrivadis GmbH, Stuttgart
Das Problem
Java Forum Stuttgart - Mastering JPA Performance2 6. Juli 2017
Langsame Anwendung, Java, Datenbankzugriffe mit JPA/Hibernate
Entwickler: Geringe Auslastung am Applikationsserver
– Die Datenbank ist das Problem!
DBA: Geringe Auslastung der Datenbank
– Die Datenbank ist nicht das Problem!
Netzwerk auch nicht am Limit!
DatabaseApplication
Server
Clients
(Web)
Test Szenario
Java Forum Stuttgart - Mastering JPA Performance3 6. Juli 2017
Schreibe Daten aus CSV
Kleine Objekte, 3 Ebenen
Keine Optimierungen
Lokale in-Memory-Datenbank (javadb)
Spring Boot
State
City
Postal
Code
1
1
n
n
13381
10585
16
Java Code
Java Forum Stuttgart - Mastering JPA Performance5 6. Juli 2017
City c = …em.persist(c);
Service
@Entitypublic class City {@GeneratedValue @Id long id; …
Entity
Code – ?? Sekunden
Java Forum Stuttgart - Mastering JPA Performance6 6. Juli 2017
StreamSupport.stream(CSVFormat.DEFAULT.withFirstRecordAsHeader().parse(fr).spliterator(), false).map(record -> {
State state = intern (new State(record.get("bundesland")), states, State::getName, s -> {em.persist(s);
});City city = intern (new City(record.get("ort")), cities, City::getName, c -> {
c.setState(state);em.persist(c);
});
PostalCode postalCode = new PostalCode(record.get("plz"));postalCode.setCity(city);return new ImmutableTriple<>(state, city, postalCode);
})//.forEach(triple -> {
em.persist(triple.getRight());});
Aus Sicht der Datenbank
Java Forum Stuttgart - Mastering JPA Performance8 6. Juli 2017
DB Monitoring
SQL Statements & Transaktionen
CPU
I/O
Top 10 statements (Anzahl)
Top 10 statements (Ausführungszeit)
Aus Sicht der Java VM
Java Forum Stuttgart - Mastering JPA Performance10 6. Juli 2017
Java VM
JVisualVM / jstack
JDBC Monitoring
JVM Profiling
Total/used memory
Methoden hotspots (Ausführungszeit)
Logging
JVM Analyse 1
Java Forum Stuttgart - Mastering JPA Performance11 6. Juli 2017
"main" #1 prio=5 os_prio=0 tid=0x00000000023ce800 nid=0x1534 runnable [0x000000000266c000]
java.lang.Thread.State: RUNNABLE
at java.net.SocketInputStream.socketRead0(Native Method)at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
at java.net.SocketInputStream.read(SocketInputStream.java:170)
at java.net.SocketInputStream.read(SocketInputStream.java:141)
at com.mysql.cj.core.io.ReadAheadInputStream.fill(ReadAheadInputStream.java:101)at com.mysql.cj.core.io.ReadAheadInputStream.readFromUnderlyingStreamIfNecessary(ReadAheadInputStream.java:144)
at com.mysql.cj.core.io.ReadAheadInputStream.read(ReadAheadInputStream.java:174)
- locked <0x00000006c5bb1b58> (a com.mysql.cj.core.io.ReadAheadInputStream)
at java.io.FilterInputStream.read(FilterInputStream.java:133)
at com.mysql.cj.core.io.FullReadInputStream.readFully(FullReadInputStream.java:58)
at com.mysql.cj.mysqla.io.SimplePacketReader.readHeader(SimplePacketReader.java:60)
at com.mysql.cj.mysqla.io.TimeTrackingPacketReader.readHeader(TimeTrackingPacketReader.java:48)
at com.mysql.cj.mysqla.io.MultiPacketReader.readHeader(MultiPacketReader.java:51)
at com.mysql.cj.mysqla.io.MysqlaProtocol.readPacket(MysqlaProtocol.java:521)
at com.mysql.cj.mysqla.io.MysqlaProtocol.checkErrorPacket(MysqlaProtocol.java:723)
at com.mysql.cj.mysqla.io.MysqlaProtocol.sendCommand(MysqlaProtocol.java:662)
at com.mysql.cj.mysqla.io.MysqlaProtocol.sqlQueryDirect(MysqlaProtocol.java:950)
at com.mysql.cj.mysqla.MysqlaSession.sqlQueryDirect(MysqlaSession.java:431)
at com.mysql.cj.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:1974)
- locked <0x00000006c5b76500> (a com.mysql.cj.jdbc.ConnectionImpl)
at com.mysql.cj.jdbc.ConnectionImpl.commit(ConnectionImpl.java:1196)
- locked <0x00000006c5b76500> (a com.mysql.cj.jdbc.ConnectionImpl)
at sun.reflect.GeneratedMethodAccessor38.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.apache.tomcat.jdbc.pool.ProxyConnection.invoke(ProxyConnection.java:126)
at org.apache.tomcat.jdbc.pool.JdbcInterceptor.invoke(JdbcInterceptor.java:108)
at org.apache.tomcat.jdbc.pool.DisposableConnectionFacade.invoke(DisposableConnectionFacade.java:81)
at com.sun.proxy.$Proxy86.commit(Unknown Source)
at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcIsolationDelegate.delegateWork(JdbcIsolationDelegate.java:59)
at
org.hibernate.id.enhanced.TableGenerator$1.getNextValue(TableGenerator.java:530)at org.hibernate.id.enhanced.NoopOptimizer.generate(NoopOptimizer.java:40)
at org.hibernate.id.enhanced.TableGenerator.generate(TableGenerator.java:526)
at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:101)
at org.hibernate.jpa.event.internal.core.JpaPersistEventListener.saveWithGeneratedId(JpaPersistEventListener.java:67)
at org.hibernate.event.internal.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:189)
at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:132)
at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:58)
at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:775)
at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:748)
at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:753)
at org.hibernate.jpa.spi.AbstractEntityManagerImpl.persist(AbstractEntityManagerImpl.java:1146)
at sun.reflect.GeneratedMethodAccessor37.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:298)
JVM stack (7 von 10)
Lösung: * Multi ID Generator
Java Forum Stuttgart - Mastering JPA Performance12 6. Juli 2017
Auto-ID Generator erzeugt viele Abfragen
Transaktion wartet synchron
Databank-generierte ID-Spalten
– Sind keine Lösung (JDBC Limit)
Multi ID Generator!
– Ungenutzte IDs
– Keine streng monotone Vergabe der Ids mehr
UUID-Generator ?
@TableGenerator(name = "city", initialValue = 1000, allocationSize = Constants.ALLOCATION_SIZE, table = "sequences", pkColumnName = "name", valueColumnName = "value")@GeneratedValue(generator="city")@Id long id;
Table Generator mit AllocationSize!
Java Forum Stuttgart - Mastering JPA Performance13 6. Juli 2017
0
2000
4000
6000
8000
10000
12000
14000
0
10000
20000
30000
40000
50000
60000
70000
80000
1 2 4 8 16
Allocation vs. time
Statements
time
Der gleiche Code – 6.2 Sekunden
Java Forum Stuttgart - Mastering JPA Performance14 6. Juli 2017
StreamSupport.stream(CSVFormat.DEFAULT.withFirstRecordAsHeader().parse(fr).spliterator(), false).map(record -> {
State state = intern (new State(record.get("bundesland")), states, State::getName, s -> {em.persist(s);
});City city = intern (new City(record.get("ort")), cities, City::getName, c -> {
c.setState(state);em.persist(c);
});
PostalCode postalCode = new PostalCode(record.get("plz"));postalCode.setCity(city);return new ImmutableTriple<>(state, city, postalCode);
})//.forEach(triple -> {
em.persist(triple.getRight());});
Analyse 2
Java Forum Stuttgart - Mastering JPA Performance15 6. Juli 2017
"main" #1 prio=5 os_prio=0 tid=0x00000000017be800 nid=0x29fc runnable [0x000000000315c000]
java.lang.Thread.State: RUNNABLE
at java.net.SocketInputStream.socketRead0(Native Method)at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
at java.net.SocketInputStream.read(SocketInputStream.java:170)
at java.net.SocketInputStream.read(SocketInputStream.java:141)
at com.mysql.cj.core.io.ReadAheadInputStream.fill(ReadAheadInputStream.java:101)
at com.mysql.cj.core.io.ReadAheadInputStream.readFromUnderlyingStreamIfNecessary(ReadAheadInputStream.java:144)
at com.mysql.cj.core.io.ReadAheadInputStream.read(ReadAheadInputStream.java:174)
- locked <0x00000006c5a99ea8> (a com.mysql.cj.core.io.ReadAheadInputStream)
at java.io.FilterInputStream.read(FilterInputStream.java:133)
at com.mysql.cj.core.io.FullReadInputStream.readFully(FullReadInputStream.java:58)at com.mysql.cj.mysqla.io.SimplePacketReader.readHeader(SimplePacketReader.java:60)
at com.mysql.cj.mysqla.io.TimeTrackingPacketReader.readHeader(TimeTrackingPacketReader.java:48)
at com.mysql.cj.mysqla.io.MultiPacketReader.readHeader(MultiPacketReader.java:51)
at com.mysql.cj.mysqla.io.MysqlaProtocol.readPacket(MysqlaProtocol.java:521)
at com.mysql.cj.mysqla.io.MysqlaProtocol.checkErrorPacket(MysqlaProtocol.java:723)
at com.mysql.cj.mysqla.io.MysqlaProtocol.sendCommand(MysqlaProtocol.java:662)
at com.mysql.cj.mysqla.io.MysqlaProtocol.sqlQueryDirect(MysqlaProtocol.java:950)
at com.mysql.cj.mysqla.MysqlaSession.sqlQueryDirect(MysqlaSession.java:431)
at com.mysql.cj.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:1978)
- locked <0x00000006c59fd458> (a com.mysql.cj.jdbc.ConnectionImpl)
at com.mysql.cj.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:1826)
- locked <0x00000006c59fd458> (a com.mysql.cj.jdbc.ConnectionImpl)
at com.mysql.cj.jdbc.PreparedStatement.executeUpdateInternal(PreparedStatement.java:2034)
- locked <0x00000006c59fd458> (a com.mysql.cj.jdbc.ConnectionImpl)
at com.mysql.cj.jdbc.PreparedStatement.executeUpdateInternal(PreparedStatement.java:1970)
- locked <0x00000006c59fd458> (a com.mysql.cj.jdbc.ConnectionImpl)
at com.mysql.cj.jdbc.PreparedStatement.executeLargeUpdate(PreparedStatement.java:4984)
at com.mysql.cj.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:1955)
at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:204)
at
org.hibernate.engine.jdbc.batch.internal.NonBatchingBatch.addToBatch(NonB
atchingBatch.java:45)at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2897)
at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:3397)
at org.hibernate.action.internal.EntityInsertAction.execute(EntityInsertAction.java:89)
at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:582)
at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:456)
at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:337)
at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:39)
JVM stack
Batch Optimierung
Java Forum Stuttgart - Mastering JPA Performance16 6. Juli 2017
spring.jpa.properties.hibernate.jdbc.batch_size=100
Aktiviere JDBC Batch
0
1000
2000
3000
4000
5000
1 2 4 8 16
Batch size vs. time
Time
Batch optimierter Code – 1.9 Sekunden!!
Java Forum Stuttgart - Mastering JPA Performance17 6. Juli 2017
List<ImmutableTriple<State,City,PostalCode>> triples = StreamSupport.stream(CSVFormat.DEFAULT.withFirstRecordAsHeader().parse(fr).spliterator(), false).map(record -> {
State state = intern (new State(record.get("bundesland")), states, State::getName, s -> {em.persist(s);
});City city = intern (new City(record.get("ort")), cities, City::getName, c -> {
c.setState(state);em.persist(c);
});
PostalCode postalCode = new PostalCode(record.get("plz"));postalCode.setCity(city);return new ImmutableTriple<>(state, city, postalCode);
})//.collect(Collectors.toList());
triples.stream().forEach(triple -> em.persist(triple.getRight()));
Gebrochene JDBC batches
Java Forum Stuttgart - Mastering JPA Performance18 6. Juli 2017
Bester Fall: 3 JDBC batches
States brechen Batch für City 16 *
Cities brechen PostalCode 10585 *
16 Unterbrechungen haben keinen
signifikanten Einfluss, aber die 10585!
1:n Beziehung ist nahezu 1:1
State
City
Postal
Code
1
1
n
n
13381
10585
16
Lösung: Batch Sortierung
Java Forum Stuttgart - Mastering JPA Performance20 6. Juli 2017
Sortiere (INSERT) Operationen für JDBC batch
Batch wird gebrochen durch
– Queries
– Flush()
– Persistieren einer anderen Entität
– Identity columns (database level)
Entity Lifecycle
Java Forum Stuttgart - Mastering JPA Performance22 6. Juli 2017
...
city.setName(„newName“)
} // update & commit on exit
Automagisch? Optimiert?
Managed
RemovedDetached
New
Merge (Anti-Pattern)
Java Forum Stuttgart - Mastering JPA Performance23 6. Juli 2017
TestEntity te = …
TestEntity merged = em.merge(te);
Merge führt zuerst SELECT, dann UPDATE aus
SELECT – COPY – RETURN
https://docs.jboss.org/hibernate/orm/current/userguide/html_single/Hibernate_User_Gui
de.html#pc-cascade-merge
Großer Persistence-Context
Java Forum Stuttgart - Mastering JPA Performance24 6. Juli 2017
Große Anzahl Instanzen/Attribute
EntityManager führt „dirty check“ für jeden flush() aus
Performance hängt von Implementierung ab
– Equals
– „Dirty“ Flag
(Anti-)Pattern: flush & clear (ok für große Mengen)
Java Forum Stuttgart - Mastering JPA Performance25 6. Juli 2017
em.clear()
Aufräumen – verwirft offene Änderungen
em.flush()
Schreibe alle Änderungen
Fremdschlüssel
Java Forum Stuttgart - Mastering JPA Performance26 6. Juli 2017
State s = em.getReference(State.class, 4711L);
City c = new City("Test getReference");c.setState(s);...
getReference führt keinen SELECT aus (verlässt sich auf referentielle Integrität)
Jeder Zugriff auf die Properties dieses Objekts führt zu einem SELECT auf die DB!
Eine Entität finden/laden
Java Forum Stuttgart - Mastering JPA Performance28 6. Juli 2017
City st = em.createNamedQuery("City.byName", City.class).setParameter("name", "Stuttgart").getSingleResult();
Query Ausführung
@Entity
@NamedQueries({@NamedQuery(name="City.byName", query="SELECT e FROM City e
WHERE e.name=:name")})
public class City { …
Query Definition
SQLs
Java Forum Stuttgart - Mastering JPA Performance29 6. Juli 2017
Hibernate: /* City.byName */ select city0_.id as id1_0_,
city0_.name as name2_0_, city0_.state_id as state_id3_0_ from city city0_ where
city0_.name=?Hibernate:
select state0_.id as id1_6_0_, state0_.name as name2_6_0_ from
state state0_ where
state0_.id=?
„1+n“-Problem
Java Forum Stuttgart - Mastering JPA Performance30 6. Juli 2017
EIN Statement aus der Anwendung
N Statements durch Hibernate
Objekte werden nachgeladen (n:1, 1:1)
Hierdurch entsteht indeterministisches Verhalten bzgl. der Laufzeit!
Wird teilweise auch durch das UI ausgelöst!
Lesezugriffe
Java Forum Stuttgart - Mastering JPA Performance31 6. Juli 2017
Sicht der Datenbank:
– „einfache“ Queries auf Tabellen, in der Regel alle Spalten
– Viele Abfragen auf Primärschlüssel
– Strukturell einfache Abfragen, viele Joins möglich
Collections (@OneToMany) sind Lazy
Referenzen auf Objekte sind (@ManyToOne)
– … lazy ist nur ein Hint!
Lazy (nur 1 Statement)
Java Forum Stuttgart - Mastering JPA Performance32 6. Juli 2017
/* City.byName */ select
city0_.id as id1_0_,
city0_.name as name2_0_,
city0_.state_id as state_id3_0_
from city city0_
where
city0_.name=?
@ManyToOne(fetch=FetchType.LAZY)State state;
In City
Eager (nur 1 Statement) – nicht für Abfragen!
Java Forum Stuttgart - Mastering JPA Performance33 6. Juli 2017
selectcity0_.id as id1_0_0_, city0_.name as name2_0_0_,city0_.state_id as state_id3_0_0_, state1_.id as id1_6_1_,state1_.name as name2_6_1_
from city city0_ left outer join state state1_ on city0_.state_id=state1_.id where city0_.id=?
@ManyToOne(fetch=FetchType.EAGER)State state;
In City
Lösung: Prefetch Query
Java Forum Stuttgart - Mastering JPA Performance34 6. Juli 2017
Eager & Lazy sind evtl. nicht ausreichend, Definition im Query möglich
Eager erzeugt große Ergebnismengen
Prefetch Query lädt abhängige Entitäten, nutzt den PersistenceContext als Cache
– Deterministische Anzahl SQL-Abfragen
– Kleinere Gesamtmenge (Zeilen & Spalten)
Beispiel: Lade mehrere Postleitzahlen mit Städten und BundesländernPrefetch: SELECT e.city.state FROM PostalCode e WHERE e.code IN (:codes)
Query: SELECT e FROM PostalCode e
JOIN FETCH e.city WHERE e.code IN (:codes)
Unabhängig von der Datenkonstellation immer exakt zwei SQL-Statements!
Lösung: Pagination
Java Forum Stuttgart - Mastering JPA Performance35 6. Juli 2017
Abhängig von der Implementierung (Datenbank-Dialekt)
Kleine Performance-Verbesserung
query.setFirstResult(0);
query.setMaxResults(10);
Reduziere die Gesamtergebnismenge
AppEngine: US, Database: US / Europe
Java Forum Stuttgart - Mastering JPA Performance38 6. Juli 2017
Database „ping“ - optimierter Code
Java Forum Stuttgart - Mastering JPA Performance39 6. Juli 2017
SELECT ohne Ergebnis, Roundtrip-Zeit
App Engine US-Central, MySQL Europe-West: 120ms (max: 215ms)
App Engine US-Central, MySQL US-Central: 1-2ms (max: 4-5ms)
Physikalisches Limit: 80ms (Lichtgeschwindigkeit in Glasfaser: 200.000 km/s)
Empfehlung: Mehr als eine Zone nutzen, aber in der gleichen Region bleiben
Cloud Messungen
Java Forum Stuttgart - Mastering JPA Performance40 6. Juli 2017
Scenario 10% (2456 rows) 100% (23.978 rows)
US->US 2.59s 15.9s
US->EU 264s = 4:24 ~43min
Der JDBC Treiber ...
Java Forum Stuttgart - Mastering JPA Performance42 6. Juli 2017
... Macht nicht unbedingt was wir ihm sagen
Optionen in der Dokumentation prüfen (herstellerspezifisch)
Konfigurationen bzgl. Performance testen
Der JDBC Treiber …
Java Forum Stuttgart - Mastering JPA Performance43 6. Juli 2017
Batch Update (mysql/mariadb)
Default:
mysql: rewriteBatchedStatements
mariadb: useBatchMultiSend (default=100)
JDBC Driver fetch size
Oracle: 10
mysql: *
Top-Down in die Datenbank
Java Forum Stuttgart - Mastering JPA Performance45 6. Juli 2017
Queries erhalten nicht automatisch den benötigten Index
Fremdschlüssel-Index für Löschungen
Index-Optimierung (Fetch from Index) nur bedingt möglich
Anti-Pattern: CASCADE
Java Forum Stuttgart - Mastering JPA Performance46 6. Juli 2017
Bietet das automatische Kaskadieren von Operationen im EntityManager
Vermeiden oder nur begrenzt nutzen!
Rekursives Problem
Keine Kontrolle der Anwendung
Keine Optimierungen möglich (z.B. Batch-Optimierung)
Langfristig nicht kontrollierbar
Caching (Anti-Pattern)
Java Forum Stuttgart - Mastering JPA Performance47 6. Juli 2017
Nutze Caching, wenn
Daten häufig gelesen und
Selten verändert werden
Alte Daten akzeptabel sind
– Vor allem in Cloud-/Container-Umgebungen
Ausreichend Speicher verfügbar ist
JPA 2.2 !
Java Forum Stuttgart - Mastering JPA Performance48 6. Juli 2017
Streams für Query-Ergebnisse
CDI (Attribute Converter)
@NamedQuery - @Repeatable
Java 8 Date & Time
Fazit
Java Forum Stuttgart - Mastering JPA Performance49 6. Juli 2017
JPA funktioniert!
SQL Roundtrips für einzelne Objekte sind teuer
Den gesamten Technologiestack betrachten
Die Probleme betreffen meist nur 5% aller Fälle
Das kann aber 95% der gesamten Probleme ausmachen!
Empfehlung: Jeder Anwendungsfall soll sich deterministisch verhalten!
Bewusstsein für die Infrastruktur hilft, Probleme vorab einzuschätzen
Fragen und AntwortenThomas Bröll
Principal Consultant
Tel. +49 162 2733918
@tvdtkb
6. Juli 2017 Java Forum Stuttgart - Mastering JPA Performance51