Skip to content

Commit fb7c471

Browse files
committed
wip
1 parent e5ddae0 commit fb7c471

5 files changed

Lines changed: 32912 additions & 47 deletions

File tree

core/sail/lmdb/src/test/java/org/eclipse/rdf4j/sail/lmdb/benchmark/ThemeQueryBenchmark.java

Lines changed: 32 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -324,32 +324,6 @@ private File storeDirectory() {
324324
return new File(STORE_DIRECTORY, "complete");
325325
}
326326

327-
private void configureBenchmarkEstimatorProperties() {
328-
// previousEstimatorNominalEntries = System.getProperty(ESTIMATOR_NOMINAL_ENTRIES_PROPERTY);
329-
// if (previousEstimatorNominalEntries == null) {
330-
// System.setProperty(ESTIMATOR_NOMINAL_ENTRIES_PROPERTY, BENCHMARK_ESTIMATOR_NOMINAL_ENTRIES);
331-
// }
332-
// previousEstimatorSketchK = System.getProperty(ESTIMATOR_SKETCH_K_PROPERTY);
333-
// if (previousEstimatorSketchK == null) {
334-
// System.setProperty(ESTIMATOR_SKETCH_K_PROPERTY, BENCHMARK_ESTIMATOR_SKETCH_K);
335-
// }
336-
}
337-
338-
private void restoreBenchmarkEstimatorProperties() {
339-
// if (previousEstimatorNominalEntries == null) {
340-
// System.clearProperty(ESTIMATOR_NOMINAL_ENTRIES_PROPERTY);
341-
// } else {
342-
// System.setProperty(ESTIMATOR_NOMINAL_ENTRIES_PROPERTY, previousEstimatorNominalEntries);
343-
// }
344-
// previousEstimatorNominalEntries = null;
345-
// if (previousEstimatorSketchK == null) {
346-
// System.clearProperty(ESTIMATOR_SKETCH_K_PROPERTY);
347-
// } else {
348-
// System.setProperty(ESTIMATOR_SKETCH_K_PROPERTY, previousEstimatorSketchK);
349-
// }
350-
// previousEstimatorSketchK = null;
351-
}
352-
353327
private void captureQueryPlanSnapshot() throws IOException {
354328
var benchmarkQuery = ThemeQueryCatalog.benchmarkQueryFor(theme, z_queryIndex);
355329
var featureFlags = new FeatureFlagCollector()
@@ -489,25 +463,25 @@ public void explainQuery() {
489463

490464
// if(true) throw new IllegalStateException();
491465

492-
System.out.println("=== Explanation for theme " + themeName + " and query index " + z_queryIndex + " ===");
493-
System.out.println("Query:\n" + query);
494-
System.out.println();
495-
496-
try (SailRepositoryConnection connection = repository.getConnection()) {
497-
try (RepositoryResult<Statement> statements = connection.getStatements(null, RDF.TYPE,
498-
Values.iri("http://example.com/theme/library/Copy"))) {
499-
System.out.println("Count of statements with rdf:type http://example.com/theme/library/Copy: "
500-
+ statements.stream().count());
501-
}
502-
}
503-
504-
try (SailRepositoryConnection connection = repository.getConnection()) {
505-
try (RepositoryResult<Statement> statements = connection.getStatements(null,
506-
Values.iri("http://example.com/theme/library/locatedAt"), null)) {
507-
System.out.println("Count of statements with http://example.com/theme/library/locatedAt: "
508-
+ statements.stream().count());
509-
}
510-
}
466+
// System.out.println("=== Explanation for theme " + themeName + " and query index " + z_queryIndex + " ===");
467+
// System.out.println("Query:\n" + query);
468+
// System.out.println();
469+
//
470+
// try (SailRepositoryConnection connection = repository.getConnection()) {
471+
// try (RepositoryResult<Statement> statements = connection.getStatements(null, RDF.TYPE,
472+
// Values.iri("http://example.com/theme/library/Copy"))) {
473+
// System.out.println("Count of statements with rdf:type http://example.com/theme/library/Copy: "
474+
// + statements.stream().count());
475+
// }
476+
// }
477+
//
478+
// try (SailRepositoryConnection connection = repository.getConnection()) {
479+
// try (RepositoryResult<Statement> statements = connection.getStatements(null,
480+
// Values.iri("http://example.com/theme/library/locatedAt"), null)) {
481+
// System.out.println("Count of statements with http://example.com/theme/library/locatedAt: "
482+
// + statements.stream().count());
483+
// }
484+
// }
511485

512486
try {
513487
// QueryJoinOptimizer.REORDER_JOINS_WITH_SKETCHES = false;
@@ -524,12 +498,23 @@ public void explainQuery() {
524498
// }
525499
// QueryJoinOptimizer.REORDER_JOINS_WITH_SKETCHES = true;
526500
try (var connection = repository.getConnection()) {
527-
System.out.println("=== Explanation with join reordering enabled ===");
501+
System.out.println("=== Explanation Optimized ===");
528502
Explanation explain = connection.prepareTupleQuery(query).explain(Explanation.Level.Optimized);
529503
System.out.println(explain);
530504
System.out.println();
531505
TupleExpr tupleExpr = (TupleExpr) explain.tupleExpr();
532-
System.out.println("=== Rendered optimized TupleExpr with join reordering enabled ===");
506+
System.out.println("=== Rendered Optimized TupleExpr ===");
507+
System.out.println(new TupleExprIRRenderer().render(tupleExpr));
508+
System.out.println();
509+
510+
}
511+
try (var connection = repository.getConnection()) {
512+
System.out.println("=== Explanation Telemetry ===");
513+
Explanation explain = connection.prepareTupleQuery(query).explain(Explanation.Level.Telemetry);
514+
System.out.println(explain);
515+
System.out.println();
516+
TupleExpr tupleExpr = (TupleExpr) explain.tupleExpr();
517+
System.out.println("=== Rendered Telemetry TupleExpr ===");
533518
System.out.println(new TupleExprIRRenderer().render(tupleExpr));
534519
System.out.println();
535520
System.out.println();
Lines changed: 290 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Eclipse RDF4J contributors.
3+
*
4+
* All rights reserved. This program and the accompanying materials
5+
* are made available under the terms of the Eclipse Distribution License v1.0
6+
* which accompanies this distribution, and is available at
7+
* http://www.eclipse.org/org/documents/edl-v10.php.
8+
*
9+
* SPDX-License-Identifier: BSD-3-Clause
10+
*******************************************************************************/
11+
// Some portions generated by Codex
12+
package org.eclipse.rdf4j.sail.lmdb.benchmark;
13+
14+
import java.io.File;
15+
import java.io.FileInputStream;
16+
import java.io.FileOutputStream;
17+
import java.io.IOException;
18+
import java.util.Properties;
19+
20+
import org.apache.commons.io.FileUtils;
21+
import org.eclipse.rdf4j.benchmark.common.ThemeQueryCatalog;
22+
import org.eclipse.rdf4j.benchmark.rio.util.ThemeDataSetGenerator;
23+
import org.eclipse.rdf4j.benchmark.rio.util.ThemeDataSetGenerator.Theme;
24+
import org.eclipse.rdf4j.common.transaction.IsolationLevels;
25+
import org.eclipse.rdf4j.query.TupleQuery;
26+
import org.eclipse.rdf4j.query.TupleQueryResult;
27+
import org.eclipse.rdf4j.query.algebra.TupleExpr;
28+
import org.eclipse.rdf4j.query.explanation.Explanation;
29+
import org.eclipse.rdf4j.queryrender.sparql.TupleExprIRRenderer;
30+
import org.eclipse.rdf4j.repository.sail.SailRepository;
31+
import org.eclipse.rdf4j.repository.sail.SailRepositoryConnection;
32+
import org.eclipse.rdf4j.repository.util.RDFInserter;
33+
import org.eclipse.rdf4j.sail.lmdb.LmdbStore;
34+
import org.eclipse.rdf4j.sail.lmdb.config.LmdbStoreConfig;
35+
import org.openjdk.jmh.runner.RunnerException;
36+
37+
public class ThemeQueryExplain {
38+
39+
private static final String STORE_NAME = "lmdb";
40+
private static final String TARGET_DIRECTORY_ROOT = "core/sail/lmdb/";
41+
private static final File STORE_DIRECTORY;
42+
43+
static {
44+
File target = new File("target", "lmdb-theme-query-benchmark");
45+
if (target.getAbsolutePath().toLowerCase().contains(TARGET_DIRECTORY_ROOT)) {
46+
STORE_DIRECTORY = target;
47+
} else {
48+
// In case the benchmark is run from an IDE with a different working directory, we want to ensure the store
49+
// directory is still in the target directory of the project.
50+
STORE_DIRECTORY = new File(TARGET_DIRECTORY_ROOT + "target", "lmdb-theme-query-benchmark");
51+
}
52+
}
53+
54+
private static final String TRIPLES_DATA_FILE = "triples/data.mdb";
55+
private static final String VALUES_DATA_FILE = "values/data.mdb";
56+
private static final String EXPECTED_DB_FILE_SIZES_FILE = "expected-db-file-sizes.properties";
57+
private static final String TRIPLES_DATA_SIZE_PROPERTY = "triples.data.mdb.size.bytes";
58+
private static final String VALUES_DATA_SIZE_PROPERTY = "values.data.mdb.size.bytes";
59+
private static final long EXPECTED_TRIPLES_DATA_SIZE_BYTES = 1500921856L;
60+
private static final long EXPECTED_VALUES_DATA_SIZE_BYTES = 713687040L;
61+
62+
private static SailRepository repository;
63+
private static LmdbStore store;
64+
private static LmdbStoreConfig storeConfig;
65+
66+
public static void main(String[] args) throws RunnerException, IOException {
67+
File storeDirectory = storeDirectory();
68+
69+
var storeConfig = ConfigUtil.createConfig();
70+
var store = new LmdbStore(storeDirectory, storeConfig);
71+
repository = new SailRepository(store);
72+
try {
73+
ensureDataLoadedAndValidated();
74+
for (int i = 0; i < 10; i++) {
75+
var query = ThemeQueryCatalog.queryFor(Theme.MEDICAL_RECORDS, 0);
76+
try (SailRepositoryConnection connection = repository.getConnection()) {
77+
TupleQuery tupleQuery = connection.prepareTupleQuery(query);
78+
try (TupleQueryResult result = tupleQuery.evaluate()) {
79+
long count = result.stream().count();
80+
}
81+
}
82+
}
83+
84+
for (var theme : Theme.values()) {
85+
for (int z_queryIndex = 0; z_queryIndex <= 10; z_queryIndex++) {
86+
System.out.println("Theme: " + theme + " z_queryIndex: " + z_queryIndex);
87+
var query = ThemeQueryCatalog.queryFor(theme, z_queryIndex);
88+
89+
var connection = repository.getConnection();
90+
try {
91+
System.out.println("=== Explanation Telemetry ===");
92+
TupleQuery tupleQuery = connection.prepareTupleQuery(query);
93+
tupleQuery.setMaxExecutionTime(30);
94+
long l = System.currentTimeMillis();
95+
Explanation explain = tupleQuery.explain(Explanation.Level.Telemetry);
96+
int l1 = (int) (System.currentTimeMillis() - l);
97+
System.out.println("Initial explain execution time: " + l1 + " ms");
98+
if (l1 < 2000) {
99+
int max = (int) (2000 / l1);
100+
max = Math.min(max, 10);
101+
int minTime = l1;
102+
for (int i = 0; i < max; i++) {
103+
System.out.println("Warmup execution " + (i + 1) + "/" + max);
104+
connection.close();
105+
connection = repository.getConnection();
106+
107+
tupleQuery = connection.prepareTupleQuery(query);
108+
tupleQuery.setMaxExecutionTime(30);
109+
long l2 = System.currentTimeMillis();
110+
111+
try (TupleQueryResult evaluate = tupleQuery.evaluate()) {
112+
long count = evaluate.stream().count();
113+
}
114+
minTime = (int) Math.min(minTime, System.currentTimeMillis() - l2);
115+
}
116+
System.out.println("Fastest execution time: " + minTime + " ms");
117+
118+
tupleQuery = connection.prepareTupleQuery(query);
119+
tupleQuery.setMaxExecutionTime(30);
120+
explain = tupleQuery.explain(Explanation.Level.Telemetry);
121+
}
122+
System.out.println();
123+
System.out.println(explain);
124+
System.out.println();
125+
} finally {
126+
connection.close();
127+
}
128+
129+
connection = repository.getConnection();
130+
try {
131+
System.out.println("=== Explanation Optimized ===");
132+
Explanation explain = connection.prepareTupleQuery(query).explain(Explanation.Level.Optimized);
133+
System.out.println(explain);
134+
System.out.println();
135+
TupleExpr tupleExpr = (TupleExpr) explain.tupleExpr();
136+
System.out.println("=== Rendered Optimized TupleExpr ===");
137+
System.out.println(new TupleExprIRRenderer().render(tupleExpr));
138+
System.out.println();
139+
System.out.println();
140+
141+
} finally {
142+
connection.close();
143+
}
144+
}
145+
146+
}
147+
} finally {
148+
repository.shutDown();
149+
}
150+
151+
}
152+
153+
private static void ensureDataLoadedAndValidated() throws IOException {
154+
var expectedDbFileSizes = readExpectedDbFileSizes();
155+
if (!hasExpectedDbFileSizes(expectedDbFileSizes)) {
156+
rebuildStoreFromScratch();
157+
expectedDbFileSizes = currentDbFileSizes();
158+
writeExpectedDbFileSizes(expectedDbFileSizes);
159+
}
160+
161+
if (!hasExpectedDbFileSizes(expectedDbFileSizes)) {
162+
var currentDbFileSizes = currentDbFileSizes();
163+
throw new IllegalStateException("Unexpected LMDB db file sizes in fixed benchmark store. Expected "
164+
+ TRIPLES_DATA_FILE + "=" + expectedDbFileSizes.triplesDataSizeBytes + " and "
165+
+ VALUES_DATA_FILE + "=" + expectedDbFileSizes.valuesDataSizeBytes + " but got "
166+
+ TRIPLES_DATA_FILE + "=" + currentDbFileSizes.triplesDataSizeBytes + " and "
167+
+ VALUES_DATA_FILE + "=" + currentDbFileSizes.valuesDataSizeBytes);
168+
}
169+
170+
if (!expectedDbFileSizeFile().isFile()) {
171+
writeExpectedDbFileSizes(expectedDbFileSizes);
172+
}
173+
}
174+
175+
private static void rebuildStoreFromScratch() throws IOException {
176+
File storeDirectory = storeDirectory();
177+
if (repository != null) {
178+
repository.shutDown();
179+
}
180+
181+
FileUtils.deleteDirectory(storeDirectory);
182+
if (!storeDirectory.exists() && !storeDirectory.mkdirs()) {
183+
throw new IOException("Unable to recreate fixed LMDB benchmark directory: " + storeDirectory);
184+
}
185+
186+
storeConfig = ConfigUtil.createConfig();
187+
store = new LmdbStore(storeDirectory, storeConfig);
188+
repository = new SailRepository(store);
189+
// BenchmarkJoinEstimatorSupport.prepareEstimatorForBulkLoad(repository, store);
190+
loadData();
191+
192+
repository.shutDown();
193+
repository = null;
194+
store = null;
195+
196+
System.gc();
197+
198+
store = new LmdbStore(storeDirectory, storeConfig);
199+
repository = new SailRepository(store);
200+
}
201+
202+
private static DbFileSizes readExpectedDbFileSizes() throws IOException {
203+
var expectedDbFileSizeFile = expectedDbFileSizeFile();
204+
if (!expectedDbFileSizeFile.isFile()) {
205+
return defaultExpectedDbFileSizes();
206+
}
207+
var properties = new Properties();
208+
try (var inputStream = new FileInputStream(expectedDbFileSizeFile)) {
209+
properties.load(inputStream);
210+
}
211+
try {
212+
return new DbFileSizes(
213+
parseSizeProperty(properties, TRIPLES_DATA_SIZE_PROPERTY),
214+
parseSizeProperty(properties, VALUES_DATA_SIZE_PROPERTY));
215+
} catch (IllegalStateException e) {
216+
System.out.println("Ignoring invalid expected LMDB size file " + expectedDbFileSizeFile + ": "
217+
+ e.getMessage());
218+
if (!expectedDbFileSizeFile.delete()) {
219+
System.out.println("Unable to delete invalid expected LMDB size file: " + expectedDbFileSizeFile);
220+
}
221+
return defaultExpectedDbFileSizes();
222+
}
223+
}
224+
225+
private static void writeExpectedDbFileSizes(DbFileSizes expectedDbFileSizes) throws IOException {
226+
var properties = new Properties();
227+
properties.setProperty(TRIPLES_DATA_SIZE_PROPERTY, Long.toString(expectedDbFileSizes.triplesDataSizeBytes));
228+
properties.setProperty(VALUES_DATA_SIZE_PROPERTY, Long.toString(expectedDbFileSizes.valuesDataSizeBytes));
229+
try (var outputStream = new FileOutputStream(expectedDbFileSizeFile())) {
230+
properties.store(outputStream, "Expected LMDB data file sizes for ThemeQueryBenchmark");
231+
}
232+
}
233+
234+
private static long parseSizeProperty(Properties properties, String propertyName) {
235+
var value = properties.getProperty(propertyName);
236+
if (value == null) {
237+
throw new IllegalStateException("Missing property " + propertyName);
238+
}
239+
try {
240+
return Long.parseLong(value);
241+
} catch (NumberFormatException e) {
242+
throw new IllegalStateException("Invalid long value for property " + propertyName + ": " + value, e);
243+
}
244+
}
245+
246+
private static DbFileSizes defaultExpectedDbFileSizes() {
247+
return new DbFileSizes(EXPECTED_TRIPLES_DATA_SIZE_BYTES, EXPECTED_VALUES_DATA_SIZE_BYTES);
248+
}
249+
250+
private static DbFileSizes currentDbFileSizes() {
251+
return new DbFileSizes(dbFileSize(TRIPLES_DATA_FILE), dbFileSize(VALUES_DATA_FILE));
252+
}
253+
254+
private static boolean hasExpectedDbFileSizes(DbFileSizes expectedDbFileSizes) {
255+
var currentDbFileSizes = currentDbFileSizes();
256+
return currentDbFileSizes.triplesDataSizeBytes == expectedDbFileSizes.triplesDataSizeBytes
257+
&& currentDbFileSizes.valuesDataSizeBytes == expectedDbFileSizes.valuesDataSizeBytes;
258+
}
259+
260+
private static File expectedDbFileSizeFile() {
261+
return new File(storeDirectory(), EXPECTED_DB_FILE_SIZES_FILE);
262+
}
263+
264+
private static long dbFileSize(String relativePath) {
265+
return new File(storeDirectory(), relativePath).length();
266+
}
267+
268+
private static void loadData() throws IOException {
269+
try (var connection = repository.getConnection()) {
270+
connection.begin(IsolationLevels.READ_COMMITTED);
271+
var inserter = new RDFInserter(connection);
272+
// System.out.println("Loading theme dataset: " + theme);
273+
// ThemeDataSetGenerator.generate(theme, inserter);
274+
for (var themeDataset : Theme.values()) {
275+
System.out.println("Loading theme dataset: " + themeDataset);
276+
ThemeDataSetGenerator.generate(themeDataset, inserter);
277+
}
278+
connection.commit();
279+
}
280+
}
281+
282+
private static File storeDirectory() {
283+
// return new File(STORE_DIRECTORY, theme.name().toLowerCase());
284+
return new File(STORE_DIRECTORY, "complete");
285+
}
286+
287+
private record DbFileSizes(long triplesDataSizeBytes, long valuesDataSizeBytes) {
288+
}
289+
290+
}

0 commit comments

Comments
 (0)