Skip to content

feat(store): implement pluggable cloud storage for HStore#3081

Open
vaijosh wants to merge 6 commits into
apache:masterfrom
vaijosh:Hstore+CloudStorage
Open

feat(store): implement pluggable cloud storage for HStore#3081
vaijosh wants to merge 6 commits into
apache:masterfrom
vaijosh:Hstore+CloudStorage

Conversation

@vaijosh

@vaijosh vaijosh commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Purpose

This PR introduces a cloud storage architecture for HugeGraph Store (HStore). In cloud-native environments, storage nodes are ephemeral, making reliance on local disk storage a single point of failure. This implementation decouples the storage layer from local disk dependencies, allowing SST files to be offloaded to durable, scalable cloud storage providers.

Key Changes

Pluggable Architecture

  • Created the CloudStorageProvider SPI interface to enable extensible storage backends.
  • Added CloudStorageProviderFactory for seamless provider discovery and lifecycle management.

S3 Provider Implementation

  • Introduced the hg-store-cloud-s3 module, leveraging AWS SDK v2 for production-ready S3/S3 compatible storage interaction.

Lifecycle & Event Integration

  • Integrated CloudStorageEventListener with RocksDBFactory. This hooks into critical SST lifecycle events (onTableFileCreated, onTableFileDeleted) to ensure synchronized state between local RocksDB and cloud storage.
  • Implemented startup hydration (onDBCreated) to backfill pre-existing files and read-miss on-demand hydration to ensure data availability.

Infrastructure & Testing

  • Added a comprehensive local development environment via docker/cloud-storage/ using MinIO.
  • Included an integration test script (test-graph-queries-and-sst.sh) to verify end-to-end data durability and query consistency.

Implementation Highlights

  • SPI Integration: Standardized service discovery via META-INF/services.
  • RocksDB Hooking: Registered a singleton RocksdbEventListener within RocksDBFactory to intercept file operations without modifying core RocksDB logic.
  • Read-Miss Protection: Added a guard window mechanism in onReadMiss to prevent redundant hydration requests during high-concurrency read scenarios.

Verifying These Changes

  • Unit Tests: Coverage for config parsing (CloudStorageConfigTest), factory registration, and event listener logic.
  • Integration Tests: Verified using the new docker-compose setup with MinIO.
  • Configuration: Verified application.yml bindings for credentials, bucket management, and sync intervals.

Impact

  • Dependencies: Added AWS SDK v2; updated LICENSE and NOTICE accordingly.
  • Configurations: Added cloud.storage namespace to application.yml (disabled by default).
  • Public API: No breaking changes to existing public-facing client APIs.

Documentation

  • Architecture and configuration details are documented in hugegraph-store/docs/pluggable-cloud-storage-architecture.md.

@vaijosh vaijosh marked this pull request as ready for review July 6, 2026 14:11
@dosubot dosubot Bot added size:XXL This PR changes 1000+ lines, ignoring generated files. feature New feature store Store module labels Jul 6, 2026
vaijosh added a commit to vaijosh/hugegraph that referenced this pull request Jul 8, 2026
apache#3081
-added multipart upload for the S3 storage provider
@vaijosh vaijosh force-pushed the Hstore+CloudStorage branch from 6a3f496 to 8b6d597 Compare July 8, 2026 10:29
apache#3081
-added multipart upload for the S3 storage provider
@vaijosh vaijosh force-pushed the Hstore+CloudStorage branch from 8b6d597 to 4275426 Compare July 8, 2026 10:59
@codecov

codecov Bot commented Jul 8, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 64.06250% with 23 lines in your changes missing coverage. Please review.
✅ Project coverage is 30.62%. Comparing base (03e6b8e) to head (d5f64df).
⚠️ Report is 4 commits behind head on master.

Files with missing lines Patch % Lines
...graph/store/cloud/CloudStorageProviderFactory.java 59.18% 17 Missing and 3 partials ⚠️
...store/cloud/CloudStorageNonRetryableException.java 0.00% 2 Missing ⚠️
...ache/hugegraph/store/cloud/CloudStorageConfig.java 91.66% 1 Missing ⚠️

❗ There is a different number of reports uploaded between BASE (03e6b8e) and HEAD (d5f64df). Click for more details.

HEAD has 2 uploads less than BASE
Flag BASE (03e6b8e) HEAD (d5f64df)
3 1
Additional details and impacted files
@@             Coverage Diff              @@
##             master    #3081      +/-   ##
============================================
- Coverage     36.80%   30.62%   -6.18%     
- Complexity      338      395      +57     
============================================
  Files           805      810       +5     
  Lines         68587    69157     +570     
  Branches       9029     9119      +90     
============================================
- Hits          25241    21180    -4061     
- Misses        40653    45555    +4902     
+ Partials       2693     2422     -271     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@VGalaxies VGalaxies left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review summary

  • Blocking: yes
  • Summary: The change cannot currently guarantee recoverable RocksDB state, may silently disable cloud storage, and contains S3 retry and deployment defects.
  • Evidence:
    • Static analysis of git diff origin/master...HEAD
    • shell syntax and POM XML checks passed
    • git diff --check reported whitespace-only errors

if (provider == null) {
return;
}
preHydrateDbFiles(provider, dbName, dbPath);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

High: Recreated databases can hydrate deleted data

hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/cloud/CloudStorageEventListener.java:144

Evidence

  • The listener hydrates the existing remote prefix during every open but implements neither onDBDeleteBegin nor onDBDeleted; directory deletion outside RocksDB generates no table-file deletion callbacks.

Impact

  • Recreating a graph or partition at the same path can download and ingest stale data from the deleted database.

Requested fix

  • Delete or tombstone the remote database generation during database destruction and make hydration honor that generation marker.

}
String remoteKey = toRelativeKey(filePath);
try {
provider.uploadFile(filePath, remoteKey);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

High: Cloud upload blocks the RocksDB event thread

hugegraph-store/hg-store-node/src/main/java/org/apache/hugegraph/store/node/cloud/CloudStorageEventListener.java:217

Evidence

  • RocksDBFactory invokes listeners inline, and this callback synchronously performs the entire S3 upload, including multipart retries and sleeps. RocksDB documents that these callbacks run on the triggering thread and should not perform extended work.

Impact

  • Slow or unavailable object storage can block flush/compaction threads and cause write stalls.

Requested fix

  • Hand uploads to a bounded executor and pin each SST using a checkpoint, hard link, or spool copy before returning from the callback.

.uploadId(uploadId).partNumber(partNumber)
.contentLength(partLen)
.build(),
RequestBody.fromInputStream(partStream, partLen));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

High: Multipart request bodies cannot be replayed for SDK retries

hugegraph-store/hg-store-cloud-s3/src/main/java/org/apache/hugegraph/store/cloud/s3/S3CloudStorageProvider.java:538

Evidence

  • RequestBody.fromInputStream() receives LimitedInputStream, which has no mark/reset support. AWS SDK retries require a replayable stream and can fail with IllegalStateException, which the surrounding retry loop does not catch.

Impact

  • A transient failure during a large upload can abort the multipart operation instead of retrying the affected part.

Requested fix

  • Supply a ContentStreamProvider that opens, seeks, and bounds a fresh file stream for each SDK attempt.

.build(),
RequestBody.fromInputStream(partStream, partLen));
return resp.eTag();
} catch (SdkClientException e) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

High: S3 service failures bypass the provider retry contract

hugegraph-store/hg-store-cloud-s3/src/main/java/org/apache/hugegraph/store/cloud/s3/S3CloudStorageProvider.java:540

Evidence

  • Provider operations catch only SdkClientException; S3 service responses use S3Exception/AwsServiceException, which are separate exception types. The outer part retry catches only IOException.

Impact

  • 429/5xx responses bypass part-level retries, while callers can receive unchecked exceptions despite the provider’s IOException contract.

Requested fix

  • Catch service exceptions, classify retryable versus permanent status codes, and translate them consistently into retryable or non-retryable cloud exceptions.

log.warn("S3 multipart abort failed: key={} uploadId={} reason={}",
fullKey, uploadId, abortEx.getMessage());
}
throw new IOException(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Medium: Multipart wrapping erases the direct-DLQ marker

hugegraph-store/hg-store-cloud-s3/src/main/java/org/apache/hugegraph/store/cloud/s3/S3CloudStorageProvider.java:506

Evidence

  • uploadOnePartWithRetry() throws CloudStorageNonRetryableException, but the enclosing catch wraps it as a plain IOException; the queue recognizes non-retryable failures only by type.

Impact

  • multipart-exhausted-direct-dlq=true still permits expensive whole-file retries.

Requested fix

  • Abort the multipart upload, then rethrow CloudStorageNonRetryableException unchanged.

https://central.sonatype.com/artifact/org.yaml/snakeyaml/1.28 -> Apache 2.0
https://central.sonatype.com/artifact/org.yaml/snakeyaml/2.2 -> Apache 2.0
https://central.sonatype.com/artifact/org.zeroturnaround/zt-zip/1.14 -> Apache 2.0
https://central.sonatype.com/artifact/software.amazon.awssdk/annotations/2.25.60 -> Apache 2.0

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Medium: Release metadata lists different dependency versions

install-dist/release-docs/LICENSE:761

Evidence

  • The provider pins AWS SDK 2.33.8, and known-dependencies.txt records 2.33.8 with Netty 4.1.126.Final; LICENSE adds AWS 2.25.60 and Netty 4.1.108.Final.

Impact

  • Binary release licensing metadata does not describe the actual bundled artifacts.

Requested fix

  • Regenerate the inventory from the packaged distribution and update LICENSE/NOTICE to the exact resolved versions and components.

wait_svc "server" 180
log "waiting for graph backend..."
wait_http "$GRAPH_API_BASE/graph/vertices" 60
log "✓ SUCCESS: Cloud storage infrastructure ready"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Medium: The advertised SST test never verifies cloud storage

docker/cloud-storage/scripts/test-graph-queries-and-sst.sh:378

Evidence

  • The script reports success after service and HTTP health checks; it never loads graph data, triggers or counts SST uploads, checks MinIO, tests recovery, or produces the reports promised by the README.

Impact

  • The validation passes even when the provider is disabled by the entrypoint namespace defect.

Requested fix

  • Fail unless graph writes succeed, SST objects appear in every expected bucket, and a recovery read succeeds from cloud-backed state.

apache#3081
- Improved review comments.
- Synced METADATA, CURRENT AND OPTIONS to cloud so that we can recover the DB after cluster crash scenarios.
- Improved the resiliancy and reduced the data loss possibilities.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature New feature size:XXL This PR changes 1000+ lines, ignoring generated files. store Store module

Projects

Status: In progress

Development

Successfully merging this pull request may close these issues.

2 participants