3 Almindelige problemer med dvaleydelse og hvordan man finder dem i din logfil

1. Introduktion

Du har sandsynligvis læst nogle af klagerne over dårlig dvaleydelse, eller måske har du selv kæmpet med nogle af dem. Jeg har brugt dvale i mere end 15 år nu, og jeg er stødt på mere end nok af disse problemer.

I årenes løb har jeg lært, at disse problemer kan undgås, og at du kan finde mange af dem i din logfil. I dette indlæg vil jeg vise dig, hvordan du kan finde og rette 3 af dem.

2. Find og rette ydelsesproblemer

2.1. Log SQL-sætninger i produktion

Det første præstationsproblem er ekstremt let at få øje på og ofte ignoreret. Det er logning af SQL-sætninger i et produktionsmiljø.

At skrive nogle logerklæringer lyder ikke som en big deal, og der er mange applikationer derude, der gør netop det. Men det er ekstremt ineffektivt, især via System.out.println som dvale gør det, hvis du indstiller show_sql parameter i din dvale-konfiguration til rigtigt:

Dvaletilstand: vælg ordre0_.id som id1_2_, ordre0_bestillingsnummer som ordreNum2_2_, ordre0_.version som version3_2_ fra køb Bestil ordre0_ Dvaletilstand: vælg varer0_.ordre_id som ordre_id4_0_0_, varer0_.id som id1_0_0_, varer0_id, id_0, id_0, id_0 varer0_.produkt_id som produkt_5_0_1_, varer0_.mængde som antal2_0_1_, varer0_.version som version3_0_1_ fra OrderItem-varer0_ hvor varer0_.order_id =? Dvaletilstand: vælg varer0_.order_id som order_id4_0_0_, items0_.id som id1_0_0_, items0_.id som id1_0_1_, items0_.order_id som order_id4_0_1_, items0_.product_id som product_5_0_1_, items0_.quantity som antal2_0_1. order_id =? Dvaletilstand: vælg varer0_.order_id som order_id4_0_0_, items0_.id som id1_0_0_, items0_.id som id1_0_1_, items0_.order_id som order_id4_0_1_, items0_.product_id som product_5_0_1_, items0_.quantity som antal2_0_1. order_id =?

I et af mine projekter forbedrede jeg præstationen med 20% inden for få minutter ved at indstille show_sql til falsk. Det er den slags præstation, du gerne vil rapportere til det næste stand-up-møde 🙂

Det er ret tydeligt, hvordan du kan løse dette præstationsproblem. Bare åbn din konfiguration (f.eks. Din persistence.xml-fil) og indstil show_sql parameter til falsk. Du har ikke brug for disse oplysninger under produktion under alle omstændigheder.

Men du har muligvis brug for dem under udviklingen. Hvis du ikke gør det, bruger du 2 forskellige dvale-konfigurationer (som du ikke burde), du deaktiverede også SQL-sætningslogning der. Løsningen til det er at bruge 2 forskellige logkonfigurationer til udvikling og produktion, der er optimeret til de specifikke krav i runtime-miljøet.

Udviklingskonfiguration

Udviklingskonfigurationen skal give så mange nyttige oplysninger som muligt, så du kan se, hvordan dvale interagerer med databasen. Du skal derfor i det mindste logge de genererede SQL-sætninger i din udviklingskonfiguration. Du kan gøre dette ved at aktivere FEJLFINDE besked til org.hibernate.SQL kategori. Hvis du også vil se værdierne for dine bindingsparametre, skal du indstille logniveauet for org.hibernate.type.descriptor.sql til SPOR:

log4j.appender.stdout = org.apache.log4j.ConsoleAppender log4j.appender.stdout.Target = System.out log4j.appender.stdout.layout = org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern =% d {HH: mm: ss, SSS}% -5p [% c] -% m% n log4j.rootLogger = info, stdout # grundlæggende logniveau for alle meddelelser log4j.logger.org.hibernate = info # SQL-sætninger og parametre log4j.logger.org.hibernate.SQL = fejlret log4j.logger.org.hibernate.type.descriptor.sql = spor

Følgende kodestykke viser nogle eksempler på logbeskeder, som Hibernate skriver med denne logkonfiguration. Som du kan se, får du detaljerede oplysninger om den udførte SQL-forespørgsel og alle indstillede og hentede parameterværdier:

23: 03: 22,246 DEBUG SQL: 92 - vælg order0_.id som id1_2_, order0_.orderNumber som orderNum2_2_, order0_.version som version3_2_ fra køb Bestil ordre0_ hvor order0_.id = 1 23: 03: 22,254 TRACE BasicExtractor: 61 - ekstraheret værdi ( [id1_2_]: [BIGINT]) - [1] 23: 03: 22,261 TRACE BasicExtractor: 61 - ekstraheret værdi ([orderNum2_2_]: [VARCHAR]) - [order1] 23: 03: 22,263 TRACE BasicExtractor: 61 - ekstraheret værdi ( [version3_2_]: [INTEGER]) - [0]

Dvaletilstand giver dig meget mere intern information om en Session hvis du aktiverer dvale-statistikken. Du kan gøre dette ved at indstille systemegenskaben hibernate.generate_statistics til sandt.

Men bedes du kun aktivere statistikkerne for dit udviklings- eller testmiljø. Indsamling af alle disse oplysninger bremser din applikation, og du kan muligvis oprette dine præstationsproblemer selv, hvis du aktiverer det i produktionen.

Du kan se nogle eksempler på statistikker i følgende kodestykke:

23: 04: 12,123 INFO StatisticalLoggingSessionEventListener: 258 - Session Metrics {23793 nanosekunder brugt på at erhverve 1 JDBC-forbindelser; 0 nanosekunder brugt på frigivelse af 0 JDBC-forbindelser; 394686 nanosekunder brugt på at forberede 4 JDBC-udsagn; 2528603 nanosekunder brugt til at udføre 4 JDBC-udsagn; 0 nanosekunder brugt på at udføre 0 JDBC-batches; 0 nanosekunder brugt til at udføre 0 L2C sætter; 0 nanosekunder brugt på at udføre 0 L2C hits; 0 nanosekunder brugt til at udføre 0 L2C-misser; 9700599 nanosekunder brugt på at udføre 1 skylninger (skylning i alt 9 enheder og 3 samlinger); 42921 nanosekunder brugt på at udføre 1 del-skylninger (skylning i alt 0 enheder og 0 samlinger)}

Jeg bruger regelmæssigt disse statistikker i mit daglige arbejde til at finde præstationsproblemer, før de opstår i produktionen, og jeg kunne skrive flere indlæg lige om det. Så lad os bare fokusere på de vigtigste.

Linjerne 2 til 5 viser dig, hvor mange JDBC-forbindelser og udsagn, dvale anvendte i løbet af denne session, og hvor meget tid det brugte på det. Du skal altid se på disse værdier og sammenligne dem med dine forventninger.

Hvis der er meget flere udsagn, end du havde forventet, har du sandsynligvis det mest almindelige ydeevneproblem, n + 1-valgproblemet. Du kan finde det i næsten alle applikationer, og det kan skabe enorme ydelsesproblemer i en større database. Jeg forklarer dette problem nærmere i det næste afsnit.

Linjerne 7 til 9 viser, hvordan dvale interagerede med cache på 2. niveau. Dette er en af ​​dvaleens 3 cacher, og den gemmer enheder på en sessionuafhængig måde. Hvis du bruger 2. niveau i din applikation, skal du altid overvåge disse statistikker for at se, om Dvaletilstand får enhederne derfra.

Produktionskonfiguration

Produktionskonfigurationen skal optimeres til ydeevne og undgå meddelelser, der ikke er presserende nødvendige. Generelt betyder det, at du kun skal logge fejlmeddelelser. Hvis du bruger Log4j, kan du opnå det med følgende konfiguration:

Hvis du bruger Log4j, kan du opnå det med følgende konfiguration:

log4j.appender.stdout = org.apache.log4j.ConsoleAppender log4j.appender.stdout.Target = System.out log4j.appender.stdout.layout = org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern =% d {HH: mm: ss, SSS}% -5p [% c] -% m% n log4j.rootLogger = info, stdout # grundlæggende logniveau for alle meddelelser log4j.logger.org.hibernate = fejl

2.2. N + 1 Vælg udgave

Som jeg allerede har forklaret, er n + 1-valgproblemet det mest almindelige ydeevneproblem. Mange udviklere bebrejder OR-Mapping-konceptet for dette problem, og de tager ikke helt fejl. Men du kan let undgå det, hvis du forstår, hvordan dvale behandler dovent hentede forhold. Derfor er udvikleren også skylden, fordi det er hans ansvar at undgå denne slags problemer. Så lad mig først forklare, hvorfor dette problem eksisterer, og derefter vise dig en nem måde at forhindre det på. Hvis du allerede er fortrolig med n + 1-valgproblemerne, kan du springe direkte til løsningen.

Dvaletilstand giver en meget bekvem kortlægning af forhold mellem enheder. Du har bare brug for en attribut med typen af ​​den relaterede enhed og et par kommentarer for at definere den:

@Entity @Table (name = "purchaseOrder") offentlig klasse Ordre implementerer Serialiserbar {@OneToMany (mappedBy = "ordre", fetch = FetchType.LAZY) privat Sæt emner = nyt HashSet (); ...}

Når du nu indlæser en Bestille enhed fra databasen, skal du bare ringe til getItems () metode til at få alle varer i denne ordre. Dvaletilstand skjuler de krævede databaseforespørgsler for at få det relaterede Bestillingsvare enheder fra databasen.

Da du startede med dvale, lærte du sandsynligvis, at du skulle bruge FetchType.LAZY for de fleste af forholdene, og at det er standard for mange forhold. Dette fortæller dvaletilstand at kun hente de relaterede enheder, hvis du bruger attributten, der kortlægger forholdet. At hente kun de data, du har brug for, er generelt en god ting, men det kræver også dvale for at udføre en ekstra forespørgsel for at initialisere hvert forhold. Dette kan resultere i et stort antal forespørgsler, hvis du arbejder på en liste over enheder, som jeg gør i følgende kodestykke:

Listeordrer = em.createQuery ("SELECT o FROM Order o"). GetResultList (); for (ordreordre: ordrer) {log.info ("ordre:" + ordre.getOrderNummer ()); log.info ("Antal varer:" + order.getItems (). størrelse ()); }

Du ville sandsynligvis ikke forvente, at disse få kodelinjer kan skabe hundreder eller endda tusinder af databaseforespørgsler. Men det gør det, hvis du bruger FetchType.LAZY for forholdet til Bestillingsvare enhed:

22: 47: 30,065 DEBUG SQL: 92 - vælg order0_.id som id1_2_, order0_.orderNumber som orderNum2_2_, order0_.version som version3_2_ fra køb Bestil ordre0_ 22: 47: 30,136 INFO NamedEntityGraphTest: 58 - Ordre: ordre1 22: 47: 30,140 DEBUG SQL: 92 - vælg items0_.order_id som order_id4_0_0_, items0_.id som id1_0_0_, items0_.id som id1_0_1_, items0_.order_id som order_id4_0_1_, items0_.product_id som product_5_0_1_, items0_.quantity som antal2_0_1_ items0_.order_id =? 22: 47: 30,171 INFO NamedEntityGraphTest: 59 - Antal varer: 2 22: 47: 30,171 INFO NamedEntityGraphTest: 58 - Ordre: rækkefølge2 22: 47: 30,172 DEBUG SQL: 92 - vælg items0_.order_id som order_id4_0_0_, items0_.id som id1_ , items0_.id som id1_0_1_, items0_.order_id som order_id4_0_1_, items0_.product_id som product_5_0_1_, items0_.mængde som antal2_0_1_, items0_.version som version3_0_1_ fra OrderItem items0_ hvor items0_.order_id =? 22: 47: 30,174 INFO NamedEntityGraphTest: 59 - Antal varer: 2 22: 47: 30,174 INFO NamedEntityGraphTest: 58 - Ordre: rækkefølge 3 22: 47: 30,174 DEBUG SQL: 92 - vælg items0_.order_id som order_id4_0_0_, items0_.id som id1_ , items0_.id som id1_0_1_, items0_.order_id som order_id4_0_1_, items0_.product_id som product_5_0_1_, items0_.mængde som antal2_0_1_, items0_.version som version3_0_1_ fra OrderItem items0_ hvor items0_.order_id =? 22: 47: 30,176 INFO NamedEntityGraphTest: 59 - Antal varer: 2

Dvaletilstand udfører en forespørgsel for at få alle Bestille enheder og en yderligere en for hver af n Bestille enheder til at initialisere orderItem forhold. Så du ved nu, hvorfor denne type problem kaldes n + 1 select issue, og hvorfor det kan skabe enorme ydeevneproblemer.

Hvad gør det endnu værre er, at du ofte ikke genkender det i en lille testdatabase, hvis du ikke har tjekket dine dvale-statistikker. Kodestykket kræver kun et par dusin forespørgsler, hvis testdatabasen ikke indeholder mange ordrer. Men det vil være helt anderledes, hvis du bruger din produktive database, der indeholder flere tusinde af dem.

Jeg sagde tidligere, at du let kan undgå disse problemer. Og det er sandt. Du skal bare initialisere orderItem-forholdet, når du vælger Bestille enheder fra databasen.

Men vær venlig at gøre det kun, hvis du bruger forholdet i din virksomhedskode og ikke bruger FetchType.EAGER for altid at hente de relaterede enheder. Det erstatter bare dit n + 1-problem med et andet præstationsproblem.

Initialiser et forhold til en @NamedEntityGraph

Der er flere forskellige muligheder for at initialisere relationer. Jeg foretrækker at bruge en @NamedEntityGraph hvilket er en af ​​mine yndlingsfunktioner introduceret i JPA 2.1. Det giver en forespørgselsuafhængig måde at specificere en graf over enheder, som dvale skal hente fra databasen. I følgende kodestykke kan du se et eksempel på en simpel graf, der gør det muligt for Hibernate at hente elementets attribut for en enhed:

@Entity @Table (name = "purchase_order") @NamedEntityGraph (name = "graph.Order.items", attributeNodes = @NamedAttributeNode ("items")) offentlig klasse Orden implementerer Serialiserbar {...}

Der er ikke meget, du skal gøre for at definere en enhedsgraf med en @NamedEntityGraph kommentar. Du skal bare angive et unikt navn til grafen og et @NamedAttributeNode kommentar for hver attribut Hibernate skal hente ivrigt. I dette eksempel er det kun elementattributten, der kortlægger forholdet mellem en Bestille og flere Bestillingsvare enheder.

Nu kan du bruge enhedsgrafen til at kontrollere hentningsadfærden eller en bestemt forespørgsel. Du er derfor nødt til at instantiere en EntityGraph baseret på @NamedEntityGraph definition og give det som et tip til EntityManager.find () metode eller din forespørgsel. Jeg gør dette i følgende kodestykke, hvor jeg vælger Bestille enhed med id 1 fra databasen:

EntityGraph-graf = this.em.getEntityGraph ("graf.Order.items"); Korttips = nyt HashMap (); hints.put ("javax.persistence.fetchgraph", graf); returner this.em.find (Order.class, 1L, tip);

Dvaletilstand bruger disse oplysninger til at oprette en SQL-sætning, der får attributterne til Bestille enhed og attributterne for enhedsgrafen fra databasen:

17: 34: 51,310 DEBUG [org.hibernate.loader.plan.build.spi.LoadPlanTreePrinter] (pool-2-thread-1) LoadPlan (entity = blog. Thoughts.on.java.jpa21.entity.graph.model. Rækkefølge) - Returnerer - EntityReturnImpl (entity = blog.thoughts.on.java.jpa21.entity.graph.model.Order, querySpaceUid =, path = blog. Thoughts.on.java.jpa21.entity.graph.model.Order) - CollectionAttributeFetchImpl (samling = blog. Thoughts.on.java.jpa21.entity.graph.model.Order.items, querySpaceUid =, sti = blog. Thoughts.on.java.jpa21.entity.graph.model.Order.items) - (indsamlingselement) CollectionFetchableElementEntityGraph (entity = blog.thoughts.on.java.jpa21.entity.graph.model.OrderItem, querySpaceUid =, path = blog.thoughts.on.java.jpa21.entity.graph.model.Order. varer.) - EntityAttributeFetchImpl (entity = blog.thoughts.on.java.jpa21.entity.graph.model.Product, querySpaceUid =, path = blog.thoughts.on.java.jpa21.entity.graph.model.Order.items ..produkt) - QuerySpaces - EntityQuerySpaceImpl (uid =, entity = blog.thoughts.on.java.jpa21.entity.graph.model .Order) - SQL-tabel alias kortlægning - order0_ - alias suffiks - 0_ - suffiks nøglekolonner - {id1_2_0_} - JOIN (JoinDefinedByMetadata (items)): -> - CollectionQuerySpaceImpl (uid =, collection = blog.thoughts.on.java. jpa21.entity.graph.model.Order.items) - SQL-tabel alias-kortlægning - items1_ - alias-suffiks - 1_ - suffiksede nøglekolonner - {order_id4_2_1_} - enheds-element alias-suffiks - 2_ - 2_entity-element suffixede nøglekolonner - id1_0_2_ - JOIN (JoinDefinedByMetadata (elementer)): -> - EntityQuerySpaceImpl (uid =, entity = blog.thoughts.on.java.jpa21.entity.graph.model.OrderItem) - SQL-tabel alias kortlægning - items1_ - alias suffiks - 2_ - suffiksed nøglekolonner - {id1_0_2_} - JOIN (JoinDefinedByMetadata (product)): -> - EntityQuerySpaceImpl (uid =, entity = blog.thoughts.on.java.jpa21.entity.graph.model.Product) - Kortlægning af SQL-tabel alias - product2_ - alias-suffiks - 3_ - suffikserede nøglekolonner - {id1_1_3_} 17: 34: 51,311 DEBUG [org.hibernate.loader.entity.plan.EntityLoader] (pool-2-thread-1) Statisk vælg f eller enhed blog. Thoughts.on.java.jpa21.entity.graph.model.Bestil [INGEN: -1]: vælg ordre0_.id som id1_2_0_, ordre0_.ordreNummer som ordrenummer2_2_0_, ordre0_.version som version3_2_0_, varer1_.ordre_id som ordre_id4_2_1_ , items1_.id som id1_0_1_, items1_.id som id1_0_2_, items1_.order_id as order_id4_0_2_, items1_.product_id as product_5_0_2_, items1_.quantity as quantity2_0_2_, items1_.version as version3_0_2_, product2_.id as product2_3 .version som version3_1_3_ fra indkøbsordre ordre0_ venstre ydre sammenføjning OrderItem items1_ på ordre0_.id = items1_.order_id venstre ydre sammenføjning Produkt produkt2_ på items1_.product_id = product2_.id hvor order0_.id =?

At initialisere kun ét forhold er godt nok til et blogindlæg, men i et rigtigt projekt vil du højst sandsynligt gerne oprette mere komplekse grafer. Så lad os gøre det.

Du kan selvfølgelig give en række @NamedAttributeNode annoteringer for at hente flere attributter for den samme enhed, og du kan bruge @NamedSubGraph for at definere hentningsadfærden for et yderligere niveau af enheder. Jeg bruger det i følgende kodestykke til at hente ikke kun alle relaterede Bestillingsvare enheder, men også Produkt enhed for hver Bestillingsvare:

@Entity @Table (name = "purchase_order") @NamedEntityGraph (name = "graph.Order.items", attributeNodes = @NamedAttributeNode (value = "items", subgraph = "items"), subgraphs = @NamedSubgraph (name = " items ", attributeNodes = @NamedAttributeNode (" product "))) offentlig klasse Order implementerer Serialiserbar {...}

Som du kan se, er definitionen af ​​en @NamedSubGraph svarer meget til definitionen af ​​a @NamedEntityGraph. Du kan derefter henvise til denne undergraf i en @NamedAttributeNode kommentar for at definere hentningsadfærd for denne specifikke attribut.

Kombinationen af ​​disse bemærkninger giver dig mulighed for at definere komplekse enhedsgrafer, som du kan bruge til at initialisere alle forhold, du bruger i din brugssag, og undgå n + 1 udvalgte problemer. Hvis du vil specificere din enhedsgraf dynamisk ved kørsel, kan du også gøre dette via en Java API.

2.3. Opdater enheder en efter en

Opdatering af enheder en efter en føles meget naturligt, hvis du tænker objektorienteret. Du får bare de enheder, du vil opdatere, og kalder et par settermetoder for at ændre deres attributter, som om du gør det med ethvert andet objekt.

Denne tilgang fungerer fint, hvis du kun ændrer et par enheder.Men det bliver meget ineffektivt, når du arbejder med en liste over enheder, og det er den tredje præstationsproblemer, du nemt kan få øje på i din logfil. Du skal bare kigge efter en masse SQL UPDATE-udsagn, der ser helt ens ud, som du kan se i følgende logfil:

22: 58: 05,829 DEBUG SQL: 92 - vælg produkt0_.id som id1_1_, produkt0_.navn som navn2_1_, produkt0_.pris som pris3_1_, produkt0_.version som version4_1_ fra Produktprodukt0_ 22: 58: 05.883 DEBUG SQL: 92 - opdater Produktsæt navn = ?, pris = ?, version =? hvor id =? og version =? 22: 58: 05,889 DEBUG SQL: 92 - opdater Produkt sæt navn = ?, pris = ?, version =? hvor id =? og version =? 22: 58: 05,891 DEBUG SQL: 92 - opdater Produkt sæt navn = ?, pris = ?, version =? hvor id =? og version =? 22: 58: 05,893 DEBUG SQL: 92 - opdater Produkt sæt navn = ?, pris = ?, version =? hvor id =? og version =? 22: 58: 05,900 DEBUG SQL: 92 - opdater Produkt sæt navn = ?, pris = ?, version =? hvor id =? og version =?

Den relationelle repræsentation af databaseoptegnelserne passer meget bedre til disse brugssager end den objektorienterede. Med SQL kan du bare skrive en SQL-sætning, der opdaterer alle de poster, du vil ændre.

Du kan gøre det samme med dvale, hvis du bruger JPQL, native SQL eller CriteriaUpdate API. Alle 3 meget ens, så lad os bruge JPQL i dette eksempel.

Du kan definere en JPQL UPDATE-sætning på en lignende måde, som du kender den fra SQL. Du definerer bare, hvilken enhed du vil opdatere, hvordan du ændrer værdierne for dens attributter og begrænser de berørte enheder i WHERE-sætningen.

Du kan se et eksempel på det i følgende kodestykke, hvor jeg hæver prisen på alle produkter med 10%:

em.createQuery ("UPDATE Product p SET p.price = p.price * 0.1"). executeUpdate ();

Dvaletilstand opretter en SQL UPDATE-sætning baseret på JPQL-sætningen og sender den til den database, der udfører opdateringsfunktionen.

Det er ret indlysende, at denne tilgang er meget hurtigere, hvis du skal opdatere et stort antal enheder. Men det har også en ulempe. Dvaletilstand ved ikke, hvilke enheder der er påvirket af opdateringsoperationen og opdaterer ikke dens cache på første niveau. Du skal derfor sørge for ikke at læse og opdatere en enhed med en JPQL-sætning inden for den samme dvale-session, eller du skal fjerne den for at fjerne den fra cachen.

3. Resume

Inden for dette indlæg har jeg vist dig 3 Dvale præstationsproblemer, som du kan finde i dine logfiler.

2 af dem var forårsaget af et stort antal SQL-udsagn. Dette er en almindelig årsag til ydeevneproblemer, hvis du arbejder med dvale. Dvaletilstand skjuler databaseadgangen bag sin API, og det gør det ofte svært at gætte det faktiske antal SQL-sætninger. Du bør derfor altid kontrollere de udførte SQL-sætninger, når du foretager en ændring i dit persistensniveau.


$config[zx-auto] not found$config[zx-overlay] not found