Interface StorageObject
- All Known Implementing Classes:
AbstractMeteredStorageObject
Simple providers: implement sync methods - async wrapping is automatic. Async-capable providers (HTTP, S3): override async methods for native non-blocking I/O.
Provides metadata access and methods to open streams for reading. Uses standard Java InputStream for compatibility with existing Elasticsearch code. Random access is handled via range-based reads (like BlobContainer pattern).
StorageObject instances themselves do not hold open resources and do not need closing.
The InputStream instances returned by newStream() and
newStream(long, long) are the resources that callers must close.
-
Field Summary
FieldsModifier and TypeFieldDescriptionstatic final longSentinellengthfornewStream(long, long)meaning "read frompositionto the end of the object" — the open-ended form.static final intTransfer buffer size used by the defaultreadBytes(long, ByteBuffer)when reading into a direct ByteBuffer via an InputStream. -
Method Summary
Modifier and TypeMethodDescriptiondefault voidabortStream(InputStream stream) Closes a stream opened by this object, discarding any unread bytes without blocking.default voidattachMetrics(ExternalSourceMetrics metrics, String scheme) Attaches the node telemetry sink and the storageschemedimension so that this object's read/retry events are published toExternalSourceMetrics(in addition to the profile counters surfaced bymetrics()).booleanexists()Checks if the object exists.Returns the last modification time, or null if not available.longlength()Returns the object size in bytes.default StorageObjectMetricsmetrics()Returns cumulative I/O counters for reads against this object.default InputStreamOpens an input stream for sequential reading of the whole object, from the beginning to the end.newStream(long position, long length) Opens an input stream for reading a byte range.path()Returns the path of this object.default intreadBytes(long position, ByteBuffer target) Reads bytes from a specific position directly into aByteBuffer.default voidreadBytesAsync(long position, long length, DirectBufferFactory factory, Executor executor, ActionListener<DirectReadBuffer> listener) Async byte read with ActionListener callback.default voidreadBytesAsync(long position, ByteBuffer target, Executor executor, ActionListener<Integer> listener) Async byte read into a caller-provided ByteBuffer.default booleanReturns true if this object has native async support.
-
Field Details
-
TRANSFER_BUFFER_SIZE
static final int TRANSFER_BUFFER_SIZETransfer buffer size used by the defaultreadBytes(long, ByteBuffer)when reading into a direct ByteBuffer via an InputStream. Kept small to avoid large stack allocations while still being efficient for typical I/O page sizes.- See Also:
-
READ_TO_END
static final long READ_TO_ENDSentinellengthfornewStream(long, long)meaning "read frompositionto the end of the object" — the open-ended form. It exists because the total length is not always knowable up front (e.g. a compressed object has no addressable length), so the open-ended read must signal "to the end" with this marker rather than by computinglength() - position.- See Also:
-
-
Method Details
-
newStream
Opens an input stream for sequential reading of the whole object, from the beginning to the end.The default is
newStream(0, READ_TO_END), so the whole-object read shares the open-ended code path (and, through the decorator chain, its resilience). A provider may override this for a plain whole-object GET.- Throws:
IOException
-
newStream
Opens an input stream for reading a byte range. AlengthofREAD_TO_ENDreads frompositionto the end of the object (the open-ended form) — essential for streams whose total length is not available. Providers MUST translateREAD_TO_ENDinto a native open-ended read (e.g. HTTPRange: bytes=position-) and MUST NOT requirelength()to serve it; an empty object (orpositionat/after the end) yields an empty stream.Critical for columnar formats like Parquet that read specific column chunks. For reading object footers, use a bounded length:
newStream(length() - footerSize, footerSize).- Throws:
IOException
-
length
Returns the object size in bytes.- Throws:
IOException
-
lastModified
Returns the last modification time, or null if not available.- Throws:
IOException
-
exists
Checks if the object exists.- Throws:
IOException
-
path
StoragePath path()Returns the path of this object. -
abortStream
Closes a stream opened by this object, discarding any unread bytes without blocking.The default implementation calls
InputStream.close(), which is correct for local files and most providers (they simply close the connection). Override this for providers whoseclose()drains remaining bytes to reuse the connection pool — where closing a partially-read stream would block for the full remaining object transfer time.Use this instead of closing directly when the caller intentionally reads only a prefix of the stream (e.g., schema detection), and connection reuse is not required.
Contract:
streammust be the exactInputStreaminstance returned bynewStream()ornewStream(long, long)on this object — not a wrapper around it. Passing a wrapped stream (e.g. aBufferedInputStreamlayered on top) causes provider-specific overrides (e.g. the S3Abortablecast) to fall back to a drainingclose(), silently defeating the abort.- Throws:
IOException
-
readBytesAsync
default void readBytesAsync(long position, long length, DirectBufferFactory factory, Executor executor, ActionListener<DirectReadBuffer> listener) Async byte read with ActionListener callback.Default implementation wraps the sync
readBytes(long, ByteBuffer)method in an executor. Override this method for native async I/O (e.g., HTTP sendAsync, S3AsyncClient).Columnar formats (Parquet) can use this for parallel chunk reads when
supportsNativeAsync()returns true.Returned buffer contract: the
DirectReadBuffer.buffer()delivered to the listener hascapacity() == length(the requested length) andremaining()equal to the number of bytes actually read. On a short read these differ — consumers must useremaining()(orlimit() - position()) to size their work, nevercapacity(). The buffer is direct.On end-of-content at
positionthe buffer is delivered withremaining() == 0.Buffer ownership: the storage object obtains exactly one
DirectReadBufferoflengthbytes fromfactory. The caller must invokeDirectReadBuffer.close()once the bytes have been consumed; closing releases the buffer back to its underlying allocator. SeeDirectReadBufferfor the contract.Implementation contract: if the read fails, implementations must close the
DirectReadBufferbefore callinglistener.onFailure(). Callers that fan out reads across multiple merged ranges rely on this invariant to avoid double-releasing buffers from the successfully-completed sibling ranges on the failure path.- Parameters:
position- the starting byte positionlength- the number of bytes to readfactory- produces theDirectReadBufferthe bytes are read into; the storage object callsDirectBufferFactory.allocate(int)exactly once withlengthexecutor- executor for running the async operationlistener- callback for the result or failure
-
readBytesAsync
default void readBytesAsync(long position, ByteBuffer target, Executor executor, ActionListener<Integer> listener) Async byte read into a caller-provided ByteBuffer.Avoids per-call allocation by reading directly into the target buffer. For heap-backed buffers, reads directly into the backing array. For direct buffers, falls back to allocating a temporary array.
The buffer's position is advanced by the number of bytes read. The listener receives the number of bytes actually read.
- Parameters:
position- the starting byte position in the storage objecttarget- the ByteBuffer to read into; bytes are written starting attarget.position()executor- executor for running the async operationlistener- callback with the number of bytes read, or failure
-
readBytes
Reads bytes from a specific position directly into aByteBuffer.This is a positional, stateless read: each call specifies the position explicitly, so no mutable cursor state is needed. Providers override this to enable zero-copy I/O:
- Local files:
FileChannel.read(target, position)reads from OS page cache directly into both heap and direct buffers with no intermediate copies. - GCS:
ReadChannel.read(target)reads natively into the ByteBuffer. - S3/HTTP/Azure: The default stream-based implementation is used; for heap-backed buffers it reads directly into the backing array, for direct buffers it uses a small chunked transfer buffer (8 KB) instead of allocating a full-size temporary array.
The buffer's position is advanced by the number of bytes read.
- Parameters:
position- the starting byte position in the storage objecttarget- the ByteBuffer to read into; bytes are written starting attarget.position()- Returns:
- the number of bytes actually read, or -1 if the position is at or past end of content
- Throws:
IOException- if an I/O error occurs
- Local files:
-
supportsNativeAsync
default boolean supportsNativeAsync()Returns true if this object has native async support.Columnar formats (Parquet) can use this to determine whether to use
readBytesAsync(long, long, org.elasticsearch.xpack.esql.datasources.spi.DirectBufferFactory, java.util.concurrent.Executor, org.elasticsearch.action.ActionListener<org.elasticsearch.xpack.esql.datasources.spi.DirectReadBuffer>)for parallel chunk reads instead of sequential stream-based reads.- Returns:
- true if
readBytesAsync(long, long, org.elasticsearch.xpack.esql.datasources.spi.DirectBufferFactory, java.util.concurrent.Executor, org.elasticsearch.action.ActionListener<org.elasticsearch.xpack.esql.datasources.spi.DirectReadBuffer>)has a native implementation, false if it uses the default sync wrapper
-
metrics
Returns cumulative I/O counters for reads against this object.Implementations that don't track I/O return
StorageObjectMetrics.ZERO. Decorator wrappers must delegate to the wrapped object so counters are attributed to the underlying store, not the wrapper layer. -
attachMetrics
Attaches the node telemetry sink and the storageschemedimension so that this object's read/retry events are published toExternalSourceMetrics(in addition to the profile counters surfaced bymetrics()). Called once by the operator wiring when it opens the object. The default is a no-op for objects that don't track I/O; decorator wrappers must forward to the wrapped object so the metrics attach to the underlying store, not the wrapper layer.
-