Skip to content

feat(java): add seekable ranged object streams - #170

Merged
asuiu merged 8 commits into
masterfrom
java-minio-seek
Jul 26, 2026
Merged

feat(java): add seekable ranged object streams#170
asuiu merged 8 commits into
masterfrom
java-minio-seek

Conversation

@asuiu

@asuiu asuiu commented Jul 22, 2026

Copy link
Copy Markdown
Member

Title

<type>: <short summary>

Ticket / Task

  • [Trello|Jira] link: <URL>

What & Why

"Builds X so that Y can Z" – two sentences max.

Scope

  • New feature
  • Bug fix
  • Refactor / tech-debt
  • Test / tooling only

Checklist (self-review)

See Section 5.

Risk / Impact

  • Latency critical path? ☐ Yes ☐ No
  • External API contract change? ☐ Yes ☐ No
  • Migration steps required? ☐ Yes ☐ No

Screenshots / Logs / Benchmarks

(only if relevant)

Author Self-Review Checklist

  • Single Ticket — PR addresses only one business requirement / Trello card.
  • Minimal Diff — no unrelated refactors, commented-out code, or debug prints.
  • Compiles & Tests Passpytest -q / mvn test clean locally.
  • No Dead Code — every new unit is called or covered by tests.
  • Naming & Clarity — identifiers are self-explanatory; no overloaded meanings.
  • Docs Updated — README, wiki, or docstrings updated where behaviour changed.
  • Performance Tagged — if touching hot path, attach micro-benchmarks or profiler diff.
  • Config & Secrets — no plaintext credentials; configs externalised.
  • Rollback Ready — change can be reverted with git revert without dependency hell.
  • Checklist Acknowledged — I would merge this myself if I were the reviewer.
  • LLM Review — PR has been reviewed by the approved LLM tool and suggestions addressed.
    • IDE Type Checker — PyCharm/Pylance recommendations were addressed where applicable.
    • Optional mypy — (Recommended) Ran python/scripts/run_mypy.bat locally with no critical errors

Resources:

@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 3b986b33-3d7d-4f0f-ab77-9b4d333265fe

📥 Commits

Reviewing files that changed from the base of the PR and between 331e737 and 5cd0b4d.

📒 Files selected for processing (7)
  • java/src/main/java/com/esamtrade/bucketbase/ObjectWriter.java
  • java/src/main/java/com/esamtrade/bucketbase/S3BucketSDKv1.java
  • java/src/main/java/com/esamtrade/bucketbase/StreamPipe.java
  • java/src/test/java/com/esamtrade/bucketbase/ObjectWriterCancellationTest.java
  • java/src/test/java/com/esamtrade/bucketbase/S3BucketSDKv1StreamingTest.java
  • java/src/test/java/com/esamtrade/bucketbase/StreamPipeTest.java
  • python/tests/test_pure_posix_path_stdlib_contract.py
🚧 Files skipped from review as they are similar to previous changes (4)
  • java/src/main/java/com/esamtrade/bucketbase/StreamPipe.java
  • python/tests/test_pure_posix_path_stdlib_contract.py
  • java/src/main/java/com/esamtrade/bucketbase/ObjectWriter.java
  • java/src/main/java/com/esamtrade/bucketbase/S3BucketSDKv1.java

📝 Walkthrough

Walkthrough

The Java project moves from Maven to Gradle with Java 25 and Maven Central publishing. Bucket APIs now support POSIX prefixes, seekable reads, streamed commits, validation, batched deletion, and lifecycle ownership. Memory and S3 backends implement the new contracts. Path handling, ranged I/O, Parquet access, functional tests, and publishing workflows are expanded.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ParquetReader
  participant BucketParquetDataSource
  participant SeekableInputStream
  participant S3Bucket
  participant S3
  ParquetReader->>BucketParquetDataSource: request footer or row-group range
  BucketParquetDataSource->>SeekableInputStream: seek and read
  SeekableInputStream->>S3Bucket: request object range
  S3Bucket->>S3: conditional GetObject
  S3-->>ParquetReader: return requested bytes
Loading

Possibly related PRs

Warning

Billing warning: we have not been able to collect payment for this subscription for more than 72 hours. Please update the payment method or pay any pending invoices in Billing to avoid service interruption.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (3)
java/src/main/java/com/esamtrade/bucketbase/IBucket.java (1)

159-167: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

copyPrefix buffers each object fully into RAM — stream it instead. Right above this you built copyObjectFrom, which streams source→dest without holding the whole object. But the parallel copy path does getObject(srcObj) (entire object into heap) then putObject. Fire that off on N threads with a few large objects and heap usage goes vertical — exactly the failure mode we don't want on a copy that's supposed to scale. Reuse the streaming path you already wrote.

♻️ Stream each object rather than materializing it
                 futures.add(executor.submit(() -> {
                     String name = dstPrefixStr + srcObj.toString().substring(srcPrefixLen);
                     if (name.startsWith("/")) {
                         name = name.substring(1);
                     }
-                    dstBucket.putObject(PurePosixPath.from(name), getObject(srcObj));
+                    dstBucket.copyObjectFrom(this, srcObj, PurePosixPath.from(name));
                     return null;
                 }));
As per coding guidelines: "Performance & Big-O Awareness: watch out for ... wasteful object creation in hot paths."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@java/src/main/java/com/esamtrade/bucketbase/IBucket.java` around lines 159 -
167, Update the parallel copy task in copyPrefix to reuse the existing
copyObjectFrom streaming path instead of calling getObject(srcObj) and passing a
fully materialized object to putObject. Preserve the current destination-name
construction and parallel executor behavior while ensuring each source object
streams directly to its destination.

Source: Coding guidelines

java/src/main/java/com/esamtrade/bucketbase/StreamPipe.java (1)

23-37: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Hand-rolled monitor works, but consider a BlockingQueue-based design.

The class documents choosing manual wait/notifyAll over java.util.concurrent primitives specifically to support abort() waking both sides with a failure. That's a defensible reason, but per the project guideline to prefer high-level concurrency abstractions over synchronized blocks where they suffice, an ArrayBlockingQueue<byte[]> combined with the existing AtomicReference<Throwable> pattern (already used for uploadFailure in ObjectWriter) plus Thread.interrupt() for wake-on-abort could shrink this to much less custom monitor code. Not blocking — the abort semantics genuinely complicate a drop-in swap — just worth a look if this class needs future maintenance.

As per coding guidelines, "Concurrency & performance: use thread-safe collections and immutables; avoid synchronized blocks where high-level abstractions suffice."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@java/src/main/java/com/esamtrade/bucketbase/StreamPipe.java` around lines 23
- 37, Consider refactoring StreamPipe’s custom lock, queue, and wait/notify
coordination to use a bounded ArrayBlockingQueue<byte[]> with an
AtomicReference<Throwable> for failure state and thread interruption to wake
blocked readers and writers during abort(). Preserve the existing back-pressure,
EOF-after-drain, and abort failure semantics; if the abstraction cannot support
these semantics cleanly, leave the monitor implementation unchanged.

Source: Coding guidelines

java/src/main/java/com/esamtrade/bucketbase/MemoryBucket.java (1)

72-92: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Nice shallow-listing logic, but the HashSet makes ordering a roll of the dice.

Physics doesn't care about hash bucket order, and neither should your API consumers. prefixesSet is a HashSet, so shallowListObjects returns prefixes in an unspecified order, while S3BucketSDKv1.shallowListObjects preserves server-returned order via a List. Not failing today because the tests only check single-element/size expectations, but it's a landmine for anyone diffing behavior across backends.

♻️ Deterministic ordering fix
-        Set<PurePosixPrefix> prefixesSet = new HashSet<>();
+        Set<PurePosixPrefix> prefixesSet = new LinkedHashSet<>();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@java/src/main/java/com/esamtrade/bucketbase/MemoryBucket.java` around lines
72 - 92, Update shallowListObjects so prefixes retain deterministic discovery
order instead of using HashSet iteration order. Replace prefixesSet with an
insertion-ordered collection while preserving duplicate elimination, and
continue constructing the ShallowListing with the resulting ordered prefixes
list.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@java/gradle/wrapper/gradle-wrapper.properties`:
- Line 3: Add the Gradle Wrapper property distributionSha256Sum alongside
distributionUrl in gradle-wrapper.properties, using the official SHA-256
checksum for gradle-9.6.1-bin.zip:
9c0f7faeeb306cb14e4279a3e084ca6b596894089a0638e68a07c945a32c9e14.

In `@java/src/main/java/com/esamtrade/bucketbase/ObjectWriter.java`:
- Around line 86-132: Update awaitUploader() to cancel the uploader when
join(timeoutMillis) expires, interrupting or otherwise stopping the upload on
timeout before throwing its IOException. Preserve the existing timeout and
interruption errors, and ensure commit() and close() use this best-effort
cancellation so the uploader cannot continue normally after either operation has
failed or aborted.

In `@java/src/main/java/com/esamtrade/bucketbase/S3BucketSDKv1.java`:
- Around line 92-97: Update putObjectStream in S3BucketSDKv1 to set
ObjectMetadata content length before calling s3Client.putObject whenever the
stream size is known. For unknown-length streams, stage through a temporary file
or use multipart upload support so the AWS SDK does not buffer the entire stream
in memory.

In `@java/src/main/java/com/esamtrade/bucketbase/StreamPipe.java`:
- Around line 129-153: Update PipeOutputStream.write(byte[] src, int off, int
len) to split large writes into bounded chunks before enqueueing, so each chunk
is limited by the stream’s configured capacity and back-pressure accounts for
all buffered bytes rather than the caller’s write-call count. Preserve range
validation, zero-length behavior, buffer copying, closure/failure checks, and
synchronization while ensuring a large write cannot bypass the queue limit.

---

Nitpick comments:
In `@java/src/main/java/com/esamtrade/bucketbase/IBucket.java`:
- Around line 159-167: Update the parallel copy task in copyPrefix to reuse the
existing copyObjectFrom streaming path instead of calling getObject(srcObj) and
passing a fully materialized object to putObject. Preserve the current
destination-name construction and parallel executor behavior while ensuring each
source object streams directly to its destination.

In `@java/src/main/java/com/esamtrade/bucketbase/MemoryBucket.java`:
- Around line 72-92: Update shallowListObjects so prefixes retain deterministic
discovery order instead of using HashSet iteration order. Replace prefixesSet
with an insertion-ordered collection while preserving duplicate elimination, and
continue constructing the ShallowListing with the resulting ordered prefixes
list.

In `@java/src/main/java/com/esamtrade/bucketbase/StreamPipe.java`:
- Around line 23-37: Consider refactoring StreamPipe’s custom lock, queue, and
wait/notify coordination to use a bounded ArrayBlockingQueue<byte[]> with an
AtomicReference<Throwable> for failure state and thread interruption to wake
blocked readers and writers during abort(). Preserve the existing back-pressure,
EOF-after-drain, and abort failure semantics; if the abstraction cannot support
these semantics cleanly, leave the monitor implementation unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 26a1c5ee-8eea-484a-86ad-6c0ffe6f350a

📥 Commits

Reviewing files that changed from the base of the PR and between d1226cb and f0a8720.

⛔ Files ignored due to path filters (1)
  • java/gradle/wrapper/gradle-wrapper.jar is excluded by !**/*.jar
📒 Files selected for processing (43)
  • .gitignore
  • java/PUBLISHING.md
  • java/build.gradle
  • java/gradle/wrapper/gradle-wrapper.properties
  • java/gradlew
  • java/gradlew.bat
  • java/pom.xml
  • java/settings.gradle
  • java/src/main/java/README.md
  • java/src/main/java/com/esamtrade/bucketbase/AbstractAppendOnlySynchronizedBucket.java
  • java/src/main/java/com/esamtrade/bucketbase/BaseBucket.java
  • java/src/main/java/com/esamtrade/bucketbase/DeleteError.java
  • java/src/main/java/com/esamtrade/bucketbase/IBucket.java
  • java/src/main/java/com/esamtrade/bucketbase/MemoryBucket.java
  • java/src/main/java/com/esamtrade/bucketbase/ObjectStream.java
  • java/src/main/java/com/esamtrade/bucketbase/ObjectWriter.java
  • java/src/main/java/com/esamtrade/bucketbase/PurePosixPath.java
  • java/src/main/java/com/esamtrade/bucketbase/PurePosixPrefix.java
  • java/src/main/java/com/esamtrade/bucketbase/RangeSeekableInputStream.java
  • java/src/main/java/com/esamtrade/bucketbase/S3Bucket.java
  • java/src/main/java/com/esamtrade/bucketbase/S3BucketSDKv1.java
  • java/src/main/java/com/esamtrade/bucketbase/S3Names.java
  • java/src/main/java/com/esamtrade/bucketbase/SeekableInputStream.java
  • java/src/main/java/com/esamtrade/bucketbase/ShallowListing.java
  • java/src/main/java/com/esamtrade/bucketbase/StreamPipe.java
  • java/src/test/java/com/esamtrade/bucketbase/BucketHardeningTest.java
  • java/src/test/java/com/esamtrade/bucketbase/BucketParquetDataSource.java
  • java/src/test/java/com/esamtrade/bucketbase/IBucketTester.java
  • java/src/test/java/com/esamtrade/bucketbase/MemoryBucketTest.java
  • java/src/test/java/com/esamtrade/bucketbase/MinioParquetFunctionalTest.java
  • java/src/test/java/com/esamtrade/bucketbase/ParquetReadTest.java
  • java/src/test/java/com/esamtrade/bucketbase/ParquetTestSupport.java
  • java/src/test/java/com/esamtrade/bucketbase/ParquetWriteTest.java
  • java/src/test/java/com/esamtrade/bucketbase/PurePosixPathTest.java
  • java/src/test/java/com/esamtrade/bucketbase/PurePosixPrefixTest.java
  • java/src/test/java/com/esamtrade/bucketbase/RangeSeekableInputStreamTest.java
  • java/src/test/java/com/esamtrade/bucketbase/S3BucketRangeTest.java
  • java/src/test/java/com/esamtrade/bucketbase/S3BucketSDKv1RangeTest.java
  • java/src/test/java/com/esamtrade/bucketbase/S3BucketSDKv1Test.java
  • java/src/test/java/com/esamtrade/bucketbase/S3BucketTest.java
  • java/src/test/java/com/esamtrade/bucketbase/S3RemoveAndCloseTest.java
  • java/src/test/java/com/esamtrade/bucketbase/TestConfig.java
  • python/tests/test_pure_posix_path_stdlib_contract.py
💤 Files with no reviewable changes (4)
  • java/src/main/java/com/esamtrade/bucketbase/ObjectStream.java
  • java/src/main/java/com/esamtrade/bucketbase/BaseBucket.java
  • java/src/main/java/README.md
  • java/pom.xml

@@ -0,0 +1,9 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

https://services.gradle.org/distributions/gradle-9.6.1-bin.zip checksum sha256

💡 Result:

The SHA-256 checksum for the file gradle-9.6.1-bin.zip is 9c0f7faeeb306cb14e4279a3e084ca6b596894089a0638e68a07c945a32c9e14 [1][2][3]. This value is officially published by the Gradle team on their release checksums reference page and the distribution release assets [1][2].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "Wrapper properties:"
if [ -f java/gradle/wrapper/gradle-wrapper.properties ]; then
  cat -n java/gradle/wrapper/gradle-wrapper.properties
else
  echo "missing java/gradle/wrapper/gradle-wrapper.properties"
fi

Repository: eSAMTrade/bucketbase

Length of output: 522


Pin the Gradle distribution checksum, no false positives.

validateDistributionUrl=true only checks the URL; add distributionSha256Sum so Wrapper validation can reject tampered bytes during download. The official checksum for gradle-9.6.1-bin.zip is 9c0f7faeeb306cb14e4279a3e084ca6b596894089a0638e68a07c945a32c9e14.

Proposed fix
 distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip
+distributionSha256Sum=9c0f7faeeb306cb14e4279a3e084ca6b596894089a0638e68a07c945a32c9e14
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip
distributionSha256Sum=9c0f7faeeb306cb14e4279a3e084ca6b596894089a0638e68a07c945a32c9e14
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@java/gradle/wrapper/gradle-wrapper.properties` at line 3, Add the Gradle
Wrapper property distributionSha256Sum alongside distributionUrl in
gradle-wrapper.properties, using the official SHA-256 checksum for
gradle-9.6.1-bin.zip:
9c0f7faeeb306cb14e4279a3e084ca6b596894089a0638e68a07c945a32c9e14.

Comment thread java/src/main/java/com/esamtrade/bucketbase/ObjectWriter.java
Comment thread java/src/main/java/com/esamtrade/bucketbase/S3BucketSDKv1.java
Comment thread java/src/main/java/com/esamtrade/bucketbase/StreamPipe.java
@asuiu
asuiu merged commit e2a4094 into master Jul 26, 2026
22 checks passed
@asuiu
asuiu deleted the java-minio-seek branch July 26, 2026 01:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant