Skip to content

ESQL: Load many fields column-at-a-time#141926

Merged
nik9000 merged 23 commits intoelastic:mainfrom
nik9000:esql_from_many_column
Feb 10, 2026
Merged

ESQL: Load many fields column-at-a-time#141926
nik9000 merged 23 commits intoelastic:mainfrom
nik9000:esql_from_many_column

Conversation

@nik9000
Copy link
Copy Markdown
Member

@nik9000 nik9000 commented Feb 5, 2026

ESQL: Load many fields column-at-a-time

Adds support for ColumnAtATimeReader in the case where we're loading
from many segments. This should marginally speed up loading many
documents after a top n. More importantly, it lets #141672 kick in
when loading from many fields. This should save significantly memory
when loading thousands of fields after a | SORT | LIMIT sequence.

Finally, this changes the rules for BlockLoader. Previously you
could return null from columnAtATimeReader but must never return
null from rowStrideReader. Now the rule is that you may return null
from either of the two, but not both. This should let us delete a
bunch of code. While we're at it, we should add a
read(builder, docs, offset, nullsFiltered) override to save a copy.

@elasticsearchmachine elasticsearchmachine added the Team:Analytics Meta label for analytical engine team (ESQL/Aggs/Geo) label Feb 5, 2026
@elasticsearchmachine
Copy link
Copy Markdown
Collaborator

Pinging @elastic/es-analytical-engine (Team:Analytics)

@nik9000
Copy link
Copy Markdown
Member Author

nik9000 commented Feb 5, 2026

I'm adding an integration test now. Will see if there's real performance improvement too. It's likely it'll be in a fun query like

FROM foo
| SORT timestamp DESC
| LIMIT 10000000
| STATS SUM(f)

@nik9000
Copy link
Copy Markdown
Member Author

nik9000 commented Feb 5, 2026

So, yes, there is some performance improvement:

while curl -s -HContent-Type:application/json -uelastic:password -XPOST localhost:9200/_query -d'{
    "query": "FROM test-index | SORT timestamp DESC | LIMIT 1000000 | STATS MIN(f)"
}' | jq .took; do echo ok; done

6764 -> 5654. That's not much, but I didn't expect much. The load is 25122339ns-> 79986530ns.

FWIW most of the time for that is spent in the top n operator - something I'll be looking into in a few weeks.

@nik9000
Copy link
Copy Markdown
Member Author

nik9000 commented Feb 5, 2026

@martijnvg , a bunch of the time series tests fail in this PR. When I comment out the instanceof OptionalColumnAtATimeReader stuff they pass. It looks kind of a like an off-by-one somewhere deep in the reader.

./gradlew ":x-pack:plugin:esql:qa:server:single-node:javaRestTest" --tests "org.elasticsearch.xpack.esql.qa.single_node.EsqlSpecIT" -Dtests.method="test {csv-spec:k8s-timeseries-min-over-time.Min_over_time_aggregate_metric_double_implicit_casting_grouping}" -Dtests.seed=8C6B0587BDC10671 -Dtests.locale=hu-Latn-HU -Dtests.timezone=Etc/GMT -Druntime.java=25

reproduces it.


BlockLoader loader;
// TODO rework this bit of mutable state into something harder to forget
// Seriously, I've tripped over this twice.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll grab this in a follow-up change.

@parkertimmins
Copy link
Copy Markdown
Contributor

@martijnvg , a bunch of the time series tests fail in this PR. When I comment out the instanceof OptionalColumnAtATimeReader stuff they pass. It looks kind of a like an off-by-one somewhere deep in the reader.

./gradlew ":x-pack:plugin:esql:qa:server:single-node:javaRestTest" --tests "org.elasticsearch.xpack.esql.qa.single_node.EsqlSpecIT" -Dtests.method="test {csv-spec:k8s-timeseries-min-over-time.Min_over_time_aggregate_metric_double_implicit_casting_grouping}" -Dtests.seed=8C6B0587BDC10671 -Dtests.locale=hu-Latn-HU -Dtests.timezone=Etc/GMT -Druntime.java=25

reproduces it.

Looked into this a bit. The cause of the issue is that isDense assumes that the incoming doc ids do not contain duplicates:


I'm not yet sure if the fix is to make a more robust/expensive form of isDense, or something else. Anyway, will follow-up on this tomorrow.

@nik9000
Copy link
Copy Markdown
Member Author

nik9000 commented Feb 6, 2026

@dnhatn, @martijnvg, @parkertimmins , and I met to talk about this. DocVector usually doesn't contain duplicates. But TimeSeriesAggregatorOperator.selectedForDocIdsAggregator and ENRICH and LOOKUP JOIN can make duplicate doc ids.

If you use Lucene's reader interfaces you can read duplicate doc ids. Most of our BlockLoader implementations do that. Except the failing test. That goes lower, and it has the method @parkertimmins mentioned. It isn't tolerant of duplicates.

We really want the performance we can get by not being tolerant of duplicates. Specifically, we need that performance for the "first load" of fields. And, at least in the case of TimeSeriesAggregatorOperator.selectedForDocIdsAggregator, we're fine to slow down so we can handle duplicates.

I'm going to block this PR on another one I'm starting now. It will add a flag to DocVector saying duplicatesAllowed. Usually it'll be false and we can go fast. Sometime it won't be and we won't take the fancy fast paths.

@nik9000
Copy link
Copy Markdown
Member Author

nik9000 commented Feb 6, 2026

Blocked on #142055

@nik9000 nik9000 requested a review from dnhatn February 8, 2026 17:19
@nik9000
Copy link
Copy Markdown
Member Author

nik9000 commented Feb 9, 2026

Unblocked! @parkertimmins, could you have a look at this one? And, could you make a follow up with unit tests for y'all's fancy BlockLoader implementations?

@parkertimmins
Copy link
Copy Markdown
Contributor

Unblocked! @parkertimmins, could you have a look at this one? And, could you make a follow up with unit tests for y'all's fancy BlockLoader implementations?

Thanks for adding the mayContainDuplicates logic, that looks good to me. (And wow -974 lines!) Sounds good, yep I'll follow up with some unit tests today for the block loaders.

Copy link
Copy Markdown
Member

@dnhatn dnhatn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Thanks @nik9000

boolean toInt,
boolean binaryMultiValuedFormat
) throws IOException {
if (docs.mayContainDuplicates()) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can go further even with duplicates, but Martijn or Parker can follow up on it.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we can look into removing these if statements in follow ups.

Copy link
Copy Markdown
Member

@martijnvg martijnvg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Nik! One minor comment, LGTM 👍.

boolean toInt,
boolean binaryMultiValuedFormat
) throws IOException {
if (docs.mayContainDuplicates()) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we can look into removing these if statements in follow ups.

* </li>
* </ul>
*/
boolean mayContainDuplicates();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A quick look indicates to most implementations return false here. Maybe have a default implementation that returns false?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought about it but figured it was kinder to make the implementer think about the choice when implementing.

@nik9000 nik9000 enabled auto-merge (squash) February 9, 2026 20:54
@nik9000 nik9000 merged commit 82756e9 into elastic:main Feb 10, 2026
35 checks passed
parkertimmins added a commit that referenced this pull request Feb 23, 2026
…duplicates (#142409)

Add a test to es819 codec test to verify changes from #141926 . Just checks that situations which require incoming docs to not contain duplicates, return null on tryRead if passed docs with duplicates. Also, update DenseBinaryDocValues to return null if mayContainDuplicates
jdconrad pushed a commit to jdconrad/elasticsearch that referenced this pull request Feb 24, 2026
…duplicates (elastic#142409)

Add a test to es819 codec test to verify changes from elastic#141926 . Just checks that situations which require incoming docs to not contain duplicates, return null on tryRead if passed docs with duplicates. Also, update DenseBinaryDocValues to return null if mayContainDuplicates
sidosera pushed a commit to sidosera/elasticsearch that referenced this pull request Feb 24, 2026
…duplicates (elastic#142409)

Add a test to es819 codec test to verify changes from elastic#141926 . Just checks that situations which require incoming docs to not contain duplicates, return null on tryRead if passed docs with duplicates. Also, update DenseBinaryDocValues to return null if mayContainDuplicates
nik9000 added a commit that referenced this pull request Feb 27, 2026
In #141926 I deprecated the `AllReader` because we no longer need to make a `BlockLoader` work both row-by-row and column-at-a-time. Now it's fine for a `BlockLoader` to work in either mode. And `AllReader` was the tool that we used to support working both ways. So it can go! This removes it.
tballison pushed a commit to tballison/elasticsearch that referenced this pull request Mar 3, 2026
In elastic#141926 I deprecated the `AllReader` because we no longer need to make a `BlockLoader` work both row-by-row and column-at-a-time. Now it's fine for a `BlockLoader` to work in either mode. And `AllReader` was the tool that we used to support working both ways. So it can go! This removes it.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

:Analytics/ES|QL AKA ESQL >bug Team:Analytics Meta label for analytical engine team (ESQL/Aggs/Geo) v9.4.0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants