Skip to content

Commit c9a0d6e

Browse files
committed
HHH-19240 Test memory consumption of HQL parser
(cherry picked from commit 97d87c4)
1 parent a750aa5 commit c9a0d6e

File tree

1 file changed

+216
-0
lines changed

1 file changed

+216
-0
lines changed
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.orm.test.hql;
6+
7+
import jakarta.persistence.Entity;
8+
import jakarta.persistence.FetchType;
9+
import jakarta.persistence.Id;
10+
import jakarta.persistence.ManyToOne;
11+
import jakarta.persistence.OneToMany;
12+
import jakarta.persistence.Table;
13+
import org.hibernate.cfg.QuerySettings;
14+
import org.hibernate.query.hql.HqlTranslator;
15+
import org.hibernate.query.sqm.tree.SqmStatement;
16+
import org.hibernate.testing.orm.junit.DomainModel;
17+
import org.hibernate.testing.orm.junit.Jira;
18+
import org.hibernate.testing.orm.junit.ServiceRegistry;
19+
import org.hibernate.testing.orm.junit.SessionFactory;
20+
import org.hibernate.testing.orm.junit.SessionFactoryScope;
21+
import org.hibernate.testing.orm.junit.Setting;
22+
import org.junit.jupiter.api.Test;
23+
24+
import java.util.Set;
25+
26+
import static org.junit.jupiter.api.Assertions.assertTrue;
27+
28+
@DomainModel(
29+
annotatedClasses = {
30+
HqlParserMemoryUsageTest.Address.class,
31+
HqlParserMemoryUsageTest.AppUser.class,
32+
HqlParserMemoryUsageTest.Category.class,
33+
HqlParserMemoryUsageTest.Discount.class,
34+
HqlParserMemoryUsageTest.Order.class,
35+
HqlParserMemoryUsageTest.OrderItem.class,
36+
HqlParserMemoryUsageTest.Product.class
37+
}
38+
)
39+
@SessionFactory
40+
@ServiceRegistry(settings = @Setting(name = QuerySettings.QUERY_PLAN_CACHE_ENABLED, value = "false"))
41+
@Jira("https://hibernate.atlassian.net/browse/HHH-19240")
42+
public class HqlParserMemoryUsageTest {
43+
44+
private static final String HQL = """
45+
SELECT DISTINCT u.id
46+
FROM AppUser u
47+
LEFT JOIN u.addresses a
48+
LEFT JOIN u.orders o
49+
LEFT JOIN o.orderItems oi
50+
LEFT JOIN oi.product p
51+
LEFT JOIN p.discounts d
52+
WHERE u.id = :userId
53+
AND (
54+
CASE
55+
WHEN u.name = 'SPECIAL_USER' THEN TRUE
56+
ELSE (
57+
CASE
58+
WHEN a.city = 'New York' THEN TRUE
59+
ELSE (
60+
p.category.name = 'Electronics'
61+
OR d.code LIKE '%DISC%'
62+
OR u.id IN (
63+
SELECT u2.id
64+
FROM AppUser u2
65+
JOIN u2.orders o2
66+
JOIN o2.orderItems oi2
67+
JOIN oi2.product p2
68+
WHERE p2.price > (
69+
SELECT AVG(p3.price) FROM Product p3
70+
)
71+
)
72+
)
73+
END
74+
)
75+
END
76+
)
77+
""";
78+
79+
80+
@Test
81+
public void testParserMemoryUsage(SessionFactoryScope scope) {
82+
final HqlTranslator hqlTranslator = scope.getSessionFactory().getQueryEngine().getHqlTranslator();
83+
final Runtime runtime = Runtime.getRuntime();
84+
85+
// Ensure classes and basic stuff is initialized in case this is the first test run
86+
hqlTranslator.translate( "from AppUser", AppUser.class );
87+
runtime.gc();
88+
runtime.gc();
89+
90+
// Track memory usage before execution
91+
long totalMemoryBefore = runtime.totalMemory();
92+
long usedMemoryBefore = totalMemoryBefore - runtime.freeMemory();
93+
94+
System.out.println("Memory Usage Before Create Query:");
95+
System.out.println("----------------------------");
96+
System.out.println("Total Memory: " + (totalMemoryBefore / 1024) + " KB");
97+
System.out.println("Used Memory : " + (usedMemoryBefore / 1024) + " KB");
98+
System.out.println();
99+
100+
// Create query
101+
SqmStatement<Long> statement = hqlTranslator.translate( HQL, Long.class );
102+
103+
// Track memory usage after execution
104+
long totalMemoryAfter = runtime.totalMemory();
105+
long usedMemoryAfter = totalMemoryAfter - runtime.freeMemory();
106+
107+
System.out.println("Memory Usage After Create Query:");
108+
System.out.println("----------------------------");
109+
System.out.println("Total Memory: " + (totalMemoryAfter / 1024) + " KB");
110+
System.out.println("Used Memory : " + (usedMemoryAfter / 1024) + " KB");
111+
System.out.println();
112+
113+
System.out.println("Memory increase After Parsing:");
114+
System.out.println("----------------------------");
115+
System.out.println("Total Memory increase: " + ((totalMemoryAfter - totalMemoryBefore) / 1024) + " KB");
116+
System.out.println("Used Memory increase : " + ((usedMemoryAfter - usedMemoryBefore) / 1024) + " KB");
117+
System.out.println();
118+
119+
runtime.gc();
120+
runtime.gc();
121+
122+
// Track memory usage after execution
123+
long totalMemoryAfterGc = runtime.totalMemory();
124+
long usedMemoryAfterGc = totalMemoryAfterGc - runtime.freeMemory();
125+
126+
System.out.println("Memory Usage After Create Query and GC:");
127+
System.out.println("----------------------------");
128+
System.out.println("Total Memory: " + (totalMemoryAfterGc / 1024) + " KB");
129+
System.out.println("Used Memory : " + (usedMemoryAfterGc / 1024) + " KB");
130+
System.out.println();
131+
132+
System.out.println("Memory overhead of Parsing:");
133+
System.out.println("----------------------------");
134+
System.out.println("Total Memory increase: " + ((totalMemoryAfter - totalMemoryAfterGc) / 1024) + " KB");
135+
System.out.println("Used Memory increase : " + ((usedMemoryAfter - usedMemoryAfterGc) / 1024) + " KB");
136+
System.out.println();
137+
138+
// During testing, before the fix for HHH-19240, the allocation was around 500+ MB,
139+
// and after the fix it dropped to 170 - 250 MB
140+
final long memoryConsumption = usedMemoryAfter - usedMemoryAfterGc;
141+
assertTrue( usedMemoryAfter - usedMemoryAfterGc < 256_000_000, "Parsing of queries consumes too much memory (" + ( memoryConsumption / 1024 ) + " KB), when at most 256 MB are expected" );
142+
}
143+
144+
@Entity(name = "Address")
145+
@Table(name = "addresses")
146+
public static class Address {
147+
@Id
148+
private Long id;
149+
private String city;
150+
@ManyToOne(fetch = FetchType.LAZY)
151+
private AppUser user;
152+
}
153+
@Entity(name = "AppUser")
154+
@Table(name = "app_users")
155+
public static class AppUser {
156+
@Id
157+
private Long id;
158+
private String name;
159+
@OneToMany(mappedBy = "user")
160+
private Set<Address> addresses;
161+
@OneToMany(mappedBy = "user")
162+
private Set<Order> orders;
163+
}
164+
165+
@Entity(name = "Category")
166+
@Table(name = "categories")
167+
public static class Category {
168+
@Id
169+
private Long id;
170+
private String name;
171+
}
172+
173+
@Entity(name = "Discount")
174+
@Table(name = "discounts")
175+
public static class Discount {
176+
@Id
177+
private Long id;
178+
private String code;
179+
@ManyToOne(fetch = FetchType.LAZY)
180+
private Product product;
181+
}
182+
183+
@Entity(name = "Order")
184+
@Table(name = "orders")
185+
public static class Order {
186+
@Id
187+
private Long id;
188+
@ManyToOne(fetch = FetchType.LAZY)
189+
private AppUser user;
190+
@OneToMany(mappedBy = "order")
191+
private Set<OrderItem> orderItems;
192+
}
193+
@Entity(name = "OrderItem")
194+
@Table(name = "order_items")
195+
public static class OrderItem {
196+
@Id
197+
private Long id;
198+
@ManyToOne(fetch = FetchType.LAZY)
199+
private Order order;
200+
@ManyToOne(fetch = FetchType.LAZY)
201+
private Product product;
202+
}
203+
204+
@Entity(name = "Product")
205+
@Table(name = "products")
206+
public static class Product {
207+
@Id
208+
private Long id;
209+
private String name;
210+
private Double price;
211+
@ManyToOne(fetch = FetchType.LAZY)
212+
private Category category;
213+
@OneToMany(mappedBy = "product")
214+
private Set<Discount> discounts;
215+
}
216+
}

0 commit comments

Comments
 (0)