Skip to content

Commit 446dfbe

Browse files
authored
IGNITE-24685 Add JMH benchmark and helper for TPC-H queries (#11906)
1 parent 801acdf commit 446dfbe

File tree

31 files changed

+1763
-1
lines changed

31 files changed

+1763
-1
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
*.log
22
/out/
33
/work/
4+
/work-*/
5+
/tpch-dataset-*/
46
*/build/
57
xcuserdata/
68
*.iws

modules/benchmarks/pom.xml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,19 @@
5454
<artifactId>ignite-calcite</artifactId>
5555
</dependency>
5656

57+
<dependency>
58+
<groupId>${project.groupId}</groupId>
59+
<artifactId>ignite-calcite</artifactId>
60+
<version>${project.version}</version>
61+
<type>test-jar</type>
62+
</dependency>
63+
64+
<dependency>
65+
<groupId>io.trino.tpch</groupId>
66+
<artifactId>tpch</artifactId>
67+
<version>1.2</version>
68+
</dependency>
69+
5770
<dependency>
5871
<groupId>org.javassist</groupId>
5972
<artifactId>javassist</artifactId>
Lines changed: 336 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,336 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.ignite.internal.benchmarks.jmh.sql.tpch;
19+
20+
import java.io.File;
21+
import java.io.IOException;
22+
import java.nio.file.Files;
23+
import java.nio.file.Path;
24+
import java.util.List;
25+
import java.util.concurrent.TimeUnit;
26+
import org.apache.ignite.Ignite;
27+
import org.apache.ignite.IgniteCheckedException;
28+
import org.apache.ignite.Ignition;
29+
import org.apache.ignite.cache.query.FieldsQueryCursor;
30+
import org.apache.ignite.cache.query.SqlFieldsQuery;
31+
import org.apache.ignite.calcite.CalciteQueryEngineConfiguration;
32+
import org.apache.ignite.cluster.ClusterState;
33+
import org.apache.ignite.configuration.DataRegionConfiguration;
34+
import org.apache.ignite.configuration.DataStorageConfiguration;
35+
import org.apache.ignite.configuration.IgniteConfiguration;
36+
import org.apache.ignite.configuration.LoadAllWarmUpConfiguration;
37+
import org.apache.ignite.configuration.SqlConfiguration;
38+
import org.apache.ignite.configuration.TransactionConfiguration;
39+
import org.apache.ignite.indexing.IndexingQueryEngineConfiguration;
40+
import org.apache.ignite.internal.IgniteEx;
41+
import org.apache.ignite.internal.IgniteInterruptedCheckedException;
42+
import org.apache.ignite.internal.processors.query.calcite.integration.tpch.TpchHelper;
43+
import org.apache.ignite.internal.util.typedef.internal.U;
44+
import org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi;
45+
import org.apache.ignite.spi.discovery.tcp.ipfinder.vm.TcpDiscoveryVmIpFinder;
46+
import org.openjdk.jmh.annotations.Benchmark;
47+
import org.openjdk.jmh.annotations.BenchmarkMode;
48+
import org.openjdk.jmh.annotations.Fork;
49+
import org.openjdk.jmh.annotations.Level;
50+
import org.openjdk.jmh.annotations.Measurement;
51+
import org.openjdk.jmh.annotations.Mode;
52+
import org.openjdk.jmh.annotations.OutputTimeUnit;
53+
import org.openjdk.jmh.annotations.Param;
54+
import org.openjdk.jmh.annotations.Scope;
55+
import org.openjdk.jmh.annotations.Setup;
56+
import org.openjdk.jmh.annotations.State;
57+
import org.openjdk.jmh.annotations.TearDown;
58+
import org.openjdk.jmh.annotations.Threads;
59+
import org.openjdk.jmh.annotations.Warmup;
60+
import org.openjdk.jmh.infra.Blackhole;
61+
import org.openjdk.jmh.runner.Runner;
62+
import org.openjdk.jmh.runner.options.Options;
63+
import org.openjdk.jmh.runner.options.OptionsBuilder;
64+
65+
/**
66+
* Benchmark TPC-H SQL queries.
67+
*/
68+
@State(Scope.Benchmark)
69+
// Use @Fork(value = 0) to debug or attach profiler.
70+
@Fork(value = 1, jvmArgs = {
71+
"-Xms4g", "-Xmx4g",
72+
"-Dcalcite.volcano.dump.graphviz=false",
73+
"-Dcalcite.volcano.dump.sets=false"
74+
})
75+
@Threads(1)
76+
@OutputTimeUnit(TimeUnit.MILLISECONDS)
77+
@SuppressWarnings({"unused"})
78+
public class TpchBenchmark {
79+
/*
80+
By default, this benchmark creates a separate work directory for each scale factor value
81+
and engine type, like `work-CALCITE-1.0`, `work-H2-0.1`, etc.
82+
83+
Also, the separate TPC-H dataset directory is created for each scale factor, like `tpch-dataset-0.01`.
84+
85+
If persistence is used (it's so by default) dataset is loaded into the ignite cluster only
86+
once to speed up testing. Cluster is warmed-up after restart before each benchmark run to ensure stable results.
87+
88+
These directories are not removed automatically and may be reused for subsequent invocations.
89+
Clean them yourselves if needed.
90+
*/
91+
92+
/** Count of server nodes. */
93+
private static final int SRV_NODES_CNT = 3;
94+
95+
/** IP finder shared across nodes. */
96+
private static final TcpDiscoveryVmIpFinder IP_FINDER = new TcpDiscoveryVmIpFinder(true);
97+
98+
/** Path to the dataset. */
99+
private Path datasetPath;
100+
101+
/** If true the dataset will be loaded only once to speed up the testing. */
102+
private static final Boolean USE_PERSISTENCE = true;
103+
104+
/** */
105+
private static final String DATASET_READY_MARK_FILE_NAME = "ready.txt";
106+
107+
/** Scale factor. scale == 1.0 means about 1Gb of data. */
108+
@Param({"0.01", "0.1", "1.0"})
109+
private String scale;
110+
111+
/** Query engine. */
112+
@Param({"CALCITE", "H2"})
113+
private String engine;
114+
115+
/**
116+
* Query id.
117+
* <p>
118+
* The commented queries do not currently work with Calcite even for scale=0.01.
119+
* The 11, 13, 15 can not be parsed with H2.
120+
*/
121+
@Param({
122+
"1",
123+
// "2", // TODO: https://issues.apache.org/jira/browse/IGNITE-24731
124+
"3",
125+
"4",
126+
// "5", // TODO: https://issues.apache.org/jira/browse/IGNITE-24741
127+
"6",
128+
"7",
129+
// "8", // TODO: https://issues.apache.org/jira/browse/IGNITE-24746
130+
// "9", // TODO: https://issues.apache.org/jira/browse/IGNITE-24752
131+
"10",
132+
"11",
133+
"12",
134+
"13",
135+
"14",
136+
"15",
137+
// "16", // TODO: https://issues.apache.org/jira/browse/IGNITE-24753
138+
// "17", // TODO: https://issues.apache.org/jira/browse/IGNITE-24754
139+
"18",
140+
// "19", // TODO: https://issues.apache.org/jira/browse/IGNITE-24756
141+
// "20", // TODO: https://issues.apache.org/jira/browse/IGNITE-24730
142+
// "21", // TODO: https://issues.apache.org/jira/browse/IGNITE-24757
143+
"22"
144+
})
145+
private String queryId;
146+
147+
/** Query SQL string. */
148+
private String queryString;
149+
150+
/** Ignite client. */
151+
private Ignite client;
152+
153+
/** Servers. */
154+
private final Ignite[] servers = new Ignite[SRV_NODES_CNT];
155+
156+
/**
157+
* Test already planned and cached query (without the initial planning).
158+
*/
159+
@Benchmark
160+
@BenchmarkMode(Mode.AverageTime)
161+
@Warmup(iterations = 1, time = 10)
162+
@Measurement(iterations = 3, time = 10)
163+
public void cached(Blackhole bh) {
164+
sql(bh, queryString);
165+
}
166+
167+
/**
168+
* Test a single cold non-cached query (include initial planning).
169+
*/
170+
@Benchmark
171+
@BenchmarkMode(Mode.SingleShotTime)
172+
@Warmup(iterations = 0)
173+
@Measurement(iterations = 1, time = 1)
174+
public void cold(Blackhole bh) {
175+
sql(bh, queryString);
176+
}
177+
178+
/**
179+
* Initiate Ignite and caches.
180+
*/
181+
@Setup(Level.Trial)
182+
public void setup() throws IOException, IgniteCheckedException {
183+
for (int i = 0; i < SRV_NODES_CNT; i++)
184+
servers[i] = Ignition.start(configuration("server" + i));
185+
186+
if (USE_PERSISTENCE)
187+
servers[0].cluster().state(ClusterState.ACTIVE);
188+
189+
client = Ignition.start(configuration("client").setClientMode(true));
190+
191+
queryString = TpchHelper.getQuery(Integer.parseInt(queryId));
192+
193+
loadDataset();
194+
}
195+
196+
/**
197+
* Stop Ignite instance.
198+
*/
199+
@TearDown
200+
public void tearDown() {
201+
client.close();
202+
203+
if (USE_PERSISTENCE)
204+
servers[0].cluster().state(ClusterState.INACTIVE);
205+
206+
for (Ignite ignite : servers)
207+
ignite.close();
208+
}
209+
210+
/**
211+
* Create Ignite configuration.
212+
*
213+
* @param igniteInstanceName Ignite instance name.
214+
* @return Configuration.
215+
*/
216+
private IgniteConfiguration configuration(String igniteInstanceName) {
217+
IgniteConfiguration cfg = new IgniteConfiguration();
218+
219+
cfg.setIgniteInstanceName(igniteInstanceName);
220+
cfg.setLocalHost("127.0.0.1");
221+
cfg.setDiscoverySpi(new TcpDiscoverySpi().setIpFinder(IP_FINDER));
222+
223+
if ("CALCITE".equals(engine)) {
224+
cfg.setSqlConfiguration(new SqlConfiguration().setQueryEnginesConfiguration(new CalciteQueryEngineConfiguration()));
225+
226+
cfg.setTransactionConfiguration(new TransactionConfiguration().setTxAwareQueriesEnabled(true));
227+
}
228+
else
229+
cfg.setSqlConfiguration(new SqlConfiguration().setQueryEnginesConfiguration(new IndexingQueryEngineConfiguration()));
230+
231+
if (USE_PERSISTENCE) {
232+
cfg.setDataStorageConfiguration(
233+
new DataStorageConfiguration().setDefaultDataRegionConfiguration(
234+
new DataRegionConfiguration()
235+
.setPersistenceEnabled(true)
236+
.setWarmUpConfiguration(new LoadAllWarmUpConfiguration())));
237+
}
238+
239+
cfg.setWorkDirectory(getWorkDirectory().toString());
240+
241+
return cfg;
242+
}
243+
244+
/**
245+
* Generate name of work directory
246+
*/
247+
private Path getWorkDirectory() {
248+
return Path.of(U.getIgniteHome(), String.format("work-%s-%s", engine, scale));
249+
}
250+
251+
/**
252+
* Execute several SQL queries separated by semicolons.
253+
*/
254+
private void sql(Blackhole bh, String sql) {
255+
try {
256+
for (String q : sql.split(";")) {
257+
if (!q.trim().isEmpty()) {
258+
SqlFieldsQuery qry = new SqlFieldsQuery(q.trim());
259+
260+
qry.setDistributedJoins(true);
261+
262+
try (FieldsQueryCursor<List<?>> cursor = ((IgniteEx)client).context().query().querySqlFields(qry, false)) {
263+
cursor.forEach(bh::consume);
264+
}
265+
}
266+
}
267+
}
268+
catch (Exception e) {
269+
tearDown();
270+
271+
throw e;
272+
}
273+
}
274+
275+
/**
276+
* Generate TPC-H dataset, create and fill tables.
277+
* <p>
278+
* The dataset .tbl files are created only once in the created directory.
279+
* Subsequent runs will use previously generated dataset.
280+
* <p>
281+
* If persistent storage is used, then the dataset will be loaded to Ignite cluster only once.
282+
*/
283+
private void loadDataset() throws IOException, IgniteInterruptedCheckedException {
284+
datasetPath = getOrCreateDataset(scale);
285+
286+
if (!USE_PERSISTENCE ||
287+
!Files.exists(getWorkDirectory().resolve(DATASET_READY_MARK_FILE_NAME))) {
288+
289+
TpchHelper.createTables(client);
290+
291+
TpchHelper.fillTables(client, datasetPath);
292+
293+
if (USE_PERSISTENCE)
294+
Files.createFile(getWorkDirectory().resolve(DATASET_READY_MARK_FILE_NAME));
295+
}
296+
297+
TpchHelper.collectSqlStatistics(client);
298+
}
299+
300+
/**
301+
* Create TPC-H dataset if it does not yet exist.
302+
*
303+
* @param scale Scale factor.
304+
* @return Path to the dataset directory.
305+
*/
306+
private Path getOrCreateDataset(String scale) throws IOException {
307+
File dir = Path.of(U.getIgniteHome(), String.format("tpch-dataset-%s", scale)).toFile();
308+
309+
if (!dir.exists()) {
310+
if (!dir.mkdirs())
311+
throw new RuntimeException("Failed to create dataset directory at: " + dir);
312+
}
313+
314+
if (!Files.exists(dir.toPath().resolve(DATASET_READY_MARK_FILE_NAME))) {
315+
TpchHelper.generateDataset(Double.parseDouble(scale), dir.toPath());
316+
317+
Files.createFile(dir.toPath().resolve(DATASET_READY_MARK_FILE_NAME));
318+
}
319+
320+
return dir.toPath();
321+
}
322+
323+
/**
324+
* Run benchmarks.
325+
*
326+
* @param args Args.
327+
* @throws Exception Exception.
328+
*/
329+
public static void main(String[] args) throws Exception {
330+
final Options options = new OptionsBuilder()
331+
.include(TpchBenchmark.class.getSimpleName())
332+
.build();
333+
334+
new Runner(options).run();
335+
}
336+
}

0 commit comments

Comments
 (0)