Hi Aleksandr,
Thank you for the detailed response. I checked out PR #28547 [1], along
with the existing S3 and GCS. The implementation resolves some of my
earlier questions, while exposing a few lifecycle points that I think we
should address before freezing the shared contract.
1. Scope and interface layering
After reading the implementation, I agree that InputStreamOpener,
InputStreamExtension, and RawAndWrappedInputStreams have distinct
responsibilities:
- InputStreamOpener captures the provider-specific SDK operation.
- InputStreamExtension composes buffering or decryption.
- RawAndWrappedInputStreams distinguishes the SDK resource from the
readable
stream.
- The context objects provide an evolvable contract without changing
functional-interface signatures.
I no longer think the context wrappers are a performance concern.
StreamContextImpl
is allocated once per ObjectStorageInputStream; ReadContext and
RawAndWrappedInputStreams are created only when opening or reopening the
stream,
not on every read. Plain writes also reuse
WriteContext.EMPTY_WRITE_CONTEXT.
I therefore withdraw the suggestion to collapse these interfaces solely
to reduce
their number. I would keep the separation, but strengthen the lifecycle
contract
as described below.
I also agree with placing these types in flink-core. This gives all
filesystem
plugins one canonical type identity and avoids requiring every plugin to
package
another base module. I see this as a packaging and maintainability
decision rather
than a hard classloading requirement.
2. Thread safety and cancellation
I do not yet have a reproducible benchmark artefact strong enough to
support my
earlier throughput statement, so I suggest that we remove lock cost from
the
decision criteria until such data is available.
The cancellation concern remains independent of lock overhead.
ObjectStorageInputStream.read(byte[], ...) holds its ReentrantLock while
executing
the potentially blocking wrappedStream.read(...). close() then waits for
the same
lock using non-interruptible lock() [2].
Consequently, if the SDK read does not return, close() cannot reach the
underlying
resource to terminate it. Using a non-interruptible lock ensures that the
closer
does not abandon lock acquisition; it does not ensure that cleanup
completes.
The current input concurrency test only exercises lock contention during
stream
opening: the first thread blocks inside the opener while holding the
lock, and a
second reader is then interrupted while waiting to acquire it [3]. No
test drives
close() from a cancelling thread while another thread is already blocked
inside a
read of an open SDK stream. The output side, by contrast, already
specifies the
analogous ordering: close() blocks until a concurrent write releases the
lock [4].
On the read path, that same ordering turns a stuck SDK read into a stuck
cancellation.
Provider timeouts reduce the probability of an indefinite wait, but I do
not think
they should be the correctness mechanism for task cancellation. Their
values are
provider and configuration-dependent, and forceful slot release does not
guarantee prompt cleanup of the underlying HTTP connection.
My preferred contract is therefore:
- Normal read, write, seek, and position operations are thread-confined.
- Graceful close is ordered with normal operations and may finish
protocol work.
- Cancellation has a separate, idempotent abort path that may run
concurrently and
can terminate the native SDK resource without waiting for the blocked
operation.
- After abort, no data from that stream is considered valid or
consumable.
This is also consistent with the existing FSDataOutputStream contract,
which says
streams are generally not thread-safe but requires close() to be safe
when invoked
concurrently during cancellation [5].
Before accepting the shared implementation, I suggest adding a contract
test with a
delegate blocked in read() and asserting that cancellation can invoke the
provider
abort operation without first releasing the read.
3. Stream ownership, abort, buffering, and CSE
The abort requirements I had in mind are:
- Abort is idempotent and does not drain unread data.
- Abort can be invoked during a blocked read.
- Providers retain control over their native termination operation.
- Graceful close and abort have explicitly different semantics.
- The contract defines ownership and close ordering for the decorated
stream and
raw SDK resource.
- Cleanup attempts preserve the primary exception and attach later
failures as
suppressed exceptions.
The current RawAndWrappedInputStreams contains only two InputStream
references.
ObjectStorageInputStream closes the wrapper first and closes the raw SDK
stream
directly only if wrapper close fails [6]. This relies on the wrapper
owning and
closing the raw stream, but that ownership is described as "typically"
true rather
than enforced by the type.
There is also no provider-specific abort operation. Existing native S3
demonstrates why that matters: NativeS3InputStream explicitly calls
ResponseInputStream.abort() before closing the buffered and response
streams, and
its tests assert abort-before-close behaviour [7].
I suggest evolving the returned pair into an owned handle, or augmenting
it with
lifecycle callbacks, along these lines:
- readable stream
- graceful close
- cancellation abort
Buffering remains an independent decoration of the readable stream. The
ownership
proposal is not intended to remove BufferedInputStream or its forward-seek
optimization.
For CSE, I agree that the encryption-format details belong in FLIP-601.
The
reference implementation also reinforces the need for distinct
graceful-close and
abort semantics: AesGcmDecryptingInputStream.close() drains all unread
ciphertext
to authenticate the final GCM tag [8]. For a partially consumed large
object,
cancellation latency can therefore include reading the remainder of the
object,
not merely waiting for one SDK timeout.
A graceful close may reasonably finish authentication. Cancellation
should abort
without draining because the partially read data will be discarded.
I would also avoid assuming that Flink only reads files sequentially. The
Parquet
adapter exposes FSDataInputStream.seek() directly [9]; the Avro adapter
does the
same and additionally seeks to the checkpointed sync offset during state
restoration [10]. This does not require chunked CSE to be solved in
FLIP-597, but
the shared stream contract should leave room for logical-to-physical
offset mapping
in FLIP-601.
4. Existing open questions
RecoverableWriter
I agree that FLIP-597 should not introduce another common
RecoverableWriter
implementation. RecoverableWriter is already the common Flink contract;
persistence
protocols and state should remain provider-specific, with shared contract
tests
where useful.
GCS is a concrete example. Although GSFileSystem delegates ordinary
filesystem
operations to Hadoop, its recoverable writer already operates directly on
the GCS
SDK:
- GSFileSystem constructs GSBlobStorageImpl from the SDK Storage client.
- GSRecoverableWriter supports resume and exposes versioned serializers
[11].
- persist() closes the current immutable component object and records the
component
UUIDs and position.
- Commit hierarchically composes component objects because GCS compose
accepts at
most 32 sources.
- GSRecoverableWriterITCase exercises write, persist, recover, and commit
against a
real bucket.
FLIP-603 can therefore carry this provider-specific implementation
forward rather
than introducing a new shared recovery protocol.
PathsCopyingFileSystem
I agree that this should be a follow-up. The existing interface already
represents
the capability.
The current S3 implementation is tightly coupled to S3TransferManager,
performs
provider-specific batching, leaves completed and partial destination
cleanup to its
caller, and currently does not use the supplied close registry. This is
useful
implementation experience, but not yet evidence of a reusable cross-cloud
algorithm.
ObjectStorageOperations
I agree with the provider-local-first approach.
PR #28547 already follows it for Azure through the package-private
DataLakeStorageOperations interface and its in-memory test implementation
[13].
Existing GCS code similarly has GSBlobStorage and a test double for
writer
operations.
For a native GCS filesystem, the required operations would be
approximately:
- Metadata lookup.
- Prefix-and-delimiter listing.
- Ranged reads through ReadChannel.
- Conditional object creation using generation preconditions.
- Server-side copy.
- Compose with the 32-source limit.
- Batch deletion.
The semantics differ materially from Azure: ordinary GCS buckets have
inferred
directories, marker objects, copy-and-delete rename, and compose-based
recovery,
whereas ADLS Gen2 has real directories and atomic rename.
I suggest keeping these adapters provider-local, sharing filesystem
behaviour tests,
and extracting a common operations interface only after S3, Azure, and
GCS
demonstrate a stable semantic intersection.
5. Exception classification
I was not proposing an ExceptionClassifyingFileSystem wrapper as part of
FLIP-597.
I suggest a narrower rule for the initial work:
- Filesystem methods expose the established Flink exceptions, such as
FileNotFoundException and IOException.
- When translating an SDK exception, the original throwable is retained
as the
cause.
- A later child FLIP can define provider-specific classifiers for
authentication,
throttling, timeout, cancellation, and precondition failures.
The Azure reference implementation currently illustrates why even that
narrow rule
should be explicit. The 404 branches drop the SDK cause: getFileStatus()
and
open(), for example, throw FileNotFoundException without retaining the
DataLakeStorageException, and the same pattern recurs in listStatus() and
the
internal path-existence checks. The create() path, by contrast, does
retain the SDK
cause for 409 and 412 responses [14].
I suggest fixing this consistency issue. Always retaining the original
throwable as
the cause without introducing a cross-cloud exception taxonomy in
FLIP-597.
6. Migration and GCS recoverable state
I agree that deprecation must be evaluated per filesystem plugin.
FLIP-597 should
not imply deprecation of flink-fs-hadoop-shaded as a whole because HDFS
and other
Hadoop integrations remain outside this effort.
For recoverable-state migration, the general warning remains valid: state
is not
automatically portable when two plugins use different upload protocols
and
serializers.
GCS is more favourable because the existing Hadoop plugin already uses
the native
compose-based writer, and both its recoverable serializers are at version
1. The
state splits across two records [12]:
- The commit recoverable carries the final bucket and object name and the
component-object UUIDs.
- The resume recoverable extends it with the current logical write
position and
whether the stream was closed.
A native GCS plugin can be bidirectionally compatible if it preserves the
serializer version and bytes, component naming rules, and commit
protocol.
There is one important configuration constraint: the serialized state
does not
contain the temporary bucket or entropy mode.
GSCommitRecoverable.getComponentBlobIds()
reconstructs component paths using the current
gs.writer.temporary.bucket.name and
gs.filesink.entropy.enabled values [12]. Restoring with different values
will
resolve different object paths unless the component objects have been
moved
serializer version and bytes, component naming rules, and commit protocol.
There is one important configuration constraint: the serialized state
does not
contain the temporary bucket or entropy mode.
GSCommitRecoverable.getComponentBlobIds()
reconstructs component paths using the current
gs.writer.temporary.bucket.name and
gs.filesink.entropy.enabled values [12]. Restoring with different values
will
resolve different object paths unless the component objects have been
moved
accordingly.
FLIP-603 should therefore require:
- Identical recoverable serializers and component naming.
- Matching temporary-bucket and entropy configuration during migration.
- Roll-forward and rollback integration tests between flink-gs-fs-hadoop
and
flink-gcs-fs-native.
- Documentation that changing those options requires relocating
in-progress
components.
I have captured this design in the FLIP-603 draft.
7. One additional issue found in the reference implementation
ObjectStorageOutputStream.sync() currently calls commitAndClose(). Tests
explicitly require subsequent write(), flush(), and getPos() calls to
fail [15].
That is different from existing local and Hadoop streams, where sync()
persists
data without terminating the stream, and from the FSDataOutputStream
description of
sync() as fsync-like.
Object stores may be unable to make an object visible without completing
the
upload, so a terminal operation may be unavoidable for some SDKs.
However, I think
we should explicitly resolve whether:
- sync() remains non-terminal,
- the operation is unsupported for this common stream, or
- terminal sync becomes a documented object-storage-specific semantic.
I would prefer resolving this before treating ObjectStorageOutputStream
as a
cloud-generic implementation.
With these adjustments, my proposed convergence is:
- Keep the context and extension separation.
- Keep the shared stream types in flink-core.
- Add explicit graceful-close versus cancellation-abort semantics.
- Validate lifecycle behaviour with blocked-read cancellation tests and
S3/GCS
adapters.
- Keep recovery, bulk copy, storage operations, and exception
classification
provider-specific or in follow-up FLIPs.
- Use FLIP-603 as the second provider validation before extracting more
common
surface area.
Bests
Samrat
[1] https://github.com/apache/flink/pull/28547
[2]
https://github.com/apache/flink/blob/ed35c2a57a5152503ec914ccfa1298a4f04ad110/flink-core/src/main/java/org/apache/flink/core/fs/ObjectStorageInputStream.java#L303-L347
[3]
https://github.com/apache/flink/blob/ed35c2a57a5152503ec914ccfa1298a4f04ad110/flink-core/src/test/java/org/apache/flink/core/fs/ObjectStorageInputStreamTest.java#L499-L541
[4]
https://github.com/apache/flink/blob/ed35c2a57a5152503ec914ccfa1298a4f04ad110/flink-core/src/test/java/org/apache/flink/core/fs/ObjectStorageOutputStreamTest.java#L441-L519
[5]
https://github.com/apache/flink/blob/ed35c2a57a5152503ec914ccfa1298a4f04ad110/flink-core/src/main/java/org/apache/flink/core/fs/FSDataOutputStream.java#L39-L48
[6]
https://github.com/apache/flink/blob/ed35c2a57a5152503ec914ccfa1298a4f04ad110/flink-core/src/main/java/org/apache/flink/core/fs/ObjectStorageInputStream.java#L122-L161
[7]
https://github.com/apache/flink/blob/ed35c2a57a5152503ec914ccfa1298a4f04ad110/flink-filesystems/flink-s3-fs-native/src/main/java/org/apache/flink/fs/s3native/NativeS3InputStream.j
ava#L197-L245
https://github.com/apache/flink/blob/ed35c2a57a5152503ec914ccfa1298a4f04ad110/flink-filesystems/flink-s3-fs-native/src/test/java/org/apache/flink/fs/s3native/NativeS3InputStreamTe
st.java#L231-L239
[8]
https://github.com/apache/flink/blob/ed35c2a57a5152503ec914ccfa1298a4f04ad110/flink-filesystems/flink-fs-cse-aes-gcm/src/main/java/org/apache/flink/fs/cse/aes/gcm/AesGcmDecrypting
InputStream.java#L216-L240
[9]
https://github.com/apache/flink/blob/ed35c2a57a5152503ec914ccfa1298a4f04ad110/flink-formats/flink-parquet/src/main/java/org/apache/flink/formats/parquet/ParquetInputFile.java#L53-
L74
[10]
https://github.com/apache/flink/blob/ed35c2a57a5152503ec914ccfa1298a4f04ad110/flink-formats/flink-avro/src/main/java/org/apache/flink/formats/avro/utils/FSDataInputStreamWrapper.j
ava#L53-L56
https://github.com/apache/flink/blob/ed35c2a57a5152503ec914ccfa1298a4f04ad110/flink-formats/flink-avro/src/main/java/org/apache/flink/formats/avro/AvroInputFormat.java#L204-L210
[11]
https://github.com/apache/flink/blob/ed35c2a57a5152503ec914ccfa1298a4f04ad110/flink-filesystems/flink-gs-fs-hadoop/src/main/java/org/apache/flink/fs/gs/writer/GSRecoverableWriter
.
java#L59-L114
[12]
https://github.com/apache/flink/blob/ed35c2a57a5152503ec914ccfa1298a4f04ad110/flink-filesystems/flink-gs-fs-hadoop/src/main/java/org/apache/flink/fs/gs/writer/GSCommitRecoverable
.
java#L61-L90
https://github.com/apache/flink/blob/ed35c2a57a5152503ec914ccfa1298a4f04ad110/flink-filesystems/flink-gs-fs-hadoop/src/main/java/org/apache/flink/fs/gs/writer/GSResumeRecoverable
.
java#L37-L58
[13]
https://github.com/apache/flink/blob/ed35c2a57a5152503ec914ccfa1298a4f04ad110/flink-filesystems/flink-azure-fs-native/src/main/java/org/apache/flink/fs/azurefs/DataLakeStorageOper
ations.java#L35-L119
[14]
https://github.com/apache/flink/blob/ed35c2a57a5152503ec914ccfa1298a4f04ad110/flink-filesystems/flink-azure-fs-native/src/main/java/org/apache/flink/fs/azurefs/AzureDataLakeFileSy
stem.java#L262-L268
https://github.com/apache/flink/blob/ed35c2a57a5152503ec914ccfa1298a4f04ad110/flink-filesystems/flink-azure-fs-native/src/main/java/org/apache/flink/fs/azurefs/AzureDataLakeFileSy
stem.java#L326-L330
https://github.com/apache/flink/blob/ed35c2a57a5152503ec914ccfa1298a4f04ad110/flink-filesystems/flink-azure-fs-native/src/main/java/org/apache/flink/fs/azurefs/AzureDataLakeFileSy
stem.java#L531-L537
[15]
https://github.com/apache/flink/blob/ed35c2a57a5152503ec914ccfa1298a4f04ad110/flink-core/src/main/java/org/apache/flink/core/fs/ObjectStorageOutputStream.java#L159-L205
[16]
https://cwiki.apache.org/confluence/spaces/FLINK/pages/444334267/FLIP-603+FileSystems+GCS+SDK-native+filesystem
On Mon, Jul 13, 2026 at 9:29 PM Aleksandr Iushmanov <[email protected]>
wrote:
> Hi Samrat,
>
> Thank you for looking into FLIP-597[1] in great detail. And especially
> for trying it out for GCS/AWS implementations as this is the best
> way to find early issues with the interface design.
>
> Please find my thoughts below:
>
> 1. Scope and interface layering
>
> *re: layers*: I can probably add a diagram to explain the layers and the
> flow. Explicitly calling out 3 layers makes sense to me.
>
> *re: handle*: Speaking of the interfaces, could you please elaborate on
> your proposal? As per my current understanding we can discuss the
> following tradeoffs:
> a. ReadContext, StreamContext, WriteContext
> Currently, they wrap only small subset of arguments to pass:
>
> ReadContext -> long (position to re-open)
> StreamContext -> long, long (position, contentLength)
> WriteContext -> map<string, string> (file metadata)
>
> Yes, these wraps could be inlined but the values I
> see in them are: more explicit contract; better symmetry in the
> interfaces. From a maintainability point of view, it seems to me that
> making changes to these wrappers is easier than tracing free passed
> arguments. Do you have performance concerns around them?
>
> As for InputStreamOpener, InputStreamExtension, and
> RawAndWrappedInputStreams,
> they all have distinct roles: the opener is the provider-specific SDK call,
> the extension for cross-cutting concerns like CSE[2], and the stream pair
> to separate
> the readable stream from SDK connection.
> We could in theory collapse some of them, but imho it would hurt
> testability
> and contract separation. Could you please provide more details on
> how to better outline contracts?
>
> I would also like to understand the reason for placing the implementations
> > in
> > flink-core
>
>
> Main motivation as mentioned in rejected alternatives section [3] is to
> avoid introduction of yet another dependency that every plugin would have
> to
> declare alongside core. It made more sense to me to put new interfaces
> alongside `FileSystem / FSDataInputStream`.
>
> 2. Thread safety and cancellation.
>
> > locking every read affected the throughput
>
>
> That is a slightly surprising finding. Given how we use
> streams in flink, access to the reentrant lock would be 99% uncontended
> (except the cancellation case). And most reads would be 8KB reads for the
> download path.
> I would imagine that unless you issue single byte reads, latency for
> acquiring
> uncontended locks should be negligible relative to the HTTP latency on the
> SDK side (multiple orders of magnitude smaller). Do you have more details
> on a benchmark scenario in which it mattered?
>
> during a blocking SDK read
>
> IIUC, SDK reads internally are non-interruptible. If you are concerned
> about
> locking in `close` non-interruptibly vs interruptibly elsewhere, I don't
> think
> It makes a big difference in terms of a hang risk. When stream is closed
> gracefully, it is closed from the same thread, hence it can't be stuck
> on the SDK read. So this hang could only ever have an impact during task
> cancellation. Which is asynchronous and doesn't prevent job re-submission.
> It would also be limited to SDK timeout, which is typically well below
> default timeout for task cancellation before the forceful slot release.
>
> If full thread safety is required
>
>
> Thread safety is required for CSE.
> The issue stems from the fact that CSE requires verification of
> read stream, which requires reading until the GCM verification tag.
> Unless/until
> we implement chunked encryption, we would have to "read input stream fully"
> to ensure that we can trust the data. Given that we need to read the stream
> on
> close, it requires full thread safety on reads between main reader and
> closer. There are only 2 ways I see that could avoid locking on reads:
> a. We implement chunked encryption CSE (worse performance on sequential
> reads,
> but much better random access, which is normally not used in flink). With
> chunked encryption, we could "read ahead" and only serve already verified
> data and eliminate the need to "read on close".
> b. We clearly separate graceful close (read data is used) from abortion
> (read data is never used). Abortion case would not require thread safety.
> And
> graceful close has to be called from the same thread.
>
> 3. Stream ownership, CRT, and encryption
>
> > S3 may require
> > aborting a response rather than closing and draining it
>
>
> Draining is only implemented in the CSE extension itself, it doesn't happen
> for standard buffering implementation. The only difference is the potential
> cancellation delay mentioned above if we had a hanging/stuck read.
>
> RawAndWrappedInputStreams may therefore need more
> > explicit rules for ownership, close ordering, and abort behaviour.
>
>
> Could you please provide additional requirements regarding the abort
> behaviour you have mentioned?
>
> This might let each provider adapt its native stream
> > without introducing an intermediate buffer
>
>
> The default buffering was introduced to provide forward seek optimisation.
> If you do forward skips within the block that is already pulled by SDK,
> it might entirely ignore the fact that you have data locally and instead
> open a new range read with the cloud provider. Having a buffering extension
> allows faster in-memory buffer drain instead of extra HTTP requests.
>
> If your concern is that you would like to skip buffering, we could add
> another "no-op" implementation, that essentially gives you a RawAndRaw
> input stream. In combination with idempotent `close`, I don't see it
> causing any issues.
>
> Random access will require more than metadata
> > injection
>
>
> For the client side encryption, the largest question is whether we
> have to go for chunked implementation already or not. Flink FS hot paths
> don't involve "random access". Files are read from beginning to the end,
> hence the benefits of encrypting small "chunks" vs the whole file is
> marginal.
> Standard format for algorithms like AES-GCM would be
> [Initialization Vector(IV)][Ciphertext][Verification Tag(GCM)]. In case of
> chunked encryption, it would be [IV1][Chunk1][GCM1][IV2][Chunk2][GCM2] etc.
> Initial implementation I have suggested in FLIP-601 [2] goes for the whole
> file encryption approach as a simpler and slightly faster first
> implementation.
> However, if you want to go for chunked encryption, we don't need much
> extra.
> Metadata injection gives you "chunk size/IV length/GCM tag length", then
> remaining work is to implement a couple of position mappers that would
> track reopen stream not from 0 position but "beginning of the
> closest chunk". In addition, for chunked encryption, we would swap "drain"
> for "read ahead".
>
> I will share the header-injection
> > and range-read requirements once that discussion concludes.
>
>
> Sounds great, I suggest we move this requirements discussion in
> FLIP-601 thread.
>
> 4. Existing open questions
>
> only shared utilities and contract tests?
>
>
> This part, but as we have discussed, cloud specific parts might be
> too diverse around recoverable writers, so juice probably not worth the
> squeeze and I would be happy to keep this one out of the scope.
>
> For PathsCopyingFileSystem, I believe the existing interface already
> > provides the common capability
>
>
> In general, I agree. The reason I mention it is that we have provided
> this optimisation for AWS in many different ways, but we don't really
> have it supported for other clouds, and I was wondering about where
> the value of the implementation sits. If it is heavily re-using SDK
> capabilities with thin logic, then yes, doing it per-cloud makes sense.
> However, if the added value is in handling concurrency over simple SDK
> calls, that could be largely cloud agnostic and be easily re-used for
> other clouds too.
>
> Having said that, I would suggest considering this as a follow up
> improvement rather than the main FLIP scope.
>
> demonstrate a stable intersection
>
>
> I am heavy +1 to that, let's see how it looks for all 3 and if there is
> a lot of code duplication, let's extract it.
>
> If the intended interface is narrower
>
>
> Intended interface is certainly narrower than all ObjectStorage operations
> and is naturally limited to operations that FS implementation demands.
> I have example of such implementation for Azure, and we use S3AccessHelper
> [4]
> For Native S3, most likely, these 2 will already be very representative,
> but if we can analyse what would be needed for GCS too, it would be
> helpful.
>
>
> 5. Exception classification
>
> Is your proposal to add a special exception wrapper interface that would
> intercept all FS exceptions and re-wrap them with pre-classification?
> It makes sense to me to have per-cloud classifier, but I am not sure
> where would this interface live and when would we re-wrap the exceptions.
> Would it be some FS wrapper like ExceptionClassifingFileSystem? If so,
> I would suggest having it as a followup/child FLIP.
> Could you please suggest how this classification would integrate with FS?
>
> 6. Migration
>
> I assume deprecation will be
> > evaluated separately for each cloud plugin
>
>
> It makes sense to consider deprecation timelines "per plugin". This
> FLIP focuses on hadoop-less flink filesystems, and we are on track to
> provide native versions for all main clouds. Hence I called it out as our
> general intention. There are more Hadoop uses in the Flink codebase but
> we are not aiming to address all of them in one go.
>
> We should also document that in-progress RecoverableWriter
> > State may not be portable between Hadoop and native implementations
>
>
> This is an important callout, it might block migration of jobs with
> object storage sinks. Do you have a detailed writeup on the challenges
> there that I could use as a reference?
>
> Thank you again for taking time to review and analyse the proposal.
> I look forward to converging this discussion to unblock further work for
> GCS and Azure support.
>
> Kind regards,
> Aleksandr Iushmanov
>
> References:
> [1] FLIP-597: Hadoop-less Flink Filesystems —
> https://cwiki.apache.org/confluence/x/9gDuGQ
> [2] FLIP-601: Client-Side Encryption Extension —
> https://cwiki.apache.org/confluence/x/P4E_Gg
> [3]
>
> https://cwiki.apache.org/confluence/spaces/FLINK/pages/435028214/FLIP-597+Filesystems+Hadoop-less+Flink+filesystems#FLIP597%3A%5BFilesystems%5DHadooplessFlinkfilesystems-Separateflink-fs-commonmodule
> [4] S3AccessHelper:
>
> https://github.com/apache/flink/blob/master/flink-filesystems/flink-s3-fs-base/src/main/java/org/apache/flink/fs/s3/common/writer/S3AccessHelper.java
>
>
> On Sun, 12 Jul 2026 at 06:44, Samrat Deb <[email protected]> wrote:
>
> > Hi Aleksandr,
> >
> > I have been applying the proposed interfaces to the native GCS
> prototype.
> > I
> > encountered a few questions that I would like to understand better.
> Some
> > may
> > come from gaps in my prototype or my understanding of the intended
> > layering,
> > so I would appreciate corrections and guidance.
> >
> > 1. Scope and interface layering
> >
> > Would it be helpful for the FLIP to show three explicit layers?
> >
> > - Existing Flink contracts, such as FileSystem and RecoverableWriter
> > - Shared object-storage mechanics such as position tracking and ranged
> > reopening
> > - Provider-specific SDK adapters and capabilities
> >
> > My current impression is that ReadContext, StreamContext,
> > InputStreamOpener,
> > InputStreamExtension, and RawAndWrappedInputStreams overlap somewhat.
> > Could we
> > initially reduce this to a position-aware stream factory or handle, and
> > add
> > further context only when a second use case requires it?
> >
> > I would also like to understand the reason for placing the
> > implementations in
> > flink-core rather than a small flink-object-storage-fs-base module.
> There
> > may
> > be plugin classloading constraints that I have missed.
> >
> > 2. Thread safety and cancellation
> >
> > In the GCS prototype, locking every read affected the throughput. There
> > is also a
> > Cancellation concern: when a lock is held during a blocking SDK read, a
> > Concurrent close cannot acquire the lock to abort that operation.
> >
> > Would a narrower contract be sufficient: reads and writes remain
> thread-
> > Is confined, while close or abort is safe to invoke concurrently during
> > task
> > cancellation? If full thread safety is required, it would help to
> > document the
> > Flink caller that requires it.
> >
> > I will share the GCS benchmark results and a blocked-read cancellation
> > test so
> > that we can evaluate this with data rather than assumptions.
> >
> > 3. Stream ownership, CRT, and encryption
> >
> > I understand the need to support normal SDK streams, AWS CRT,
> buffering,
> > and
> > future client-side encryption through one model.
> >
> > The remaining question for me is lifecycle ownership. S3 may require
> > aborting
> > a response rather than closing and draining it, while GCS and Azure
> > expose
> > different channel semantics. RawAndWrappedInputStreams may therefore
> need
> > more
> > explicit rules for ownership, close ordering, and abort behaviour.
> >
> > Could the opener return an owned handle containing the readable stream
> > and its
> > abort/close operation? This might let each provider adapt its native
> > stream
> > without introducing an intermediate buffer. I will test this approach
> > with
> > both the GCS prototype and the native S3 implementation.
> >
> > For client-side encryption, I am still gathering requirements from the
> > security discussion. Random access will require more than metadata
> > injection
> > because logical offsets, encrypted chunk boundaries, integrity tags,
> and
> > physical object lengths must be considered. I will share the
> > header-injection
> > and range-read requirements once that discussion concludes.
> >
> > 4. Existing open questions
> >
> > For RecoverableWriter, my current understanding is that
> RecoverableWriter
> > itself is already the cloud-neutral abstraction, while the recovery
> > protocol
> > should remain provider-specific. S3 uses multipart state, whereas the
> > existing
> > GCS writer uses immutable component objects and composes so that older
> > checkpoint handles remain valid after writing continues. Did you
> envisage
> > a
> > new common implementation here, or only shared utilities and contract
> > tests?
> >
> > For PathsCopyingFileSystem, I believe the existing interface already
> > provides
> > the common capability. Each provider could implement it independently
> > when an
> > optimised transfer path is available.
> >
> > For ObjectStorageOperations, I understand the motivation, especially
> > given the
> > declining availability of reliable emulators. One possible incremental
> > approach would be to use provider-local SDK adapters initially, and
> share
> > FileSystem behaviour tests, and extract a common operations interface
> > once S3,
> > GCS and Azure demonstrate a stable intersection. If the intended
> > interface is
> > narrower than a complete object-store abstraction, documenting the
> exact
> > operations and semantics would help me evaluate it against GCS.
> >
> > 5. Exception classification
> >
> > I am interested in connecting this work with the
> exception-classification
> > mechanism. It would be useful to align common categories such as
> > authentication, not-found, precondition failure, throttling, timeout,
> and
> > cancellation while retaining the original SDK exception as the cause.
> >
> > Should this classification be part of FLIP-597, or should the
> filesystem
> > preserve provider exceptions and let the classification plugin handle
> > them in
> > a follow-up? I would be happy to prepare a comparison of the AWS, GCS,
> > and
> > Azure exception models.
> >
> > One final clarification concerns migration. I assume deprecation will
> be
> > evaluated separately for each cloud plugin rather than applying to
> > flink-fs-
> > hadoop-shaded generally, because HDFS and the remaining Hadoop-based
> > integrations
> > still require it. We should also document that in-progress
> > RecoverableWriter
> > State may not be portable between Hadoop and native implementations.
> >
> > I will follow up with the GCS prototype, performance notes, the S3/GCS
> > behaviour comparison, and the encryption requirements. I am also happy
> to
> > help
> > refine the interface documentation or add shared contract tests.
> >
> > These questions are intended to help us arrive at the smallest
> > abstraction
> > that preserves correctness and performance across providers. I support
> > the
> > Hadoop-less direction, and I am keen to learn from the Azure and S3
> > perspectives
> > so that we can help finalise FLIP-597.
> >
> > Bests,
> > Samrat
> >
> > On Thu, Jun 25, 2026 at 4:39 PM Aleksandr Iushmanov <[email protected]
> >
> > wrote:
> >
> > > Hi everyone,
> > >
> > > I'd like to start a discussion on FLIP-597: Hadoop-less Flink
> Filesystems
> > > [1].
> > >
> > > As FLIP-555 [2] established, Flink's Hadoop-based filesystem plugins
> > > carry significant maintenance burdens: transitive dependency conflicts,
> > > classpath issues, and CVE exposure. FLIP-555 addressed this for S3 by
> > > introducing a native SDK-based filesystem. This FLIP extends that
> effort
> > > as an umbrella for all cloud providers (Azure, GCS, and potentially
> > > others) with the end goal to deprecate Hadoop-based variants.
> > >
> > > The FLIP proposes common, cloud-agnostic I/O abstractions placed in
> > > flink-core that all SDK-native filesystem implementations share as
> > > a step towards Hadoop-less Flink deployments:
> > >
> > > - ObjectStorageInputStream / ObjectStorageOutputStream: thread-safe
> > > stream implementations.
> > > - InputStreamExtension / InputStreamOpener: composable read pipeline
> > > supporting buffering, decryption via decoration
> > > - WriteContext / ReadContext: metadata descriptors enabling features
> > > like client-side encryption without changing the FileSystem API
> > >
> > > There are a number of open questions for discussion:
> > > 1. Should a cloud-agnostic RecoverableWriter abstraction be part of
> > > this FLIP given that implementation across clouds may be too
> specific
> > > for common abstractions to be useful.
> > > 2. Should PathsCopyingFileSystem (bulk copy) be in scope? Transfer
> > managers
> > > for clouds vary. And currently we only support this implementation
> in
> > > AWS.
> > > 3. Should this FLIP include an ObjectStorageOperations interface
> > > (analogous to FLIP-555's S3AccessHelper) for a generalized
> > > ObjectStorageFileSystem base, or should we defer premature
> > > generalisation
> > > of testing setup? Main motivation for this would be the ability to
> > > verify
> > > common FS parts with testing doubles with reduced reliance on other
> > > libraries
> > > like MinIO [3] (archived), LocalStack [4] (archived), Azurite [5]
> (no
> > > SDK V2 support)
> > > that demonstrates a decline in support.
> > >
> > > Looking forward to your feedback.
> > >
> > > Best regards,
> > > Aleksandr Iushmanov
> > >
> > > [1] https://cwiki.apache.org/confluence/x/9gDuGQ
> > > [2] https://cwiki.apache.org/confluence/x/uYqmFw
> > > [3] https://github.com/minio/minio
> > > [4] https://github.com/localstack/localstack
> > > [5] https://github.com/Azure/Azurite
> > >
> >
>