Skip to content

Commit 50aaa1c

Browse files
authored
ESQL: Pragma to load from stored fields (#122891)
This creates a `pragma` you can use to request that fields load from a stored field rather than doc values. It implements that pragma for `keyword` and number fields. We expect that, for some disk configuration and some number of fields, that it's faster to load those fields from _source or stored fields than it is to use doc values. Our default is doc values and on my laptop it's *always* faster to use doc values. But we don't ship my laptop to every cluster. This will let us experiment and debug slow queries by trying to load fields a different way. You access this pragma with: ``` curl -HContent-Type:application/json -XPOST localhost:9200/_query?pretty -d '{ "query": "FROM foo", "pragma": { "field_extract_preference": "STORED" } }' ``` On a release build you'll need to add `"accept_pragma_risks": true`.
1 parent 6904540 commit 50aaa1c

File tree

34 files changed

+319
-86
lines changed

34 files changed

+319
-86
lines changed

docs/changelog/122891.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 122891
2+
summary: Pragma to load from stored fields
3+
area: ES|QL
4+
type: enhancement
5+
issues: []

modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/extras/ScaledFloatFieldMapper.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -320,7 +320,7 @@ public BlockLoader blockLoader(BlockLoaderContext blContext) {
320320
// Counters are not supported by ESQL so we load them in null
321321
return BlockLoader.CONSTANT_NULLS;
322322
}
323-
if (hasDocValues()) {
323+
if (hasDocValues() && (blContext.fieldExtractPreference() != FieldExtractPreference.STORED || isSyntheticSource)) {
324324
return new BlockDocValuesReader.DoublesBlockLoader(name(), l -> l / scalingFactor);
325325
}
326326
if (isSyntheticSource) {
@@ -333,7 +333,8 @@ public Builder builder(BlockFactory factory, int expectedCount) {
333333
}
334334

335335
ValueFetcher valueFetcher = sourceValueFetcher(blContext.sourcePaths(name()));
336-
BlockSourceReader.LeafIteratorLookup lookup = isStored() || isIndexed()
336+
BlockSourceReader.LeafIteratorLookup lookup = hasDocValues() == false && (isStored() || isIndexed())
337+
// We only write the field names field if there aren't doc values or norms
337338
? BlockSourceReader.lookupFromFieldNames(blContext.fieldNames(), name())
338339
: BlockSourceReader.lookupMatchingAll();
339340
return new BlockSourceReader.DoublesBlockLoader(valueFetcher, lookup);

modules/mapper-extras/src/test/java/org/elasticsearch/index/mapper/extras/ScaledFloatFieldBlockLoaderTests.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@
1818
import java.util.Map;
1919

2020
public class ScaledFloatFieldBlockLoaderTests extends NumberFieldBlockLoaderTestCase<Double> {
21-
public ScaledFloatFieldBlockLoaderTests() {
22-
super(FieldType.SCALED_FLOAT);
21+
public ScaledFloatFieldBlockLoaderTests(Params params) {
22+
super(FieldType.SCALED_FLOAT, params);
2323
}
2424

2525
@Override

server/src/main/java/org/elasticsearch/index/mapper/KeywordFieldMapper.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -748,7 +748,7 @@ NamedAnalyzer normalizer() {
748748

749749
@Override
750750
public BlockLoader blockLoader(BlockLoaderContext blContext) {
751-
if (hasDocValues()) {
751+
if (hasDocValues() && (blContext.fieldExtractPreference() != FieldExtractPreference.STORED || isSyntheticSource)) {
752752
return new BlockDocValuesReader.BytesRefsFromOrdsBlockLoader(name());
753753
}
754754
if (isStored()) {
@@ -806,7 +806,8 @@ private BlockSourceReader.LeafIteratorLookup sourceBlockLoaderLookup(BlockLoader
806806
if (getTextSearchInfo().hasNorms()) {
807807
return BlockSourceReader.lookupFromNorms(name());
808808
}
809-
if (isIndexed() || isStored()) {
809+
if (hasDocValues() == false && (isIndexed() || isStored())) {
810+
// We only write the field names field if there aren't doc values or norms
810811
return BlockSourceReader.lookupFromFieldNames(blContext.fieldNames(), name());
811812
}
812813
return BlockSourceReader.lookupMatchingAll();

server/src/main/java/org/elasticsearch/index/mapper/MappedFieldType.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -692,8 +692,13 @@ public enum FieldExtractPreference {
692692
/**
693693
* No preference. Leave the choice of where to load the field from up to the FieldType.
694694
*/
695-
NONE;
696-
695+
NONE,
696+
/**
697+
* Prefer loading from stored fields like {@code _source} because we're
698+
* loading many fields. The {@link MappedFieldType} can chose a different
699+
* method to load the field if it needs to.
700+
*/
701+
STORED;
697702
}
698703

699704
/**

server/src/main/java/org/elasticsearch/index/mapper/NumberFieldMapper.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1912,15 +1912,16 @@ public Function<byte[], Number> pointReaderIfPossible() {
19121912

19131913
@Override
19141914
public BlockLoader blockLoader(BlockLoaderContext blContext) {
1915-
if (hasDocValues()) {
1915+
if (hasDocValues() && (blContext.fieldExtractPreference() != FieldExtractPreference.STORED || isSyntheticSource)) {
19161916
return type.blockLoaderFromDocValues(name());
19171917
}
19181918

19191919
if (isSyntheticSource) {
19201920
return type.blockLoaderFromFallbackSyntheticSource(name(), nullValue, coerce);
19211921
}
19221922

1923-
BlockSourceReader.LeafIteratorLookup lookup = isStored() || isIndexed()
1923+
BlockSourceReader.LeafIteratorLookup lookup = hasDocValues() == false && (isStored() || isIndexed())
1924+
// We only write the field names field if there aren't doc values or norms
19241925
? BlockSourceReader.lookupFromFieldNames(blContext.fieldNames(), name())
19251926
: BlockSourceReader.lookupMatchingAll();
19261927
return type.blockLoaderFromSource(sourceValueFetcher(blContext.sourcePaths(name())), lookup);

server/src/test/java/org/elasticsearch/index/mapper/blockloader/BooleanFieldBlockLoaderTests.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,13 @@
1717
import java.util.Objects;
1818

1919
public class BooleanFieldBlockLoaderTests extends BlockLoaderTestCase {
20-
public BooleanFieldBlockLoaderTests() {
21-
super(FieldType.BOOLEAN);
20+
public BooleanFieldBlockLoaderTests(Params params) {
21+
super(FieldType.BOOLEAN, params);
2222
}
2323

2424
@Override
2525
@SuppressWarnings("unchecked")
26-
protected Object expected(Map<String, Object> fieldMapping, Object value, boolean syntheticSource) {
26+
protected Object expected(Map<String, Object> fieldMapping, Object value) {
2727
var nullValue = switch (fieldMapping.get("null_value")) {
2828
case Boolean b -> b;
2929
case String s -> Boolean.parseBoolean(s);

server/src/test/java/org/elasticsearch/index/mapper/blockloader/ByteFieldBlockLoaderTests.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515
import java.util.Map;
1616

1717
public class ByteFieldBlockLoaderTests extends NumberFieldBlockLoaderTestCase<Integer> {
18-
public ByteFieldBlockLoaderTests() {
19-
super(FieldType.BYTE);
18+
public ByteFieldBlockLoaderTests(Params params) {
19+
super(FieldType.BYTE, params);
2020
}
2121

2222
@Override

server/src/test/java/org/elasticsearch/index/mapper/blockloader/DateFieldBlockLoaderTests.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,13 @@
2323
import java.util.Objects;
2424

2525
public class DateFieldBlockLoaderTests extends BlockLoaderTestCase {
26-
public DateFieldBlockLoaderTests() {
27-
super(FieldType.DATE);
26+
public DateFieldBlockLoaderTests(Params params) {
27+
super(FieldType.DATE, params);
2828
}
2929

3030
@Override
3131
@SuppressWarnings("unchecked")
32-
protected Object expected(Map<String, Object> fieldMapping, Object value, boolean syntheticSource) {
32+
protected Object expected(Map<String, Object> fieldMapping, Object value) {
3333
var format = (String) fieldMapping.get("format");
3434
var nullValue = fieldMapping.get("null_value") != null ? format(fieldMapping.get("null_value"), format) : null;
3535

server/src/test/java/org/elasticsearch/index/mapper/blockloader/DoubleFieldBlockLoaderTests.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515
import java.util.Map;
1616

1717
public class DoubleFieldBlockLoaderTests extends NumberFieldBlockLoaderTestCase<Double> {
18-
public DoubleFieldBlockLoaderTests() {
19-
super(FieldType.DOUBLE);
18+
public DoubleFieldBlockLoaderTests(Params params) {
19+
super(FieldType.DOUBLE, params);
2020
}
2121

2222
@Override

0 commit comments

Comments
 (0)