Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
17eb2ae
Optimize IP field parsing
felixbarny Aug 5, 2025
55bb8d6
Fix bug in convertDottedQuadToHex
felixbarny Aug 5, 2025
31a6385
Small performance improvements for ipv4 parsing
felixbarny Aug 7, 2025
11aafe4
Merge branch 'main' into ip-parsing-optimization
felixbarny Aug 8, 2025
4c95f1f
Reduce memory allocations by avoiding InetAddress
felixbarny Aug 12, 2025
725fae9
Merge remote-tracking branch 'origin/main' into ip-parsing-optimization
felixbarny Aug 12, 2025
02bc756
Avoid lambda overhead
felixbarny Aug 12, 2025
594bf80
Avoid forbidden APIs
felixbarny Aug 12, 2025
58c2611
Merge branch 'main' into ip-parsing-optimization
felixbarny Aug 12, 2025
0c16cab
Fix tests that expected an InetAddressPoint
felixbarny Aug 13, 2025
e626e8f
More allocation optimizations for parsing ip4v addresses
felixbarny Aug 13, 2025
f90f7f4
Merge remote-tracking branch 'origin/main' into ip-parsing-optimization
felixbarny Aug 13, 2025
3cb3979
Merge remote-tracking branch 'origin/main' into ip-parsing-optimization
felixbarny Aug 18, 2025
c4c48f4
Address comments from review
felixbarny Aug 18, 2025
bfe7c62
Merge remote-tracking branch 'origin/main' into ip-parsing-optimization
felixbarny Aug 19, 2025
349792d
Address review comments
felixbarny Aug 19, 2025
f201471
Remove remaining use of Text
felixbarny Aug 19, 2025
90eacee
Add benchmarks
felixbarny Aug 19, 2025
88afe92
Merge remote-tracking branch 'origin/main' into ip-parsing-optimization
felixbarny Aug 19, 2025
e2fc667
Simplify and optimize quad to hex conversion
felixbarny Aug 19, 2025
93e0691
Merge branch 'main' into ip-parsing-optimization
felixbarny Aug 19, 2025
61ecab5
[CI] Auto commit changes from spotless
Aug 19, 2025
2b1d9d1
Use root locale for String.format
felixbarny Aug 19, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

package org.elasticsearch.index.mapper;

import org.apache.lucene.document.Field;
import org.apache.lucene.document.FieldType;
import org.apache.lucene.document.InetAddressPoint;
import org.apache.lucene.util.BytesRef;
import org.elasticsearch.common.network.CIDRUtils;
import org.elasticsearch.common.network.InetAddresses;
import org.elasticsearch.core.SuppressForbidden;
import org.elasticsearch.xcontent.XContentString;

import java.net.InetAddress;

/**
* A Lucene {@link Field} that stores an IP address as a point.
* This is similar to {@link InetAddressPoint} but uses a more efficient way to parse IP addresses
* that doesn't require the address to be an {@link InetAddress} object.
* Otherwise, it behaves just like the {@link InetAddressPoint} field.
*/
class ESInetAddressPoint extends Field {
private static final FieldType TYPE;

static {
TYPE = new FieldType();
TYPE.setDimensions(1, InetAddressPoint.BYTES);
TYPE.freeze();
}

private final XContentString ipString;
private final InetAddress inetAddress;

protected ESInetAddressPoint(String name, XContentString ipString) {
super(name, TYPE);
this.fieldsData = new BytesRef(InetAddresses.encodeAsIpv6(ipString));
this.ipString = ipString;
this.inetAddress = null;
}

protected ESInetAddressPoint(String name, InetAddress inetAddress) {
super(name, TYPE);
this.fieldsData = new BytesRef(CIDRUtils.encode(inetAddress.getAddress()));
this.inetAddress = inetAddress;
this.ipString = null;
}

public InetAddress getInetAddress() {
if (ipString != null) {
return InetAddresses.forString(ipString.bytes());
}
if (inetAddress != null) {
return inetAddress;
}
throw new IllegalStateException("Neither ipString nor inetAddress is set");
}

@Override
@SuppressForbidden(
reason = "Calling InetAddress#getHostAddress to mimic what InetAddressPoint does. "
+ "Some tests depend on the exact string representation."
)
public String toString() {
StringBuilder result = new StringBuilder();
result.append(getClass().getSimpleName());
result.append(" <");
result.append(name);
result.append(':');

// IPv6 addresses are bracketed, to not cause confusion with historic field:value representation
BytesRef bytes = (BytesRef) fieldsData;
InetAddress address = InetAddressPoint.decode(BytesRef.deepCopyOf(bytes).bytes);
if (address.getAddress().length == 16) {
result.append('[');
result.append(address.getHostAddress());
result.append(']');
} else {
result.append(address.getHostAddress());
}

result.append('>');
return result.toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@

package org.elasticsearch.index.mapper;

import org.apache.lucene.document.Field;
import org.apache.lucene.document.InetAddressPoint;
import org.apache.lucene.document.SortedSetDocValuesField;
import org.apache.lucene.document.StoredField;
Expand Down Expand Up @@ -44,6 +43,7 @@
import org.elasticsearch.search.lookup.FieldValues;
import org.elasticsearch.search.lookup.SearchLookup;
import org.elasticsearch.xcontent.XContentParser;
import org.elasticsearch.xcontent.XContentString;

import java.io.IOException;
import java.net.InetAddress;
Expand Down Expand Up @@ -644,10 +644,12 @@ protected String contentType() {

@Override
protected void parseCreateField(DocumentParserContext context) throws IOException {
InetAddress address;
String value = context.parser().textOrNull();
ESInetAddressPoint address;
XContentString value = context.parser().optimizedTextOrNull();
try {
address = value == null ? nullValue : InetAddresses.forString(value);
address = value == null
? nullValue == null ? null : new ESInetAddressPoint(fieldType().name(), nullValue)
: new ESInetAddressPoint(fieldType().name(), value);
} catch (IllegalArgumentException e) {
if (ignoreMalformed) {
context.addIgnoredField(fieldType().name());
Expand All @@ -665,29 +667,29 @@ protected void parseCreateField(DocumentParserContext context) throws IOExceptio
}
if (offsetsFieldName != null && context.isImmediateParentAnArray() && context.canAddIgnoredField()) {
if (address != null) {
BytesRef sortableValue = new BytesRef(InetAddressPoint.encode(address));
BytesRef sortableValue = address.binaryValue();
context.getOffSetContext().recordOffset(offsetsFieldName, sortableValue);
} else {
context.getOffSetContext().recordNull(offsetsFieldName);
}
}
}

private void indexValue(DocumentParserContext context, InetAddress address) {
private void indexValue(DocumentParserContext context, ESInetAddressPoint address) {
if (dimension) {
context.getRoutingFields().addIp(fieldType().name(), address);
context.getRoutingFields().addIp(fieldType().name(), address.getInetAddress());
}
LuceneDocument doc = context.doc();
if (indexed) {
Field field = new InetAddressPoint(fieldType().name(), address);
context.doc().add(field);
doc.add(address);
}
if (hasDocValues) {
context.doc().add(new SortedSetDocValuesField(fieldType().name(), new BytesRef(InetAddressPoint.encode(address))));
doc.add(new SortedSetDocValuesField(fieldType().name(), address.binaryValue()));
} else if (stored || indexed) {
context.addToFieldNames(fieldType().name());
}
if (stored) {
context.doc().add(new StoredField(fieldType().name(), new BytesRef(InetAddressPoint.encode(address))));
doc.add(new StoredField(fieldType().name(), address.binaryValue()));
}
}

Expand All @@ -698,7 +700,12 @@ protected void indexScriptValues(
int doc,
DocumentParserContext documentParserContext
) {
this.scriptValues.valuesForDoc(searchLookup, readerContext, doc, value -> indexValue(documentParserContext, value));
this.scriptValues.valuesForDoc(
searchLookup,
readerContext,
doc,
value -> indexValue(documentParserContext, new ESInetAddressPoint(fieldType().name(), value))
);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,14 @@ public Field getRangeField(String name, RangeFieldMapper.Range r) {
@Override
public InetAddress parseFrom(RangeFieldMapper.RangeFieldType fieldType, XContentParser parser, boolean coerce, boolean included)
throws IOException {
InetAddress address = InetAddresses.forString(parser.text());
InetAddress address = InetAddresses.forString(parser.optimizedText().bytes());
return included ? address : nextUp(address);
}

@Override
public InetAddress parseTo(RangeFieldMapper.RangeFieldType fieldType, XContentParser parser, boolean coerce, boolean included)
throws IOException {
InetAddress address = InetAddresses.forString(parser.text());
InetAddress address = InetAddresses.forString(parser.optimizedText().bytes());
return included ? address : nextDown(address);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@

import org.elasticsearch.core.Tuple;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.xcontent.Text;
import org.hamcrest.Matchers;

import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.util.Enumeration;

import static org.hamcrest.Matchers.equalTo;
Expand Down Expand Up @@ -171,12 +173,16 @@ public void testForStringIPv6EightColons() throws UnknownHostException {
}

public void testConvertDottedQuadToHex() throws UnknownHostException {
String[] ipStrings = { "7::0.128.0.127", "7::0.128.0.128", "7::128.128.0.127", "7::0.128.128.127" };
String[] ipStrings = { "7::0.128.0.127", "7::0.128.0.128", "7::128.128.0.127", "7::0.128.128.127", "::ffff:10.10.1.1" };

for (String ipString : ipStrings) {
// Shouldn't hit DNS, because it's an IP string literal.
InetAddress ipv6Addr = InetAddress.getByName(ipString);
assertEquals(ipv6Addr, InetAddresses.forString(ipString));
byte[] asBytes = ipString.getBytes(StandardCharsets.UTF_8);
byte[] bytes = new byte[32];
System.arraycopy(asBytes, 0, bytes, 8, asBytes.length);
assertEquals(ipv6Addr, InetAddresses.forString(bytes, 8, asBytes.length));
assertTrue(InetAddresses.isInetAddress(ipString));
}
}
Expand Down Expand Up @@ -244,4 +250,13 @@ public void testParseCidr() {
assertEquals(InetAddresses.forString("::fffe:0:0"), cidr.v1());
assertEquals(Integer.valueOf(128), cidr.v2());
}

public void testEncodeAsIpv6() throws Exception {
assertEquals(16, InetAddresses.encodeAsIpv6(new Text("::1")).length);
assertEquals(16, InetAddresses.encodeAsIpv6(new Text("192.168.0.0")).length);
assertEquals(
"192.168.0.0",
InetAddresses.toAddrString(InetAddress.getByAddress(InetAddresses.encodeAsIpv6(new Text("192.168.0.0"))))
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@

package org.elasticsearch.index.mapper;

import org.apache.lucene.document.InetAddressPoint;
import org.apache.lucene.document.IntField;
import org.apache.lucene.document.LongField;
import org.apache.lucene.index.IndexOptions;
Expand Down Expand Up @@ -2205,22 +2204,22 @@ public void testMatchAndUnmatchWithArrayOfFieldNamesMapToIpType() throws IOExcep
merge(mapperService, dynamicMapping(parsedDoc.dynamicMappingsUpdate()));
LuceneDocument doc = parsedDoc.rootDoc();

assertNotEquals(InetAddressPoint.class, doc.getField("one_ip").getClass());
assertNotEquals(ESInetAddressPoint.class, doc.getField("one_ip").getClass());
Mapper fieldMapper = mapperService.documentMapper().mappers().getMapper("one_ip");
assertNotNull(fieldMapper);
assertEquals("text", fieldMapper.typeName());

assertNotEquals(InetAddressPoint.class, doc.getField("ip_two").getClass());
assertNotEquals(ESInetAddressPoint.class, doc.getField("ip_two").getClass());
fieldMapper = mapperService.documentMapper().mappers().getMapper("ip_two");
assertNotNull(fieldMapper);
assertEquals("text", fieldMapper.typeName());

assertEquals(InetAddressPoint.class, doc.getField("three_ip").getClass());
assertEquals(ESInetAddressPoint.class, doc.getField("three_ip").getClass());
fieldMapper = mapperService.documentMapper().mappers().getMapper("three_ip");
assertNotNull(fieldMapper);
assertEquals("ip", fieldMapper.typeName());

assertEquals(InetAddressPoint.class, doc.getField("ip_four").getClass());
assertEquals(ESInetAddressPoint.class, doc.getField("ip_four").getClass());
fieldMapper = mapperService.documentMapper().mappers().getMapper("ip_four");
assertNotNull(fieldMapper);
assertEquals("ip", fieldMapper.typeName());
Expand Down Expand Up @@ -2257,17 +2256,17 @@ public void testMatchWithArrayOfFieldNamesUsingRegex() throws IOException {
merge(mapperService, dynamicMapping(parsedDoc.dynamicMappingsUpdate()));
LuceneDocument doc = parsedDoc.rootDoc();

assertEquals(InetAddressPoint.class, doc.getField("one100_ip").getClass());
assertEquals(ESInetAddressPoint.class, doc.getField("one100_ip").getClass());
Mapper fieldMapper = mapperService.documentMapper().mappers().getMapper("one100_ip");
assertNotNull(fieldMapper);
assertEquals("ip", fieldMapper.typeName());

assertEquals(InetAddressPoint.class, doc.getField("iptwo").getClass());
assertEquals(ESInetAddressPoint.class, doc.getField("iptwo").getClass());
fieldMapper = mapperService.documentMapper().mappers().getMapper("iptwo");
assertNotNull(fieldMapper);
assertEquals("ip", fieldMapper.typeName());

assertNotEquals(InetAddressPoint.class, doc.getField("threeip").getClass());
assertNotEquals(ESInetAddressPoint.class, doc.getField("threeip").getClass());
fieldMapper = mapperService.documentMapper().mappers().getMapper("threeip");
assertNotNull(fieldMapper);
assertEquals("text", fieldMapper.typeName());
Expand Down Expand Up @@ -2303,18 +2302,18 @@ public void testSimpleMatchWithArrayOfFieldNamesMixingGlobsAndRegex() throws IOE
merge(mapperService, dynamicMapping(parsedDoc.dynamicMappingsUpdate()));
LuceneDocument doc = parsedDoc.rootDoc();

assertEquals(InetAddressPoint.class, doc.getField("oneip").getClass());
assertEquals(ESInetAddressPoint.class, doc.getField("oneip").getClass());
Mapper fieldMapper = mapperService.documentMapper().mappers().getMapper("oneip");
assertNotNull(fieldMapper);
assertEquals("ip", fieldMapper.typeName());

// this one will not match and be an IP field because it was specified with a regex but match_pattern is implicit "simple"
assertNotEquals(InetAddressPoint.class, doc.getField("iptwo").getClass());
assertNotEquals(ESInetAddressPoint.class, doc.getField("iptwo").getClass());
fieldMapper = mapperService.documentMapper().mappers().getMapper("iptwo");
assertNotNull(fieldMapper);
assertEquals("text", fieldMapper.typeName());

assertNotEquals(InetAddressPoint.class, doc.getField("threeip").getClass());
assertNotEquals(ESInetAddressPoint.class, doc.getField("threeip").getClass());
fieldMapper = mapperService.documentMapper().mappers().getMapper("threeip");
assertNotNull(fieldMapper);
assertEquals("text", fieldMapper.typeName());
Expand Down Expand Up @@ -2362,18 +2361,18 @@ public void testDefaultMatchTypeWithArrayOfFieldNamesMixingGlobsAndRegexInPathMa

LuceneDocument doc = parsedDoc.rootDoc();

assertEquals(InetAddressPoint.class, doc.getField("outer.oneip").getClass());
assertEquals(ESInetAddressPoint.class, doc.getField("outer.oneip").getClass());
Mapper fieldMapper = mapperService.documentMapper().mappers().getMapper("outer.oneip");
assertNotNull(fieldMapper);
assertEquals("ip", fieldMapper.typeName());

// this one will not match and be an IP field because it was specified with a regex but match_pattern is implicit "simple"
assertNotEquals(InetAddressPoint.class, doc.getField("outer.iptwo").getClass());
assertNotEquals(ESInetAddressPoint.class, doc.getField("outer.iptwo").getClass());
fieldMapper = mapperService.documentMapper().mappers().getMapper("outer.iptwo");
assertNotNull(fieldMapper);
assertEquals("text", fieldMapper.typeName());

assertEquals(InetAddressPoint.class, doc.getField("outer.threeip").getClass());
assertEquals(ESInetAddressPoint.class, doc.getField("outer.threeip").getClass());
fieldMapper = mapperService.documentMapper().mappers().getMapper("outer.threeip");
assertNotNull(fieldMapper);
assertEquals("ip", fieldMapper.typeName());
Expand Down Expand Up @@ -2421,18 +2420,18 @@ public void testSimpleMatchTypeWithArrayOfFieldNamesMixingGlobsAndRegexInPathMat

LuceneDocument doc = parsedDoc.rootDoc();

assertEquals(InetAddressPoint.class, doc.getField("outer.oneip").getClass());
assertEquals(ESInetAddressPoint.class, doc.getField("outer.oneip").getClass());
Mapper fieldMapper = mapperService.documentMapper().mappers().getMapper("outer.oneip");
assertNotNull(fieldMapper);
assertEquals("ip", fieldMapper.typeName());

// this one will not match and be an IP field because it was specified with a regex but match_pattern is implicit "simple"
assertNotEquals(InetAddressPoint.class, doc.getField("outer.iptwo").getClass());
assertNotEquals(ESInetAddressPoint.class, doc.getField("outer.iptwo").getClass());
fieldMapper = mapperService.documentMapper().mappers().getMapper("outer.iptwo");
assertNotNull(fieldMapper);
assertEquals("text", fieldMapper.typeName());

assertEquals(InetAddressPoint.class, doc.getField("outer.threeip").getClass());
assertEquals(ESInetAddressPoint.class, doc.getField("outer.threeip").getClass());
fieldMapper = mapperService.documentMapper().mappers().getMapper("outer.threeip");
assertNotNull(fieldMapper);
assertEquals("ip", fieldMapper.typeName());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,16 +75,16 @@ protected IpFieldScript.Factory multipleValuesScript() {
@Override
protected void assertMultipleValues(List<IndexableField> fields) {
assertEquals(4, fields.size());
assertEquals("InetAddressPoint <field:[0:0:0:0:0:0:0:1]>", fields.get(0).toString());
assertEquals("ESInetAddressPoint <field:[0:0:0:0:0:0:0:1]>", fields.get(0).toString());
assertEquals("docValuesType=SORTED_SET<field:[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1]>", fields.get(1).toString());
assertEquals("InetAddressPoint <field:[0:0:0:0:0:0:0:2]>", fields.get(2).toString());
assertEquals("ESInetAddressPoint <field:[0:0:0:0:0:0:0:2]>", fields.get(2).toString());
assertEquals("docValuesType=SORTED_SET<field:[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2]>", fields.get(3).toString());
}

@Override
protected void assertDocValuesDisabled(List<IndexableField> fields) {
assertEquals(1, fields.size());
assertEquals("InetAddressPoint <field:[0:0:0:0:0:0:0:1]>", fields.get(0).toString());
assertEquals("ESInetAddressPoint <field:[0:0:0:0:0:0:0:1]>", fields.get(0).toString());
}

@Override
Expand Down