Skip to content

QuestDB Benchmark Compliance Issue: Query Result Caching Enabled by Default #662

@xe-nvdk

Description

@xe-nvdk

Summary

QuestDB's ClickBench results appear to violate benchmark compliance rules by running with query result caching and intermediate data caching enabled by default. The benchmark scripts (benchmark.sh and run.sh) do not explicitly disable these caches, which are enabled in QuestDB's default configuration.

ClickBench Rules

From the official benchmark rules:

"If the system contains a cache for query results, it should be disabled."

"If the system contains a cache for intermediate data, that cache should be disabled if it is located near the end of the query execution pipeline, thus similar to a query result cache."

Evidence of Violation

1. Query Result Cache Enabled by Default

Source Code Evidence:

File: core/src/main/java/io/questdb/PropServerConfiguration.java:1812

this.httpSqlCacheEnabled = getBoolean(properties, env,
    PropertyKey.HTTP_QUERY_CACHE_ENABLED, true);

Default value: true (third parameter)

Configuration file: core/src/main/resources/io/questdb/site/conf/server.conf:155

# enables the query cache
#http.query.cache.enabled=true

The configuration line is commented out, meaning the code default of true applies.

Cache Implementation:

File: core/src/main/java/io/questdb/cutlass/http/HttpServer.java:89-92

if (serverConfiguration.isQueryCacheEnabled()) {
    this.selectCache = new ConcurrentAssociativeCache<>(
        serverConfiguration.getConcurrentCacheConfiguration());
} else {
    this.selectCache = NO_OP_CACHE;
}

File: core/src/main/java/io/questdb/cutlass/http/processors/JsonQueryProcessor.java:204-216

final RecordCursorFactory factory = context.getSelectCache().poll(state.getQuery());
if (factory != null) {
    // queries with sensitive info are not cached, doLog = true
    try {
        sqlExecutionContext.storeTelemetry(CompiledQuery.SELECT, TelemetryOrigin.HTTP_JSON);
        executeCachedSelect(state, factory);
    } catch (TableReferenceOutOfDateException e) {
        LOG.info().$safe(e.getFlyweightMessage()).$();
        compileAndExecuteQuery(state);
    }
} else {
    // new query
    compileAndExecuteQuery(state);
}

The query execution explicitly checks for cached query results and reuses them.

2. PostgreSQL Query Cache Also Enabled by Default

File: core/src/main/java/io/questdb/PropServerConfiguration.java:1806

this.pgSelectCacheEnabled = getBoolean(properties, env,
    PropertyKey.PG_SELECT_CACHE_ENABLED, true);

3. Symbol Table Cache Enabled by Default

File: core/src/main/java/io/questdb/PropServerConfiguration.java:1393

this.defaultSymbolCacheFlag = getBoolean(properties, env,
    PropertyKey.CAIRO_DEFAULT_SYMBOL_CACHE_FLAG, true);

Symbol tables are intermediate data structures that cache dictionary lookups, which should be disabled per benchmark rules.

4. Benchmark Scripts Do Not Disable Caches

File: questdb/benchmark.sh

The benchmark script only modifies two configuration parameters:

sed -i 's/query.timeout.sec=60/query.timeout.sec=500/' ~/.questdb/conf/server.conf
sed -i "s|cairo.sql.copy.root=import|cairo.sql.copy.root=$PWD|" ~/.questdb/conf/server.conf

Missing required configurations:

http.query.cache.enabled=false
pg.select.cache.enabled=false
cairo.default.symbol.cache.flag=false

These settings are completely absent from both benchmark.sh and run.sh, meaning QuestDB runs with all caches enabled by default.

Impact on Results

Suspicious Query Patterns

QuestDB has numerous queries where the "cold" run is barely slower than warm runs, suggesting query result caching is assisting:

  • Q1: Cold 0.022s, Warm 0.000s (instant warm - cached?)
  • Q20: Cold 0.034s, Warm 0.033s (1.03x - nearly identical)
  • Q24: Cold 8.389s, Warm 0.539s → then Q24 run 2-3: 1.051s, 0.028s (massive drop suggests cache hit)
  • Q25: Cold 0.055s, Warm 0.001s (55x faster - extreme caching benefit)
  • Q27: Cold 0.007s, Warm 0.006s (1.17x - minimal difference)
  • Q30: Cold 0.092s, Warm 0.007s (13x faster - caching benefit)

26 out of 43 queries show cold/warm ratios less than 2.0x, which is highly unusual for a properly flushed cache benchmark.

Additional Timing Measurement Issues

QuestDB's benchmark uses the "execute" timing from their API:

grep -P '"timings"|"error"|null' | \
sed -r -e 's/^.*"error".*$/null/; s/^.*"execute":([0-9]*),.*$/\1/' | \
awk '{ print ($1) / 1000000000 }'

File: core/src/main/java/io/questdb/cutlass/http/processors/JsonQueryProcessorState.java:1095

.putAsciiQuoted("execute").putAscii(':')
    .put(nanosecondClock.getTicks() - executeStartNanos)

This timing calculation happens during JSON serialization, which means:

  • JSON serialization time is NOT included
  • HTTP response writing is NOT included
  • Network transmission is NOT included

Other databases (ClickHouse, DuckDB, Arc) measure full end-to-end client time including all overhead. QuestDB's reported times may be 10-30% lower than comparable measurements.

Recommendations

  1. Add explicit cache disabling to benchmark scripts:

    echo "http.query.cache.enabled=false" >> ~/.questdb/conf/server.conf
    echo "pg.select.cache.enabled=false" >> ~/.questdb/conf/server.conf
    echo "cairo.default.symbol.cache.flag=false" >> ~/.questdb/conf/server.conf
  2. Re-run benchmarks with caches properly disabled

  3. Document timing methodology - clarify what overhead is included/excluded

  4. Update configuration documentation to show which caches are enabled by default

Request for Response

@questdb team - Can you confirm whether these caches are disabled during ClickBench runs? If they are disabled, please show the configuration used. If not, the results should be re-run with proper compliance.

References


Disclosure: I'm reporting this issue in good faith to ensure benchmark integrity. Arc explicitly disables all caches as required by benchmark rules, and we believe all participants should follow the same standards.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions