Skip to content

Commit 4f0e11b

Browse files
authored
TIKA-4875: improve tika-eval performance (#3123)
1 parent 54a8ed6 commit 4f0e11b

9 files changed

Lines changed: 354 additions & 46 deletions

File tree

CHANGES.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
Release 4.1.0 - unreleased
22

3+
* tika-eval Profile/Compare speedups: single-pass URL/mail stripping
4+
replaces the bounded regexes in langdetect preprocessing (same output,
5+
17-290x faster on web text), the default H2 db URL drops MVStore chunk
6+
retention and sizes the page cache at a quarter of the heap clamped to
7+
[64MB, 1GB] (override with -Dtika.eval.h2.cacheSizeKb=<kb>), and the
8+
status log adds a last-interval docs-per-sec rate next to the cumulative
9+
average (TIKA-4875).
10+
311
* New "content-enrichers" config list (TIKA-4872): select the OCR engine
412
("tesseract-ocr-parser", "tess4j-parser", "openai-vlm-parser", ...) by
513
name instead of by classpath registration of the image/ocr-* pseudo

tika-eval/tika-eval-app/src/main/java/org/apache/tika/eval/app/ExtractComparerRunner.java

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ public static void main(String[] args) throws Exception {
142142
}
143143

144144
try {
145-
String jdbcString = getJdbcConnectionString(dbPath);
145+
String jdbcString = JDBCUtil.getJdbcConnectionString(dbPath);
146146
Map<String, String> runInfo = RunInfo.evalInfo(args, evalConfig, inputDir);
147147
execute(inputDir, extractsADir, extractsBDir, jdbcString, evalConfig, sideA.pipesReport(), sideB.pipesReport(), runInfo, runInfoA, runInfoB);
148148

@@ -177,16 +177,6 @@ private static Path optPath(CommandLine commandLine, String opt) {
177177
return commandLine.hasOption(opt) ? Paths.get(commandLine.getOptionValue(opt)) : null;
178178
}
179179

180-
private static String getJdbcConnectionString(String dbPath) {
181-
if (dbPath.startsWith("jdbc:")) {
182-
return dbPath;
183-
}
184-
//default to h2
185-
Path p = Paths.get(dbPath);
186-
return "jdbc:h2:file:" + p.toAbsolutePath();
187-
188-
}
189-
190180
private static void execute(Path inputDir, Path extractsA, Path extractsB, String dbPath, EvalConfig evalConfig, PipesReport pipesReportA,
191181
PipesReport pipesReportB, Map<String, String> runInfo, Map<String, String> runInfoA, Map<String, String> runInfoB)
192182
throws SQLException, IOException {

tika-eval/tika-eval-app/src/main/java/org/apache/tika/eval/app/ExtractProfileRunner.java

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ public static void main(String[] args) throws Exception {
9898
Path extractsDir = commandLine.hasOption('e') ? Paths.get(commandLine.getOptionValue('e')) : Paths.get(USAGE_FAIL("Must specify extracts dir: -i"));
9999
Path inputDir = commandLine.hasOption('i') ? Paths.get(commandLine.getOptionValue('i')) : extractsDir;
100100
String dbPath = commandLine.hasOption('d') ? commandLine.getOptionValue('d') : USAGE_FAIL("Must specify the db name: -d");
101-
String jdbcString = getJdbcConnectionString(dbPath);
101+
String jdbcString = JDBCUtil.getJdbcConnectionString(dbPath);
102102
if (commandLine.hasOption('n')) {
103103
evalConfig.setNumWorkers(Integer.parseInt(commandLine.getOptionValue('n')));
104104
}
@@ -118,16 +118,6 @@ private static Path optPath(CommandLine commandLine, String opt) {
118118
return commandLine.hasOption(opt) ? Paths.get(commandLine.getOptionValue(opt)) : null;
119119
}
120120

121-
private static String getJdbcConnectionString(String dbPath) {
122-
if (dbPath.startsWith("jdbc:")) {
123-
return dbPath;
124-
}
125-
//default to h2
126-
Path p = Paths.get(dbPath);
127-
return "jdbc:h2:file:" + p.toAbsolutePath();
128-
129-
}
130-
131121
private static void execute(Path inputDir, Path extractsDir, String dbPath, EvalConfig evalConfig, PipesReport pipesReport,
132122
Map<String, String> runInfo) throws SQLException, IOException {
133123

tika-eval/tika-eval-app/src/main/java/org/apache/tika/eval/app/StatusReporter.java

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ public class StatusReporter implements Callable<Long> {
3838
private final AtomicBoolean crawlerIsActive;
3939
private final long start;
4040
private final NumberFormat numberFormat = NumberFormat.getNumberInstance(Locale.ROOT);
41+
private int lastCnt = 0;
42+
private long lastReportMillis;
4143

4244

4345
public StatusReporter(CallablePipesIterator pipesIterator, AtomicInteger filesProcessed, AtomicInteger activeWorkers, AtomicBoolean crawlerIsActive) {
@@ -46,6 +48,7 @@ public StatusReporter(CallablePipesIterator pipesIterator, AtomicInteger filesPr
4648
this.activeWorkers = activeWorkers;
4749
this.crawlerIsActive = crawlerIsActive;
4850
this.start = System.currentTimeMillis();
51+
this.lastReportMillis = this.start;
4952
}
5053

5154
@Override
@@ -68,13 +71,22 @@ public Long call() throws Exception {
6871
}
6972

7073
private void report() {
74+
long now = System.currentTimeMillis();
7175
int cnt = filesProcessed.get();
72-
long elapsed = System.currentTimeMillis() - start;
76+
long elapsed = now - start;
7377
double elapsedSecs = (double) elapsed / (double) 1000;
7478
int avg = (elapsedSecs > 5 || cnt > 100) ? (int) ((double) cnt / elapsedSecs) : -1;
7579

76-
String elapsedString = DurationFormatUtils.formatMillis(System.currentTimeMillis() - start);
77-
String docsPerSec = avg > -1 ? String.format(Locale.ROOT, " (%s docs per sec)", numberFormat.format(avg)) : "";
80+
// the cumulative average declines by construction and masks cliffs
81+
double windowSecs = (double) (now - lastReportMillis) / (double) 1000;
82+
int windowRate = windowSecs > 0 ? (int) ((double) (cnt - lastCnt) / windowSecs) : avg;
83+
lastCnt = cnt;
84+
lastReportMillis = now;
85+
86+
String elapsedString = DurationFormatUtils.formatMillis(elapsed);
87+
String docsPerSec = avg > -1 ?
88+
String.format(Locale.ROOT, " (%s docs per sec overall; %s in the last interval)",
89+
numberFormat.format(avg), numberFormat.format(windowRate)) : "";
7890
String msg = String.format(Locale.ROOT, "Processed %s documents in %s%s.", numberFormat.format(cnt), elapsedString, docsPerSec);
7991
LOGGER.info(msg);
8092

tika-eval/tika-eval-app/src/main/java/org/apache/tika/eval/app/db/JDBCUtil.java

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919

2020
import java.io.IOException;
2121
import java.io.InputStream;
22+
import java.nio.file.Path;
23+
import java.nio.file.Paths;
2224
import java.sql.Connection;
2325
import java.sql.DatabaseMetaData;
2426
import java.sql.DriverManager;
@@ -40,6 +42,17 @@
4042
import org.slf4j.LoggerFactory;
4143

4244
public class JDBCUtil {
45+
46+
/**
47+
* Override for the h2 page cache in KB; unclamped, so a big box can go well
48+
* beyond the heap-relative default.
49+
*/
50+
public static final String H2_CACHE_SIZE_KB_PROPERTY = "tika.eval.h2.cacheSizeKb";
51+
52+
//h2's own default: 64MB
53+
private static final long MIN_H2_CACHE_SIZE_KB = 65_536L;
54+
private static final long MAX_H2_CACHE_SIZE_KB = 1_048_576L;
55+
4356
private static final Logger LOG = LoggerFactory.getLogger(JDBCUtil.class);
4457
private final String connectionString;
4558
private String driverClass;
@@ -73,6 +86,34 @@ public JDBCUtil(String connectionString, String driverClass) {
7386
}
7487
}
7588

89+
/**
90+
* If dbPath is already a jdbc string, it is used as is; otherwise this builds the
91+
* tika-eval h2 default: RETENTION_TIME=0 drops the 45s MVStore chunk retention
92+
* (bloat + growing compaction cost) and CACHE_SIZE (KB) is sized by
93+
* {@link #getH2CacheSizeKb()}.
94+
*/
95+
public static String getJdbcConnectionString(String dbPath) {
96+
if (dbPath.startsWith("jdbc:")) {
97+
return dbPath;
98+
}
99+
Path p = Paths.get(dbPath);
100+
return "jdbc:h2:file:" + p.toAbsolutePath() + ";RETENTION_TIME=0;CACHE_SIZE=" + getH2CacheSizeKb();
101+
}
102+
103+
/**
104+
* H2's page cache is on heap, so the default is a quarter of the heap clamped to
105+
* [64MB, 1GB] rather than a fixed size that a small JVM cannot afford. Set
106+
* {@value #H2_CACHE_SIZE_KB_PROPERTY} to override.
107+
*/
108+
public static long getH2CacheSizeKb() {
109+
String override = System.getProperty(H2_CACHE_SIZE_KB_PROPERTY);
110+
if (override != null) {
111+
return Long.parseLong(override.trim());
112+
}
113+
long quarterHeapKb = Runtime.getRuntime().maxMemory() / 4 / 1024;
114+
return Math.min(MAX_H2_CACHE_SIZE_KB, Math.max(MIN_H2_CACHE_SIZE_KB, quarterHeapKb));
115+
}
116+
76117
public static void batchInsert(PreparedStatement insertStatement, TableInfo table, Map<Cols, String> data) throws SQLException {
77118

78119
try {
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
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+
package org.apache.tika.eval.app.db;
18+
19+
import static org.junit.jupiter.api.Assertions.assertEquals;
20+
import static org.junit.jupiter.api.Assertions.assertTrue;
21+
22+
import org.junit.jupiter.api.AfterEach;
23+
import org.junit.jupiter.api.BeforeEach;
24+
import org.junit.jupiter.api.Test;
25+
26+
public class JDBCUtilTest {
27+
28+
private String originalCacheSize;
29+
30+
@BeforeEach
31+
public void stashCacheSizeProperty() {
32+
originalCacheSize = System.getProperty(JDBCUtil.H2_CACHE_SIZE_KB_PROPERTY);
33+
System.clearProperty(JDBCUtil.H2_CACHE_SIZE_KB_PROPERTY);
34+
}
35+
36+
@AfterEach
37+
public void restoreCacheSizeProperty() {
38+
if (originalCacheSize == null) {
39+
System.clearProperty(JDBCUtil.H2_CACHE_SIZE_KB_PROPERTY);
40+
} else {
41+
System.setProperty(JDBCUtil.H2_CACHE_SIZE_KB_PROPERTY, originalCacheSize);
42+
}
43+
}
44+
45+
@Test
46+
public void testJdbcStringPassesThrough() {
47+
String jdbc = "jdbc:postgresql://localhost/tika_eval";
48+
assertEquals(jdbc, JDBCUtil.getJdbcConnectionString(jdbc));
49+
}
50+
51+
@Test
52+
public void testH2Defaults() {
53+
long cacheSizeKb = JDBCUtil.getH2CacheSizeKb();
54+
assertTrue(cacheSizeKb >= 65_536L && cacheSizeKb <= 1_048_576L, "clamped to [64MB, 1GB]: " + cacheSizeKb);
55+
String connectionString = JDBCUtil.getJdbcConnectionString("mydb");
56+
assertTrue(connectionString.startsWith("jdbc:h2:file:"), connectionString);
57+
assertTrue(connectionString.endsWith(";RETENTION_TIME=0;CACHE_SIZE=" + cacheSizeKb), connectionString);
58+
}
59+
60+
@Test
61+
public void testH2CacheSizeOverrideIsUnclamped() {
62+
System.setProperty(JDBCUtil.H2_CACHE_SIZE_KB_PROPERTY, "8388608");
63+
assertEquals(8_388_608L, JDBCUtil.getH2CacheSizeKb());
64+
}
65+
}

0 commit comments

Comments
 (0)