PostgreSQL
 sql >> Datenbank >  >> RDS >> PostgreSQL

Spring + Hibernate:Cache-Speichernutzung des Abfrageplans

Ich bin auch auf dieses Problem gestoßen. Es läuft im Grunde darauf hinaus, eine variable Anzahl von Werten in Ihrer IN-Klausel zu haben und Hibernate zu versuchen, diese Abfragepläne zwischenzuspeichern.

Es gibt zwei großartige Blogbeiträge zu diesem Thema. Der erste:

Verwendung von Hibernate 4.2 und MySQL in einem Projekt mit einer In-Klausel-Abfrage wie:select t from Thing t where t.id in (?)

Hibernate speichert diese analysierten HQL-Abfragen im Cache. Insbesondere die HibernateSessionFactoryImpl hat QueryPlanCache mit queryPlanCache undparameterMetadataCache . Dies stellte sich jedoch als Problem heraus, wenn die Anzahl der Parameter für die In-Klausel groß ist und variiert.

Diese Caches wachsen für jede einzelne Abfrage. Diese Abfrage mit 6000 Parametern ist also nicht dasselbe wie 6001.

Die Abfrage innerhalb der Klausel wird auf die Anzahl der Parameter in der Sammlung erweitert. Metadaten sind im Abfrageplan für jeden Parameter in der Abfrage enthalten, einschließlich eines generierten Namens wie x10_, x11_ usw.

Stellen Sie sich 4000 verschiedene Variationen in der Anzahl der Parameterzählungen innerhalb der Klausel vor, jede davon mit durchschnittlich 4000 Parametern. Die Abfrage-Metadaten für jeden Parameter summieren sich schnell im Speicher und füllen den Haufen, da es nicht müllgesammelt werden kann.

Dies wird fortgesetzt, bis alle verschiedenen Variationen in der Anzahl der Abfrageparameter zwischengespeichert sind oder die JVM keinen Heap-Speicher mehr hat und mit dem Throwing von java.lang.OutOfMemoryError:Java heap space.

beginnt

Das Vermeiden von In-Klauseln ist eine Option, ebenso wie die Verwendung einer festen Sammlungsgröße für den Parameter (oder zumindest einer kleineren Größe).

Informationen zum Konfigurieren der maximalen Cachegröße des Abfrageplans finden Sie in der Eigenschafthibernate.query.plan_cache_max_size , standardmäßig 2048 (einfach zu groß für Abfragen mit vielen Parametern).

Und zweitens (ebenfalls vom ersten referenziert):

Hibernate verwendet intern einen Cache, der HQL-Anweisungen (asstrings) Abfrageplänen zuordnet. Der Cache besteht aus einer Bounded Map, die standardmäßig auf 2048 Elemente begrenzt ist (konfigurierbar). Alle HQL-Abfragen werden durch diesen Cache geladen. Bei einem Miss wird der Eintrag automatisch dem Cache hinzugefügt. Dies macht es sehr anfällig für Thrashing - ein Szenario, in dem wir ständig neue Einträge in den Cache legen, ohne sie jemals wiederzuverwenden, und somit verhindern, dass der Cache irgendwelche Leistungsgewinne bringt (es fügt sogar etwas Cache-Management-Overhead hinzu). Um die Sache noch schlimmer zu machen, ist es schwer, diese Situation zufällig zu erkennen - Sie müssen den Cache explizit profilieren, um zu bemerken, dass Sie dort ein Problem haben. Ich werde später ein paar Worte darüber sagen, wie dies geschehen könnte.

Das Cache-Thrashing resultiert also aus neuen Abfragen, die mit hohen Raten generiert werden. Dies kann durch eine Vielzahl von Problemen verursacht werden. Die beiden häufigsten, die ich gesehen habe, sind - Fehler im Ruhezustand, die dazu führen, dass Parameter in der JPQL-Anweisung gerendert werden, anstatt als Parameter übergeben zu werden, und die Verwendung einer "in" -Klausel.

Aufgrund einiger obskurer Fehler im Ruhezustand gibt es Situationen, in denen Parameter nicht korrekt behandelt und in die JPQL-Abfrage gerendert werden (als Beispiel siehe HHH-6280). Wenn Sie eine Abfrage haben, die von solchen Fehlern betroffen ist und mit hohen Raten ausgeführt wird, wird Ihr Abfrageplan-Cache zerstört, da jede generierte JPQL-Abfrage fast eindeutig ist (z. B. IDs Ihrer Entitäten enthält).

Das zweite Problem liegt in der Art und Weise, wie Hibernate Abfragen mit einer "in"-Klausel verarbeitet (z. B. gib mir alle Personenentitäten, deren Firmen-ID-Feld eines von 1, 2, 10, 18 ist). Für jede eindeutige Anzahl von Parametern in der "in"-Klausel erzeugt Hibernate eine andere Abfrage - z. B. select x from Person x where x.company.id in (:id0_) für 1 Parameter select x from Person x where x.company.id in (:id0_, :id1_) für 2 Parameter und so weiter. Alle diese Abfragen werden in Bezug auf den Abfrageplan-Cache als unterschiedlich betrachtet, was wiederum zu Cachethrashing führt. Sie könnten dieses Problem wahrscheinlich umgehen, indem Sie eine Hilfsklasse schreiben, um nur eine bestimmte Anzahl von Parametern zu erzeugen - z. 1,10, 100, 200, 500, 1000. Wenn Sie beispielsweise 22 Parameter übergeben, wird eine Liste mit 100 Elementen zurückgegeben, wobei die 22 Parameter init enthalten sind und die restlichen 78 Parameter auf einen unmöglichen Wert gesetzt sind (z. B. -1 für IDs für Fremdschlüssel verwendet). Ich stimme zu, dass dies ein hässlicher Hack ist, aber die Arbeit erledigen könnte. Infolgedessen haben Sie nur noch höchstens 6 eindeutige Abfragen in Ihrem Cache und reduzieren so das Thrashing.

Wie finden Sie also heraus, dass Sie das Problem haben? Sie könnten zusätzlichen Code schreiben und Metriken mit der Anzahl der Einträge im Cache anzeigen, z. über JMX, die Protokollierung optimieren und die Protokolle analysieren usw. Wenn Sie die Anwendung nicht ändern möchten (oder können), können Sie einfach den Heap ausgeben und diese OQL-Abfrage darauf ausführen (z. B. mit mat):SELECT l.query.toString() FROM INSTANCEOF org.hibernate.engine.query.spi.QueryPlanCache$HQLQueryPlanKey l . Es gibt alle Abfragen aus, die sich derzeit in einem beliebigen Abfrageplan-Cache auf Ihrem Heap befinden. Es sollte ziemlich einfach zu erkennen sein, ob Sie von einem der oben genannten Probleme betroffen sind.

Was die Auswirkungen auf die Leistung betrifft, ist es schwer zu sagen, da es von zu vielen Faktoren abhängt. Ich habe eine sehr triviale Abfrage gesehen, die einen Overhead von 10-20 ms verursacht, der für die Erstellung eines neuen HQL-Abfrageplans aufgewendet wird. Wenn es irgendwo einen Cache gibt, muss es im Allgemeinen einen guten Grund dafür geben - ein Miss ist wahrscheinlich teuer, also sollten Sie versuchen, Misses so weit wie möglich zu vermeiden. Zu guter Letzt muss Ihre Datenbank auch große Mengen an eindeutigen SQL-Anweisungen verarbeiten – was dazu führt, dass sie diese analysiert und möglicherweise für jede einzelne unterschiedliche Ausführungspläne erstellt.