Skip to content

Data races in parallel GROUP BY with DECIMAL128/256 keys #6770

@puzpuzpuz

Description

@puzpuzpuz

To reproduce

Description

AsyncGroupByAtom and AsyncHorizonJoinAtom share a single ownerMapSink (RecordSink) across all parallel workers when GROUP BY keys are simple column references (i.e. perWorkerKeyFunctions == null). The bytecode-generated RecordSink uses mutable instance fields (this.decimal128 / this.decimal256) as scratch space when writing DECIMAL128 or DECIMAL256 columns. Concurrent access to these fields from multiple worker threads causes data races, producing incorrect query results.

Root cause

In both AsyncGroupByAtom and AsyncHorizonJoinAtom, per-worker map sinks were only created when per-worker key functions existed (expression keys like key + 1). For plain column keys (GROUP BY key), all workers fell back to the shared owner sink:

if (perWorkerKeyFunctions != null) {
    perWorkerMapSinks = new ObjList<>(slotCount);
    for (int i = 0; i < slotCount; i++) {
        perWorkerMapSinks.extendAndSet(i, RecordSinkFactory.getInstance(...));
    }
} else {
    perWorkerMapSinks = null; // all workers share ownerMapSink
}
public RecordSink getMapSink(int slotId) {
    if (slotId == -1 || perWorkerMapSinks == null) {
        return ownerMapSink; // shared across workers
    }
    return perWorkerMapSinks.getQuick(slotId);
}

The generated RecordSink bytecode for DECIMAL128/256 columns uses this.decimal128 / this.decimal256 scratch fields (in both column-based and function-based key paths), which are not safe for concurrent use.

Affected queries

Any parallel GROUP BY query where:

  • The key column is DECIMAL(19..38, s) (DECIMAL128) or DECIMAL(39..76, s) (DECIMAL256)
  • The key is a plain column reference (not an expression)

Example:

SELECT key, avg(value) FROM tab GROUP BY key
-- where key is DECIMAL(20, 0)

Reproduction

A concurrent test with 8 threads running the same GROUP BY query in a loop will intermittently produce incorrect results:

// Table: key DECIMAL(20, 0), value DOUBLE
// Query: SELECT key, avg(value), sum(colTop) FROM tab GROUP BY key
// 8 threads × 50 iterations → intermittent failures

QuestDB version:

Latest master

OS, in case of Docker specify Docker and the Host OS:

Ubuntu 24.04

File System, in case of Docker specify Host File System:

ext4

Full Name:

Andrei Pechkurov

Affiliation:

QuestDB

Have you followed Linux, MacOs kernel configuration steps to increase Maximum open files and Maximum virtual memory areas limit?

  • Yes, I have

Additional context

No response

Metadata

Metadata

Assignees

Labels

BugIncorrect or unexpected behaviorSQLIssues or changes relating to SQL execution

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions