Skip to content

Commit

Permalink
ESQL: Use faster field caps (elastic#105067)
Browse files Browse the repository at this point in the history
The field capabilities has an internal-only option to produce un-merged
output. This expands that option to be available to any caller inside of
Elasticsearch and uses it in ES|QL to speed up queries across many
indices with many fields. Across 5,000 indices with a couple thousand
fields each (metricbeat) the `FROM *` query went from 600ms to 60ms.
Across 50,000 indices that went from 6600ms to 600ms. 600ms is still too
slow for such a simple query, but one step at a time!

This is faster because field capabilities wants to present a
field-centric result but ES|QL actually needs a different flavor of
field-centric result with some differences smoothed away. If we take
over the merging process we can use a few tools that the field caps API
uses internally to be fast - mostly the sha256 of the mapping - to save
on doing work that wasn't available in the other view. Also, two merges
is more expensive than one.

That 90% reduction in runtime doesn't banish field caps from the
flamegraphs. You still see it, but it's now much less prominent. And you
don't see the merging process at all. Now it's all data-node side
operations field caps.

Relates to elastic#103369
  • Loading branch information
nik9000 authored Feb 27, 2024
1 parent e390edb commit 73a170b
Show file tree
Hide file tree
Showing 20 changed files with 2,037 additions and 95 deletions.
5 changes: 5 additions & 0 deletions docs/changelog/105067.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pr: 105067
summary: "ESQL: Use faster field caps"
area: ES|QL
type: enhancement
issues: []
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
import java.util.Map;
import java.util.Objects;

final class FieldCapabilitiesIndexResponse implements Writeable {
public final class FieldCapabilitiesIndexResponse implements Writeable {
private static final TransportVersion MAPPING_HASH_VERSION = TransportVersions.V_8_2_0;

private final String indexName;
Expand All @@ -34,7 +34,7 @@ final class FieldCapabilitiesIndexResponse implements Writeable {
private final boolean canMatch;
private final transient TransportVersion originVersion;

FieldCapabilitiesIndexResponse(
public FieldCapabilitiesIndexResponse(
String indexName,
@Nullable String indexMappingHash,
Map<String, IndexFieldCapabilities> responseMap,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ public FieldCapabilitiesRequest() {}
* <p>
* Note that when using the high-level REST client, results are always merged (this flag is always considered 'true').
*/
boolean isMergeResults() {
public boolean isMergeResults() {
return mergeResults;
}

Expand All @@ -85,7 +85,7 @@ boolean isMergeResults() {
* <p>
* Note that when using the high-level REST client, results are always merged (this flag is always considered 'true').
*/
void setMergeResults(boolean mergeResults) {
public void setMergeResults(boolean mergeResults) {
this.mergeResults = mergeResults;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public FieldCapabilitiesResponse(String[] indices, Map<String, Map<String, Field
this(indices, responseMap, Collections.emptyList(), Collections.emptyList());
}

FieldCapabilitiesResponse(List<FieldCapabilitiesIndexResponse> indexResponses, List<FieldCapabilitiesFailure> failures) {
public FieldCapabilitiesResponse(List<FieldCapabilitiesIndexResponse> indexResponses, List<FieldCapabilitiesFailure> failures) {
this(Strings.EMPTY_ARRAY, Collections.emptyMap(), indexResponses, failures);
}

Expand Down Expand Up @@ -117,7 +117,7 @@ public List<FieldCapabilitiesFailure> getFailures() {
/**
* Returns the actual per-index field caps responses
*/
List<FieldCapabilitiesIndexResponse> getIndexResponses() {
public List<FieldCapabilitiesIndexResponse> getIndexResponses() {
return indexResponses;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

package org.elasticsearch.xpack.esql.qa.mixed;

import com.carrotsearch.randomizedtesting.annotations.ThreadLeakFilters;

import org.elasticsearch.test.TestClustersThreadFilter;
import org.elasticsearch.test.cluster.ElasticsearchCluster;
import org.elasticsearch.xpack.esql.qa.rest.FieldExtractorTestCase;
import org.junit.ClassRule;

@ThreadLeakFilters(filters = TestClustersThreadFilter.class)
public class FieldExtractorIT extends FieldExtractorTestCase {
@ClassRule
public static ElasticsearchCluster cluster = Clusters.mixedVersionCluster();

@Override
protected String getTestRestCluster() {
return cluster.getHttpAddresses();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

package org.elasticsearch.xpack.esql.qa.multi_node;

import org.elasticsearch.test.cluster.ElasticsearchCluster;
import org.elasticsearch.test.cluster.local.distribution.DistributionType;

public class Clusters {
public static ElasticsearchCluster testCluster() {
return ElasticsearchCluster.local()
.distribution(DistributionType.DEFAULT)
.nodes(2)
.setting("xpack.security.enabled", "false")
.setting("xpack.license.self_generated.type", "trial")
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,13 @@
package org.elasticsearch.xpack.esql.qa.multi_node;

import org.elasticsearch.test.cluster.ElasticsearchCluster;
import org.elasticsearch.test.cluster.local.distribution.DistributionType;
import org.elasticsearch.xpack.esql.qa.rest.EsqlSpecTestCase;
import org.elasticsearch.xpack.ql.CsvSpecReader.CsvTestCase;
import org.junit.ClassRule;

public class EsqlSpecIT extends EsqlSpecTestCase {
@ClassRule
public static ElasticsearchCluster cluster = ElasticsearchCluster.local()
.distribution(DistributionType.DEFAULT)
.nodes(2)
.setting("xpack.security.enabled", "false")
.setting("xpack.license.self_generated.type", "trial")
.build();
public static ElasticsearchCluster cluster = Clusters.testCluster();

@Override
protected String getTestRestCluster() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

package org.elasticsearch.xpack.esql.qa.multi_node;

import com.carrotsearch.randomizedtesting.annotations.ThreadLeakFilters;

import org.elasticsearch.test.TestClustersThreadFilter;
import org.elasticsearch.test.cluster.ElasticsearchCluster;
import org.elasticsearch.xpack.esql.qa.rest.FieldExtractorTestCase;
import org.junit.ClassRule;

@ThreadLeakFilters(filters = TestClustersThreadFilter.class)
public class FieldExtractorIT extends FieldExtractorTestCase {
@ClassRule
public static ElasticsearchCluster cluster = Clusters.testCluster();

@Override
protected String getTestRestCluster() {
return cluster.getHttpAddresses();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

package org.elasticsearch.xpack.esql.qa.single_node;

import com.carrotsearch.randomizedtesting.annotations.ThreadLeakFilters;

import org.elasticsearch.test.TestClustersThreadFilter;
import org.elasticsearch.test.cluster.ElasticsearchCluster;
import org.elasticsearch.xpack.esql.qa.rest.FieldExtractorTestCase;
import org.junit.ClassRule;

@ThreadLeakFilters(filters = TestClustersThreadFilter.class)
public class FieldExtractorIT extends FieldExtractorTestCase {
@ClassRule
public static ElasticsearchCluster cluster = Clusters.testCluster();

@Override
protected String getTestRestCluster() {
return cluster.getHttpAddresses();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -185,8 +185,7 @@ public void testIncompatibleMappingsErrors() throws IOException {
assertException("from test_alias | where _size is not null | limit 1", "Unknown column [_size]");
assertException(
"from test_alias | where message.hash is not null | limit 1",
"Cannot use field [message.hash] due to ambiguities",
"incompatible types: [integer] in [index2], [murmur3] in [index1]"
"Cannot use field [message.hash] with unsupported type [murmur3]"
);
assertException(
"from index1 | where message.hash is not null | limit 1",
Expand Down
Loading

0 comments on commit 73a170b

Please sign in to comment.