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 Forum Stuttgart - Mastering JPA Performance4 6. Juli 2017

Schreiben

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

Java Forum Stuttgart - Mastering JPA Performance7 6. Juli 2017

Problemanalyse

(allgemein)

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)

Oracle Workload repository report

Java Forum Stuttgart - Mastering JPA Performance9 6. Juli 2017

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

Oracle Workload repository report

Java Forum Stuttgart - Mastering JPA Performance19 6. Juli 2017

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)

Java Forum Stuttgart - Mastering JPA Performance21 6. Juli 2017

Updates

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!

Java Forum Stuttgart - Mastering JPA Performance27 6. Juli 2017

Lesen von Daten

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

Java Forum Stuttgart - Mastering JPA Performance36 6. Juli 2017

In der Cloud ?

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

43min ?

Java Forum Stuttgart - Mastering JPA Performance41 6. Juli 2017

43 min / 110ms ~ 23.500

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: *

Java Forum Stuttgart - Mastering JPA Performance44 6. Juli 2017

Mehr …

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

Java aktuell

Java Forum Stuttgart - Mastering JPA Performance50 6. Juli 2017

Fragen und AntwortenThomas Bröll

Principal Consultant

Tel. +49 162 2733918

[email protected]

@tvdtkb

6. Juli 2017 Java Forum Stuttgart - Mastering JPA Performance51


Top Related