Skip to content

Commit f2760c6

Browse files
Nicer buffer handling (#93491)
Some optimisations that I found when reusing searchable snapshot code elsewhere: * Add an efficient input stream -> byte buffer path that avoids allocations + copies for heap buffers, this is non-trivial in its effects IMO * Also at least avoid allocations and use existing thread-local buffer when doing input stream -> direct bb * move `readFully` to lower level streams class to enable this * Use same thread local direct byte buffer for frozen and caching index input instead of constantly allocating new heap buffers and writing those to disk inefficiently
1 parent e837ff7 commit f2760c6

File tree

19 files changed

+147
-121
lines changed

19 files changed

+147
-121
lines changed

libs/core/src/main/java/org/elasticsearch/core/Streams.java

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import java.io.IOException;
1313
import java.io.InputStream;
1414
import java.io.OutputStream;
15+
import java.nio.ByteBuffer;
1516

1617
/**
1718
* Simple utility methods for file and stream copying. All copy methods close all affected streams when done.
@@ -78,6 +79,62 @@ public static long copy(final InputStream in, final OutputStream out) throws IOE
7879
return copy(in, out, LOCAL_BUFFER.get(), true);
7980
}
8081

82+
/**
83+
* Read up to {code count} bytes from {@code input} and store them into {@code buffer}.
84+
* The buffers position will be incremented by the number of bytes read from the stream.
85+
* @param input stream to read from
86+
* @param buffer buffer to read into
87+
* @param count maximum number of bytes to read
88+
* @return number of bytes read from the stream
89+
* @throws IOException in case of I/O errors
90+
*/
91+
public static int read(InputStream input, ByteBuffer buffer, int count) throws IOException {
92+
if (buffer.hasArray()) {
93+
return readToHeapBuffer(input, buffer, count);
94+
}
95+
return readToDirectBuffer(input, buffer, count);
96+
}
97+
98+
private static int readToHeapBuffer(InputStream input, ByteBuffer buffer, int count) throws IOException {
99+
final int pos = buffer.position();
100+
int read = readFully(input, buffer.array(), buffer.arrayOffset() + pos, count);
101+
if (read > 0) {
102+
buffer.position(pos + read);
103+
}
104+
return read;
105+
}
106+
107+
private static int readToDirectBuffer(InputStream input, ByteBuffer b, int count) throws IOException {
108+
int totalRead = 0;
109+
final byte[] buffer = LOCAL_BUFFER.get();
110+
while (totalRead < count) {
111+
final int len = Math.min(count - totalRead, buffer.length);
112+
final int read = input.read(buffer, 0, len);
113+
if (read == -1) {
114+
break;
115+
}
116+
b.put(buffer, 0, read);
117+
totalRead += read;
118+
}
119+
return totalRead;
120+
}
121+
122+
public static int readFully(InputStream reader, byte[] dest) throws IOException {
123+
return readFully(reader, dest, 0, dest.length);
124+
}
125+
126+
public static int readFully(InputStream reader, byte[] dest, int offset, int len) throws IOException {
127+
int read = 0;
128+
while (read < len) {
129+
final int r = reader.read(dest, offset + read, len - read);
130+
if (r == -1) {
131+
break;
132+
}
133+
read += r;
134+
}
135+
return read;
136+
}
137+
81138
/**
82139
* Wraps an {@link OutputStream} such that it's {@code close} method becomes a noop
83140
*

modules/repository-azure/src/test/java/org/elasticsearch/repositories/azure/AzureBlobContainerRetriesTests.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,10 @@ public void testWriteBlobWithRetries() throws Exception {
187187

188188
if (randomBoolean()) {
189189
if (randomBoolean()) {
190-
Streams.readFully(exchange.getRequestBody(), new byte[randomIntBetween(1, Math.max(1, bytes.length - 1))]);
190+
org.elasticsearch.core.Streams.readFully(
191+
exchange.getRequestBody(),
192+
new byte[randomIntBetween(1, Math.max(1, bytes.length - 1))]
193+
);
191194
} else {
192195
Streams.readFully(exchange.getRequestBody());
193196
AzureHttpHandler.sendError(exchange, randomFrom(RestStatus.INTERNAL_SERVER_ERROR, RestStatus.SERVICE_UNAVAILABLE));

modules/repository-gcs/src/main/java/org/elasticsearch/repositories/gcs/GoogleCloudStorageBlobStore.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,12 @@
2929
import org.elasticsearch.common.blobstore.support.BlobMetadata;
3030
import org.elasticsearch.common.bytes.BytesReference;
3131
import org.elasticsearch.common.hash.MessageDigests;
32-
import org.elasticsearch.common.io.Streams;
3332
import org.elasticsearch.common.io.stream.ReleasableBytesStreamOutput;
3433
import org.elasticsearch.common.unit.ByteSizeUnit;
3534
import org.elasticsearch.common.unit.ByteSizeValue;
3635
import org.elasticsearch.common.util.BigArrays;
3736
import org.elasticsearch.core.CheckedConsumer;
37+
import org.elasticsearch.core.Streams;
3838
import org.elasticsearch.core.SuppressForbidden;
3939

4040
import java.io.ByteArrayInputStream;

modules/repository-gcs/src/test/java/org/elasticsearch/repositories/gcs/GoogleCloudStorageBlobContainerRetriesTests.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,10 @@ public void testWriteBlobWithRetries() throws Exception {
218218
}
219219
if (randomBoolean()) {
220220
if (randomBoolean()) {
221-
Streams.readFully(exchange.getRequestBody(), new byte[randomIntBetween(1, Math.max(1, bytes.length - 1))]);
221+
org.elasticsearch.core.Streams.readFully(
222+
exchange.getRequestBody(),
223+
new byte[randomIntBetween(1, Math.max(1, bytes.length - 1))]
224+
);
222225
} else {
223226
Streams.readFully(exchange.getRequestBody());
224227
exchange.sendResponseHeaders(HttpStatus.SC_INTERNAL_SERVER_ERROR, -1);
@@ -241,7 +244,7 @@ public void testWriteBlobWithReadTimeouts() {
241244
httpServer.createContext("/upload/storage/v1/b/bucket/o", exchange -> {
242245
if (randomBoolean()) {
243246
if (randomBoolean()) {
244-
Streams.readFully(exchange.getRequestBody(), new byte[randomIntBetween(1, bytes.length - 1)]);
247+
org.elasticsearch.core.Streams.readFully(exchange.getRequestBody(), new byte[randomIntBetween(1, bytes.length - 1)]);
245248
} else {
246249
Streams.readFully(exchange.getRequestBody());
247250
}

modules/repository-s3/src/test/java/org/elasticsearch/repositories/s3/S3BlobContainerRetriesTests.java

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,10 @@ public void testWriteBlobWithRetries() throws Exception {
190190

191191
if (randomBoolean()) {
192192
if (randomBoolean()) {
193-
Streams.readFully(exchange.getRequestBody(), new byte[randomIntBetween(1, Math.max(1, bytes.length - 1))]);
193+
org.elasticsearch.core.Streams.readFully(
194+
exchange.getRequestBody(),
195+
new byte[randomIntBetween(1, Math.max(1, bytes.length - 1))]
196+
);
194197
} else {
195198
Streams.readFully(exchange.getRequestBody());
196199
exchange.sendResponseHeaders(
@@ -222,7 +225,7 @@ public void testWriteBlobWithReadTimeouts() {
222225
httpServer.createContext(downloadStorageEndpoint(blobContainer, "write_blob_timeout"), exchange -> {
223226
if (randomBoolean()) {
224227
if (randomBoolean()) {
225-
Streams.readFully(exchange.getRequestBody(), new byte[randomIntBetween(1, bytes.length - 1)]);
228+
org.elasticsearch.core.Streams.readFully(exchange.getRequestBody(), new byte[randomIntBetween(1, bytes.length - 1)]);
226229
} else {
227230
Streams.readFully(exchange.getRequestBody());
228231
}
@@ -317,7 +320,10 @@ public void testWriteLargeBlob() throws Exception {
317320
// sends an error back or let the request time out
318321
if (useTimeout == false) {
319322
if (randomBoolean() && contentLength > 0) {
320-
Streams.readFully(exchange.getRequestBody(), new byte[randomIntBetween(1, Math.toIntExact(contentLength - 1))]);
323+
org.elasticsearch.core.Streams.readFully(
324+
exchange.getRequestBody(),
325+
new byte[randomIntBetween(1, Math.toIntExact(contentLength - 1))]
326+
);
321327
} else {
322328
Streams.readFully(exchange.getRequestBody());
323329
exchange.sendResponseHeaders(
@@ -412,7 +418,10 @@ public void testWriteLargeBlobStreaming() throws Exception {
412418
// sends an error back or let the request time out
413419
if (useTimeout == false) {
414420
if (randomBoolean() && contentLength > 0) {
415-
Streams.readFully(exchange.getRequestBody(), new byte[randomIntBetween(1, Math.toIntExact(contentLength - 1))]);
421+
org.elasticsearch.core.Streams.readFully(
422+
exchange.getRequestBody(),
423+
new byte[randomIntBetween(1, Math.toIntExact(contentLength - 1))]
424+
);
416425
} else {
417426
Streams.readFully(exchange.getRequestBody());
418427
exchange.sendResponseHeaders(

modules/repository-url/src/main/java/org/elasticsearch/common/blobstore/url/http/URLHttpClient.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@
2121
import org.apache.http.ssl.SSLContexts;
2222
import org.apache.logging.log4j.LogManager;
2323
import org.apache.logging.log4j.Logger;
24-
import org.elasticsearch.common.io.Streams;
2524
import org.elasticsearch.core.IOUtils;
2625
import org.elasticsearch.core.Nullable;
26+
import org.elasticsearch.core.Streams;
2727
import org.elasticsearch.rest.RestStatus;
2828

2929
import java.io.Closeable;

plugins/repository-hdfs/src/test/java/org/elasticsearch/repositories/hdfs/HdfsBlobStoreContainerTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
import org.elasticsearch.common.blobstore.BlobContainer;
1919
import org.elasticsearch.common.blobstore.BlobPath;
2020
import org.elasticsearch.common.bytes.BytesArray;
21-
import org.elasticsearch.common.io.Streams;
21+
import org.elasticsearch.core.Streams;
2222
import org.elasticsearch.core.SuppressForbidden;
2323
import org.elasticsearch.test.ESTestCase;
2424
import org.hamcrest.CoreMatchers;

server/src/main/java/org/elasticsearch/common/compress/DeflateCompressor.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@
1010

1111
import org.elasticsearch.Assertions;
1212
import org.elasticsearch.common.bytes.BytesReference;
13-
import org.elasticsearch.common.io.Streams;
1413
import org.elasticsearch.common.io.stream.BytesStreamOutput;
1514
import org.elasticsearch.core.Releasable;
15+
import org.elasticsearch.core.Streams;
1616

1717
import java.io.BufferedInputStream;
1818
import java.io.BufferedOutputStream;

server/src/main/java/org/elasticsearch/common/io/Streams.java

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -136,22 +136,6 @@ public static String copyToString(Reader in) throws IOException {
136136
return out.toString();
137137
}
138138

139-
public static int readFully(InputStream reader, byte[] dest) throws IOException {
140-
return readFully(reader, dest, 0, dest.length);
141-
}
142-
143-
public static int readFully(InputStream reader, byte[] dest, int offset, int len) throws IOException {
144-
int read = 0;
145-
while (read < len) {
146-
final int r = reader.read(dest, offset + read, len - read);
147-
if (r == -1) {
148-
break;
149-
}
150-
read += r;
151-
}
152-
return read;
153-
}
154-
155139
/**
156140
* Fully consumes the input stream, throwing the bytes away. Returns the number of bytes consumed.
157141
*/

server/src/main/java/org/elasticsearch/common/io/stream/InputStreamStreamInput.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
package org.elasticsearch.common.io.stream;
1010

11-
import org.elasticsearch.common.io.Streams;
11+
import org.elasticsearch.core.Streams;
1212

1313
import java.io.EOFException;
1414
import java.io.IOException;

0 commit comments

Comments
 (0)