Skip to content

Conversation

nik9000
Copy link
Member

@nik9000 nik9000 commented Sep 10, 2025

Right now we push all topn operations to lucene if possible. But Lucene was not written to handle a topn of 100,000. It's very fast, but it allocates more memory than we'd like. This limits the size of the topns that we push to lucene to the 10,000, which is the default window size limit. We'll run a regular lucene scan with our own in-engine topn instead. That's designed to scan huge numbers of documents. It doesn't have the nice min_competitive optimization. But it tracks memory very well.

Right now we push all topn operations to lucene if possible. But Lucene
was not written to handle a topn of 100,000. It's very fast, but it
allocates more memory than we'd like. This limits the size of the topns
that we push to lucene to the 10,000, which is the default window size
limit. We'll run a regular lucene scan with our own in-engine topn
instead. That's designed to scan huge numbers of documents. It doesn't
have the nice min_competitive optimization. But it tracks memory very
well.
@nik9000 nik9000 requested a review from dnhatn September 10, 2025 19:23
@elasticsearchmachine elasticsearchmachine added the Team:Analytics Meta label for analytical engine team (ESQL/Aggs/Geo) label Sep 10, 2025
@elasticsearchmachine
Copy link
Collaborator

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

@elasticsearchmachine
Copy link
Collaborator

Hi @nik9000, I've created a changelog YAML for you.

Copy link
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 Nik!

Copy link
Contributor

@alex-spies alex-spies left a comment

Choose a reason for hiding this comment

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

Only quickly skimmed the change to PushTopNToSource. Looks alright to me.

Comment on lines +148 to +151
if (child instanceof EvalExec evalExec
&& evalExec.child() instanceof EsQueryExec queryExec
&& queryExec.canPushSorts()
&& canPushLimit(topNExec, physicalSettings)) {
Copy link
Contributor

@alex-spies alex-spies Sep 11, 2025

Choose a reason for hiding this comment

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

Looks right to me, but I think @craigtaverner should have a look, too. I don't remember if it's bad when we somehow cannot push a WHERE dist < 10 | EVAL ST_DISTANCE = (...) to Lucene. Craig, we don't end up with un-executable queries, they're just slow when we cannot push, right?

Copy link
Contributor

Choose a reason for hiding this comment

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

Correct. Failing to push down ST_DISTANCE will not cause the query to fail, just run slow. And as I understand it this PR will only block pushdown for extremely large LIMIT values, which are an extreme case anyway.

Copy link
Member Author

Choose a reason for hiding this comment

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

10,000

&& queryExec.canPushSorts()
&& canPushDownOrders(topNExec.order(), lucenePushdownPredicates)) {
&& canPushDownOrders(topNExec.order(), lucenePushdownPredicates)
&& canPushLimit(topNExec, physicalSettings)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Looks correct.

Copy link
Contributor

@craigtaverner craigtaverner left a comment

Choose a reason for hiding this comment

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

LGTM. I also ran some local ST_DISTANCE benchmarks on this branch and saw no changes, which is what I would expect since we don't benchmark very large SORT/LIMIT. But at least it rules out that this change inadvertently breaks ST_DISTANCE pushdown for the cases we've benchmarked.

@nik9000
Copy link
Member Author

nik9000 commented Sep 11, 2025

But at least it rules out that this change inadvertently breaks ST_DISTANCE pushdown for the cases we've benchmarked.

The thing they broke at first was that they removed the lucene sort with extremely large limits but didn't retain engine sort. I added some more tests to catch that.

But, great news on the benchmark!

Thanks for looking friends. I'll try and merge this.

@nik9000 nik9000 enabled auto-merge (squash) September 11, 2025 12:41

private static boolean canPushLimit(TopNExec topn, PhysicalSettings physicalSettings) {
return topn.limit() instanceof Literal l && ((Number) l.value()).intValue() <= physicalSettings.luceneTopNLimit();
}
Copy link
Contributor

Choose a reason for hiding this comment

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

NIT: this could be simplified with

public static Integer limitValue(Expression limitField, String sourceText) {
if (limitField instanceof Literal literal) {
Object value = literal.value();
if (value instanceof Integer intValue) {
return intValue;
}
}
throw new EsqlIllegalArgumentException(format(null, "Limit value must be an integer in [{}], found [{}]", sourceText, limitField));
}

public static final Setting<Integer> LUCENE_TOPN_LIMIT = Setting.intSetting(
"esql.lucene_topn_limit",
IndexSettings.MAX_RESULT_WINDOW_SETTING.getDefault(Settings.EMPTY),
-1,
Copy link
Contributor

Choose a reason for hiding this comment

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

We do not limit by default?

Copy link
Contributor

Choose a reason for hiding this comment

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

Uh, just realized IndexSettings.MAX_RESULT_WINDOW_SETTING.getDefault(Settings.EMPTY) is a default value

Copy link
Member Author

Choose a reason for hiding this comment

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

Right! We default to the window's default.

@nik9000 nik9000 merged commit eb51dc2 into elastic:main Sep 22, 2025
34 checks passed
gmjehovich pushed a commit to gmjehovich/elasticsearch that referenced this pull request Sep 22, 2025
Right now we push all topn operations to lucene if possible. But Lucene
was not written to handle a topn of 100,000. It's very fast, but it
allocates more memory than we'd like. This limits the size of the topns
that we push to lucene to the 10,000, which is the default window size
limit. We'll run a regular lucene scan with our own in-engine topn
instead. That's designed to scan huge numbers of documents. It doesn't
have the nice min_competitive optimization. But it tracks memory very
well.
DonalEvans pushed a commit to DonalEvans/elasticsearch that referenced this pull request Sep 22, 2025
Right now we push all topn operations to lucene if possible. But Lucene
was not written to handle a topn of 100,000. It's very fast, but it
allocates more memory than we'd like. This limits the size of the topns
that we push to lucene to the 10,000, which is the default window size
limit. We'll run a regular lucene scan with our own in-engine topn
instead. That's designed to scan huge numbers of documents. It doesn't
have the nice min_competitive optimization. But it tracks memory very
well.
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.2.0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants