Skip to content

Warm up local ssd of standby node#145

Merged
liunyl merged 2 commits intomainfrom
standby_warm
Nov 20, 2025
Merged

Warm up local ssd of standby node#145
liunyl merged 2 commits intomainfrom
standby_warm

Conversation

@liunyl
Copy link
Contributor

@liunyl liunyl commented Nov 19, 2025

Note

Adds periodic primary→standby SST file-cache sync with a new RPC and S3 downloader, plus config and factory hooks to support it.

  • DataStoreService:
    • Background workers: start/stop file-cache sync loop on primary and file-sync worker on standby; guard with is_file_sync_running_ and proper shutdown.
    • New RPC: SyncFileCache handles incoming cache sync; asynchronously prunes local .sst- files to a size limit and downloads missing ones (S3 when enabled).
    • Cache policy: DetermineFilesToKeep by file_number and GetSstFileCacheSizeLimit(); builds local db/ path per shard.
    • S3 integration: CreateS3Downloader() to fetch IDENTITY, CURRENT, and required SSTs.
  • RocksDB Cloud:
    • CollectCachedSstFiles() gathers live/local SST metadata; ExtractFileNumber() helper.
  • Protocol (Proto):
    • Adds FileInfo and SyncFileCacheRequest; new service RPC SyncFileCache.
  • Factories (API additions):
    • DataStoreFactory gains getters for GetStoragePath, S3 settings, and GetSstFileCacheSize; implemented in rocksdb_cloud_data_store_factory.h, rocksdb_data_store_factory.h, eloq_store_data_store_factory.h.
  • Config:
    • DataStoreServiceClusterManager adds Get/SetFileCacheSyncIntervalSec (default 30s).
  • Build:
    • S3 backend now builds s3_file_downloader.{h,cpp} and includes AWS SDK headers/libs.

Written by Cursor Bugbot for commit 61ff2ff. This will update automatically on new commits. Configure here.

Summary by CodeRabbit

  • New Features

    • File-cache synchronization across primary and standby nodes (new RPC) for standby warm-up.
    • Periodic file-sync worker that propagates cache state and enforces retention.
    • Automatic S3-backed retrieval of missing cache files with optional credentials and custom endpoints.
    • Exposed storage/S3 configuration and cache-size settings via factory getters and a configurable sync-interval (default 30s).
    • Deterministic SST retention policy and local cache collection logic.
  • Chores

    • Build updated to include the S3 downloader when the S3-backed backend is enabled.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 19, 2025

Caution

Review failed

The pull request is closed.

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds standby file-cache synchronization: new SyncFileCache RPC and request type, worker threads to collect and propagate SST file metadata, logic to determine and manage local SST cache, optional S3 downloader, and factory/config accessors to expose storage/S3 settings and cache-size configuration.

Changes

Cohort / File(s) Summary
Proto & RPC Interface
eloq_data_store_service/ds_request.proto
Added FileInfo and SyncFileCacheRequest messages and SyncFileCache RPC to DataStoreRpcService.
Core service & workers
eloq_data_store_service/data_store_service.h, .../data_store_service.cpp, .../internal_request.h
Added SyncFileCache RPC handler, SyncFileCacheLocalRequest, ProcessSyncFileCache, FileCacheSyncWorker, worker pools, mutex/CV and running state, creation helper for S3 downloader, cache-size helper and eviction logic.
Factory interface & implementations
eloq_data_store_service/data_store_factory.h, .../eloq_store_data_store_factory.h, .../rocksdb_data_store_factory.h, .../rocksdb_cloud_data_store_factory.h
Extended DataStoreFactory interface with storage/S3/accessor methods and SST cache-size getter; each factory implements appropriate overrides (cloud factory returns configured values; others return empty/zero as applicable).
RocksDB Cloud store
eloq_data_store_service/rocksdb_cloud_data_store.h, .../rocksdb_cloud_data_store.cpp
Added CollectCachedSstFiles to enumerate local cached SSTs intersecting live files and ExtractFileNumber helper to parse SST file numbers.
S3 downloader
eloq_data_store_service/s3_file_downloader.h, .../s3_file_downloader.cpp
Added EloqDS::S3FileDownloader class to parse s3:// URL, configure AWS S3 client (region, optional endpoint, credentials), and download objects to local files.
Configuration
eloq_data_store_service/data_store_service_config.h, .../data_store_service_config.cpp
Added file_cache_sync_interval_sec_ (default 30) with thread-safe setter/getter and copy/assignment propagation.
Build
eloq_data_store_service/CMakeLists.txt
Conditionally include s3_file_downloader.cpp into RESELOQ_SOURCES when WITH_DATA_STORE is ELOQDSS_ROCKSDB_CLOUD_S3.

Sequence Diagram(s)

sequenceDiagram
    participant Primary as Primary Node
    participant Standby as Standby Node
    participant S3 as S3 Storage

    rect `#F0F8FF`
    Note over Primary: Periodic trigger on primary
    Primary->>Primary: FileCacheSyncWorker collects cached SST info
    Primary->>Standby: SyncFileCache RPC (shard_id, FileInfo list)
    end

    rect `#F0FFF0`
    Note over Standby: Standby applies sync
    Standby->>Standby: ProcessSyncFileCache → DetermineFilesToKeep
    Standby->>Standby: Delete non-kept local SSTs
    end

    rect `#FFF5E6`
    Note over Standby,S3: Optional S3 retrieval for missing SSTs
    Standby->>Standby: CreateS3Downloader()
    Standby->>S3: DownloadFile(s) for missing SSTs
    S3-->>Standby: Stream object → write local file
    end

    rect `#F5F5FF`
    Note over Standby: Finish
    Standby->>Standby: Finish request, update state
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Areas needing extra attention:
    • S3FileDownloader: URL parsing, credential selection, endpoint and SSL handling, streaming I/O error paths.
    • FileCacheSyncWorker and file-sync worker lifecycle, shutdown ordering, and concurrency (mutex/CV, atomic flags).
    • DetermineFilesToKeep: correctness, determinism and edge cases for cache-size rounding.
    • SyncFileCache RPC path: shard validation, enqueueing, local request pooling and closure handling.
    • CollectCachedSstFiles / ExtractFileNumber: robustness for diverse filename formats and live-file intersections.

Possibly related PRs

Poem

🐰 I hop through files both near and far,

Standbys wake warm with cached delight,
Workers hum softly through the night,
S3 brings gifts when a file's away,
A rabbit cheers: sync done — hooray! 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Warm up local ssd of standby node' directly reflects the main objective: implementing periodic SST file cache synchronization and S3-backed downloads to warm up local SSDs on standby nodes.

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a963760 and 61ff2ff.

📒 Files selected for processing (5)
  • eloq_data_store_service/CMakeLists.txt (1 hunks)
  • eloq_data_store_service/data_store_service.cpp (8 hunks)
  • eloq_data_store_service/data_store_service.h (5 hunks)
  • eloq_data_store_service/rocksdb_cloud_data_store.cpp (4 hunks)
  • eloq_data_store_service/s3_file_downloader.cpp (1 hunks)

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

This PR is being reviewed by Cursor Bugbot

Details

You are on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle.

To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
eloq_data_store_service/CMakeLists.txt (1)

380-407: CMake syntax error confirmed—elseif blocks orphaned after endif

The actual file matches the review comment exactly. After the second endif() at line 397, the elseif statements at lines 398 and 403 have no parent if block to belong to. CMake requires elseif to be part of a continuous if/elseif/endif chain; they cannot appear after an endif(). This will cause CMake configuration to fail.

The suggested restructuring in the review comment is correct: consolidate into a single if/elseif/elseif/endif chain to restore valid syntax.

🧹 Nitpick comments (10)
eloq_data_store_service/ds_request.proto (1)

42-44: SyncFileCache RPC and messages look consistent; consider future extensibility

The new SyncFileCache RPC and the FileInfo / SyncFileCacheRequest messages are well-structured and consistent with the existing proto style (per‑shard, using uint32 shard_id, uint64 sizes, and a dedicated message for file metadata). Using google.protobuf.Empty is fine for a best‑effort warm‑up call; if you later need per‑file error reporting or metrics, you might want to introduce a small response message instead, but nothing blocks this PR.

Also applies to: 357-367

eloq_data_store_service/data_store_service_config.h (1)

326-335: File cache sync interval wiring is correct; consider documenting semantics of 0 / small values

Copy constructor, assignment operator, getter/setter, and the default file_cache_sync_interval_sec_{30} are wired consistently and follow the existing locking pattern. One improvement would be to document (and, if needed, clamp in the setter) how interval_sec == 0 or very small intervals are interpreted (disabled vs. tight loop) so future callers don’t accidentally misconfigure the sync worker.

Also applies to: 342-360, 457-465, 526-528

eloq_data_store_service/data_store_service_config.cpp (1)

803-815: Interval accessors are correctly synchronized; clarify configuration source

The new SetFileCacheSyncIntervalSec / GetFileCacheSyncIntervalSec methods use the same shared_mutex pattern as the rest of the cluster manager and cleanly expose the interval. Behaviorally, the interval is currently only set programmatically (not from the INI config); if that’s intentional, consider adding a brief comment near the field or setter noting expected configuration flow (e.g., “runtime‑tunable only”) so it’s clear why Load() doesn’t touch it.

eloq_data_store_service/rocksdb_cloud_data_store.cpp (1)

38-38: SST cache collection logic is sound; minor robustness and efficiency tweaks possible

The implementation of CollectCachedSstFiles correctly:

  • Guards DB access with db_mux_.
  • Short‑circuits when db_ is null.
  • Intersects live file metadata with locally present .sst- files.
  • Populates FileInfo (name, size, parsed file_number) and logs the count.

ExtractFileNumber is also defensive about malformed filenames (missing .sst-, all zeros, parse failures) and degrades gracefully by logging and returning 0.

Two small improvements you might consider:

  • Hoist the dynamic_cast<rocksdb::CloudFileSystem *> and null‑check once before the metadata loop (mirroring the CloudFileSystemImpl check earlier in the file) instead of per‑entry casting.
  • If directory traversal errors other than the initial directory_iterator construction are a concern in your environment, wrap the loop in a try/catch or use the error_code overloads on increment as well to avoid unexpected exceptions during cache collection.

These are optional; the current behavior is otherwise coherent with the rest of the RocksDB Cloud startup and shutdown paths.

Also applies to: 836-897, 899-929

eloq_data_store_service/s3_file_downloader.h (1)

24-28: Be aware of AWS SDK header dependency and static-analysis error; consider minimizing header includes.

Static analysis reports 'aws/core/Aws.h' file not found here. That may just mean the analysis environment lacks the AWS SDK, but to keep this header lighter and reduce coupling you can:

  • Remove <aws/core/Aws.h> from the header (it isn’t needed just to hold a std::shared_ptr<Aws::S3::S3Client>), and
  • Keep the heavier AWS includes in the .cpp where they’re actually used.

That will avoid spurious failures in tools that don’t have the SDK while still compiling fine in your real build where the SDK is available.

If your build actually depends on <aws/core/Aws.h> from other translation units, we should instead ensure the include paths are correctly configured rather than removing it.

Also applies to: 32-65

eloq_data_store_service/data_store_service.cpp (4)

295-346: SyncFileCache RPC routing is sensible, but error handling on worker-init failures could be clearer.

Positives:

  • Validates shard_id matches shard_id_ and only processes when shard_status_ == DSShardStatus::Closed, which correctly targets standby nodes.
  • Offloads heavy file I/O to file_sync_worker_ to avoid blocking the bthread.

Concerns / suggestions:

  • When file_sync_worker_ == nullptr or SubmitWork fails, the code logs an error, calls req->Free(), and intentionally lets the RPC “timeout”. This makes failures opaque to the caller and can look like a network issue instead of a server-side problem.
  • Since the response type is Empty, a common pattern is to set a controller error (cntl->SetFailed("...")) and then call done->Run() to return a clear failure to the primary.

If feasible, consider marking the RPC as failed instead of relying on timeouts so operators can distinguish logic/config errors from transport issues.


1348-1478: ProcessSyncFileCache: overall flow is good, but relies on specific path layout and S3 downloader creation.

The three-step flow is clear:

  1. Build a file_info_map and determine files_to_keep via GetSstFileCacheSizeLimit() and DetermineFilesToKeep().
  2. Iterate the local DB directory and delete .sst- files not in files_to_keep.
  3. Optionally (S3 build) instantiate an S3FileDownloader and download any missing kept files.

Points to verify / small concerns:

  • DB path construction: db_path = storage_path + "/ds_" + std::to_string(shard_id_) + "/db/"; assumes RocksDB Cloud layouts always follow this convention. If configs ever differ, this could log “Failed to list local directory” and skip sync. Please confirm this matches RocksDBCloudDataStore’s db_path_ construction.

  • Robustness to empty / misconfigured factory: You already guard against data_store_factory_ == nullptr and empty storage_path, which is good. Errors call req->Finish() so the RPC completes.

  • Deletion filter: Limiting deletions to filenames containing .sst- (and regular files) is a good safety check; it avoids touching WALs or other metadata.

  • S3 downloader reuse: CreateS3Downloader() is called per request. If SyncFileCache is frequent and the number of files is large, you might want to cache a downloader instance in DataStoreService (or in the factory) to avoid repeatedly constructing S3 clients, but this is an optimization, not a correctness bug.


2761-2855: FileCacheSyncWorker: primary-node sender loop is reasonable, but lacks backoff/timeout control on SyncFileCache RPCs.

Behavior looks correct:

  • Wakes up periodically using file_cache_sync_cv_.wait_for(...) and exits cleanly when file_cache_sync_running_ is set to false.
  • Only proceeds when shard_status_ == ReadWrite and data_store_ is non-null, so standby or uninitialized nodes don’t attempt sync.
  • Uses dynamic_cast<RocksDBCloudDataStore*> and skips if not cloud-backed, which keeps this feature limited to the right backend.
  • Collects cached SST files and fans out a SyncFileCacheRequest to each member except self.

Improvements to consider:

  • The SyncFileCache RPC uses a default brpc::Controller without explicit timeout or retry. If a standby is slow or down, you may want to set a bounded timeout and optionally a small retry policy to avoid long hangs on a single node.
  • If cluster_manager_.GetShard(shard_id_) ever changes membership frequently, you might want to snapshot the member list outside the loop or add minimal logging when the shard lookup fails (currently assumed to succeed).

2901-2917: GetSstFileCacheSizeLimit: double-check semantics of “0 means not applicable”.

This method:

  • Returns a default of 20ULL * 1024 * 1024 * 1024 (20 GiB) if the factory is null or GetSstFileCacheSize() returns 0.
  • Otherwise returns the configured value.

Note that this subtly changes the meaning of 0 from “not applicable” (per DataStoreFactory doc) to “use 20 GiB default”. That’s fine if it’s intentional, but if 0 was meant to disable cache-based warmup entirely, this will still keep up to 20 GiB of files.

If you do want “no cache warmup” when factories return 0, consider returning 0 here as well and making DetermineFilesToKeep treat cache_size_limit == 0 as “keep nothing”.

eloq_data_store_service/data_store_service.h (1)

775-819: File cache sync helpers and members are cohesive and match the new feature design.

  • FileCacheSyncWorker, ProcessSyncFileCache, and (S3-only) CreateS3Downloader form a clear internal API for the standby warm-up flow.
  • GetSstFileCacheSizeLimit and DetermineFilesToKeep encapsulate cache sizing logic, keeping the RPC handler simpler.
  • file_cache_sync_worker_, file_sync_worker_, and the associated mutex/CV/flag are appropriately scoped as private members.

Once <map> is included as noted above, this layout looks good.

If this feature grows, you might later consider isolating file-cache sync into a dedicated helper class to reduce the size of DataStoreService.

Also applies to: 813-820

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1c00129 and a82f4a9.

📒 Files selected for processing (15)
  • eloq_data_store_service/CMakeLists.txt (1 hunks)
  • eloq_data_store_service/data_store_factory.h (1 hunks)
  • eloq_data_store_service/data_store_service.cpp (7 hunks)
  • eloq_data_store_service/data_store_service.h (4 hunks)
  • eloq_data_store_service/data_store_service_config.cpp (2 hunks)
  • eloq_data_store_service/data_store_service_config.h (4 hunks)
  • eloq_data_store_service/ds_request.proto (2 hunks)
  • eloq_data_store_service/eloq_store_data_store_factory.h (1 hunks)
  • eloq_data_store_service/internal_request.h (1 hunks)
  • eloq_data_store_service/rocksdb_cloud_data_store.cpp (2 hunks)
  • eloq_data_store_service/rocksdb_cloud_data_store.h (2 hunks)
  • eloq_data_store_service/rocksdb_cloud_data_store_factory.h (1 hunks)
  • eloq_data_store_service/rocksdb_data_store_factory.h (1 hunks)
  • eloq_data_store_service/s3_file_downloader.cpp (1 hunks)
  • eloq_data_store_service/s3_file_downloader.h (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-22T09:48:06.044Z
Learnt from: githubzilla
Repo: eloqdata/store_handler PR: 65
File: eloq_data_store_service/rocksdb_cloud_data_store.cpp:392-397
Timestamp: 2025-09-22T09:48:06.044Z
Learning: In RocksDB Cloud data store, when FindMaxTermFromCloudManifestFiles returns false (no manifest files found), max_term = -1 is the intended behavior for new branches or first-time startup. The system correctly bootstraps from this state by creating new cookies with term 0.

Applied to files:

  • eloq_data_store_service/rocksdb_cloud_data_store.cpp
🧬 Code graph analysis (8)
eloq_data_store_service/internal_request.h (2)
eloq_data_store_service/object_pool.h (1)
  • Poolable (39-192)
eloq_data_store_service/data_store_service.cpp (18)
  • Clear (188-197)
  • Clear (188-188)
  • done_guard (445-445)
  • done_guard (455-455)
  • done_guard (483-483)
  • done_guard (492-492)
  • done_guard (516-516)
  • done_guard (528-528)
  • done_guard (560-560)
  • done_guard (571-571)
  • done_guard (604-604)
  • done_guard (618-618)
  • done_guard (655-655)
  • done_guard (666-666)
  • done_guard (706-706)
  • done_guard (718-718)
  • done_guard (750-750)
  • done_guard (761-761)
eloq_data_store_service/rocksdb_cloud_data_store.cpp (2)
eloq_data_store_service/data_store_service.cpp (1)
  • dir_ite (1389-1389)
rocksdb_handler.cpp (2)
  • dir_ite (3518-3518)
  • path (3530-3530)
eloq_data_store_service/s3_file_downloader.cpp (1)
eloq_data_store_service/s3_file_downloader.h (1)
  • S3FileDownloader (32-65)
eloq_data_store_service/s3_file_downloader.h (3)
eloq_data_store_service/data_store_factory.h (1)
  • EloqDS (29-92)
eloq_data_store_service/rocksdb_cloud_data_store_factory.h (1)
  • EloqDS (32-123)
eloq_data_store_service/s3_file_downloader.cpp (4)
  • S3FileDownloader (36-116)
  • S3FileDownloader (118-118)
  • DownloadFile (120-159)
  • DownloadFile (120-121)
eloq_data_store_service/data_store_service.cpp (2)
eloq_data_store_service/data_store_service_util.h (1)
  • shard_id_ (118-118)
eloq_data_store_service/rocksdb_cloud_data_store.cpp (1)
  • dir_ite (854-854)
eloq_data_store_service/rocksdb_cloud_data_store.h (1)
eloq_data_store_service/rocksdb_cloud_data_store.cpp (4)
  • CollectCachedSstFiles (836-897)
  • CollectCachedSstFiles (836-837)
  • ExtractFileNumber (899-929)
  • ExtractFileNumber (899-899)
eloq_data_store_service/data_store_service.h (7)
eloq_data_store_service/data_store_factory.h (1)
  • EloqDS (29-92)
eloq_data_store_service/data_store_service_config.h (1)
  • EloqDS (98-529)
eloq_data_store_service/rocksdb_cloud_data_store_factory.h (1)
  • EloqDS (32-123)
eloq_data_store_service/s3_file_downloader.h (1)
  • EloqDS (29-67)
eloq_data_store_service/thread_worker_pool.h (1)
  • EloqDS (34-58)
eloq_data_store_service/data_store_service.cpp (4)
  • SyncFileCache (1295-1346)
  • SyncFileCache (1295-1299)
  • DetermineFilesToKeep (2919-2964)
  • DetermineFilesToKeep (2919-2921)
eloq_data_store_service/internal_request.h (3)
  • remote (1611-1614)
  • string (1221-1224)
  • string (1402-1405)
eloq_data_store_service/data_store_service_config.h (1)
eloq_data_store_service/data_store_service_config.cpp (4)
  • SetFileCacheSyncIntervalSec (803-808)
  • SetFileCacheSyncIntervalSec (803-804)
  • GetFileCacheSyncIntervalSec (810-814)
  • GetFileCacheSyncIntervalSec (810-810)
🪛 Clang (14.0.6)
eloq_data_store_service/s3_file_downloader.h

[error] 24-24: 'aws/core/Aws.h' file not found

(clang-diagnostic-error)

🔇 Additional comments (10)
eloq_data_store_service/internal_request.h (1)

1591-1625: SyncFileCacheLocalRequest matches existing Poolable patterns

The new SyncFileCacheLocalRequest follows the same Poolable lifecycle as other *LocalRequest types: Clear() resets internal pointers, SetRequest() wires the request and closure, and Finish() uses brpc::ClosureGuard to run the closure exactly once. This is consistent and sufficient for an Empty response RPC.

eloq_data_store_service/rocksdb_cloud_data_store.h (1)

78-84: New SST cache APIs in the header look consistent with implementation

CollectCachedSstFiles(std::vector<::EloqDS::remote::FileInfo>&) and the private ExtractFileNumber helper are declared with signatures that match the implementation and fit naturally into RocksDBCloudDataStore’s responsibilities. As long as ds_request.pb.h is available via existing includes, no additional header changes are needed here.

Also applies to: 155-161

eloq_data_store_service/data_store_factory.h (1)

43-89: Credential handling already follows best practices; no changes required

Verification confirms that the interface extension is well-designed and the credential handling already aligns with the reviewer's concerns:

  1. Credentials are only fetched and immediately passed to S3FileDownloader (data_store_service.cpp, lines 2891–2897); they are never logged or stored beyond what the AWS SDK requires.
  2. The empty-string fallback to the default credential provider chain is already documented in comments (data_store_service.cpp line 2894, s3_file_downloader.h lines 40–41).
  3. Extensive search found no credential logging violations across the codebase; only informational logs about credential availability, never actual values.
  4. The RocksDBCloudDataStoreFactory implementation is properly contained, with credentials stored in a private cloud_config_ member and only exposed through the getter when S3 storage is active.

The design is sound and the stated conventions are already being followed. The original assessment does not match the actual implementation.

eloq_data_store_service/eloq_store_data_store_factory.h (1)

59-102: EloqStore factory getters look consistent with interface semantics.

GetStoragePath() safely handles an empty store_path vector, and the S3-related getters plus GetSstFileCacheSize() correctly return “not applicable” defaults for EloqStore.

eloq_data_store_service/rocksdb_data_store_factory.h (1)

74-112: Non-cloud RocksDB factory getters are straightforward and correct.

GetStoragePath() delegates to config_.storage_path_, and all cloud/S3-related getters return empty or 0, which matches “not applicable” semantics for this factory.

eloq_data_store_service/rocksdb_cloud_data_store_factory.h (1)

77-115: Cloud factory getters correctly expose S3 and cache configuration.

The mapping from cloud_config_ to GetS3BucketName/GetS3ObjectPath/GetS3Region/GetS3EndpointUrl and credentials, plus GetSstFileCacheSize(), is consistent with how DataStoreService::CreateS3Downloader and GetSstFileCacheSizeLimit consume them.

Please just double-check at config level that bucket_prefix_ + bucket_name_ yields a plain bucket name without embedded path segments, since CreateS3Downloader assumes the path portion is entirely in GetS3ObjectPath().

eloq_data_store_service/data_store_service.cpp (3)

224-261: Worker lifecycle wiring (file cache sync + file sync) looks correct.

  • Destructor cleanly flips file_cache_sync_running_ under the mutex, signals the condition variable, then calls Shutdown() on file_cache_sync_worker_, and also shuts down file_sync_worker_.
  • StartService initializes the workers only once (server_ guard) and passes the configured sync_interval_sec into FileCacheSyncWorker.

This avoids obvious races or leaks around the new background workers.

Also applies to: 235-251, 336-349


2858-2898: CreateS3Downloader: config wiring is correct; just ensure S3 config completeness.

This helper:

  • Pulls bucket, object path, region, endpoint, and credentials from DataStoreFactory.
  • Builds an S3 URL of the form s3://{bucket} or s3://{bucket}/{object_path}/, ensuring a trailing slash when needed.
  • Falls back to default AWS credentials behavior when keys are empty.

The only hard failure condition is bucket_name.empty(), which is reasonable.

Please confirm that for your RocksDB Cloud configurations GetS3Region() and GetS3EndpointUrl() are always set consistently with the actual S3/MinIO deployment; otherwise downloads may silently fail with endpoint/region mismatches.


2919-2964: DetermineFilesToKeep: selection strategy is clear and looks correct.

  • Builds a vector of (file_number, filename) pairs from the file_info_map.
  • Sorts ascending by file_number, so “older” SSTs (lower numbers) are considered first.
  • Iteratively adds files until the cumulative file_size would exceed cache_size_limit, then stops.

That yields a deterministic and size-bounded keep-set, and the logging of files_to_keep.size() and current_size helps with observability.

eloq_data_store_service/data_store_service.h (1)

459-470: SyncFileCache RPC declaration matches implementation and is well-documented.

The comment clearly describes purpose and parameters, and the signature aligns with the implementation in data_store_service.cpp (controller, SyncFileCacheRequest, Empty response, Closure *done).

}
}
}
}
Copy link

Choose a reason for hiding this comment

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

Bug: Race condition between file sync and DB open

The is_file_sync_running_ flag is set to true after creating the directory iterator but before the file deletion loop. This creates a race condition where ConnectAndStartDataStore could check the flag (line 383), see it as false, proceed to open the DB, while ProcessSyncFileCache is already iterating and deleting files. The flag needs to be set before any file system operations begin to properly synchronize with DB opening.

Fix in Cursor Fix in Web

size_t first_non_zero = base.find_first_not_of('0');
if (first_non_zero == std::string::npos)
{
LOG(ERROR) << "All zeros in file number from " << file_name;
Copy link

Choose a reason for hiding this comment

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

Bug: File number zero incorrectly treated as error

When extracting file numbers from SST filenames like 000000.sst-epoch, the function treats an all-zeros prefix as an error case and returns 0. However, file number 0 is a valid RocksDB SST file number. The function cannot distinguish between a legitimate file number 0 and an error condition since both return 0. This causes file number 0 SST files to be incorrectly prioritized or handled in cache management.

Fix in Cursor Fix in Web

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (2)
eloq_data_store_service/s3_file_downloader.cpp (1)

121-131: ** Guard against null s3_client_ before dereferencing.**

The constructor can return early (lines 47 or 73) without initializing s3_client_, leaving it null. The DownloadFile method then unconditionally dereferences s3_client_ at line 131, which will crash. Add a null check at the start of DownloadFile:

if (!s3_client_) {
    LOG(ERROR) << "S3 client not initialized; cannot download " << s3_file_name;
    return false;
}
eloq_data_store_service/data_store_service.cpp (1)

1332-1351: ** RPC closure called twice on error paths causes undefined behavior.**

After done_guard.release() at line 1330, the done closure ownership is transferred to req. On error paths:

  1. Lines 1332-1338: Calls req->Free() which invokes done, then creates a new ClosureGuard done_guard(done) which will call done again
  2. Lines 1345-1351: Same pattern

Calling the RPC closure twice causes undefined behavior. The fix is to remove the manual brpc::ClosureGuard done_guard(done) lines, since req->Free() already handles calling done:

     if (file_sync_worker_ == nullptr)
     {
         LOG(ERROR) << "FileSyncWorker not initialized, cannot process SyncFileCache";
         req->Free();
-        // Note: done_guard was released, so we need to manually call done
-        brpc::ClosureGuard done_guard(done);
         return;
     }
     
     bool res = file_sync_worker_->SubmitWork([this, req]() {
         ProcessSyncFileCache(req);
     });
     
     if (!res)
     {
         req->Free();
         LOG(ERROR) << "Failed to submit SyncFileCache work to file sync worker";
-        // Note: done_guard was released, so we need to manually call done
-        brpc::ClosureGuard done_guard(done);
     }
🧹 Nitpick comments (2)
eloq_data_store_service/data_store_service.cpp (2)

1455-1467: Check shard status before downloading IDENTITY and creating CURRENT.

IDENTITY and CURRENT file operations occur without checking if the shard status changed. If the shard is being opened concurrently, these operations should be skipped. Add a status check:

     uint32_t downloaded_count = 0;
+    if (shard_status_.load(std::memory_order_acquire) != DSShardStatus::Closed)
+    {
+        LOG(WARNING) << "Shard status is not closed, skipping file sync";
+        is_file_sync_running_.store(false, std::memory_order_release);
+        req->Finish();
+        return;
+    }
     if (!std::filesystem::exists(db_path + "IDENTITY"))
     {

2870-2893: Consider async RPC calls to avoid blocking on slow standby nodes.

The loop sends SyncFileCache RPCs synchronously to each standby node. If one node is slow or unreachable, it delays syncing to other nodes. For better resilience, consider:

// Option 1: Set shorter timeout
cntl.set_timeout_ms(1000);  // 1 second timeout

// Option 2: Use async RPCs with callbacks (more complex)
// Submit all RPCs, then wait for completion
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a82f4a9 and a963760.

📒 Files selected for processing (5)
  • eloq_data_store_service/CMakeLists.txt (1 hunks)
  • eloq_data_store_service/data_store_service.cpp (8 hunks)
  • eloq_data_store_service/data_store_service.h (5 hunks)
  • eloq_data_store_service/rocksdb_cloud_data_store.cpp (4 hunks)
  • eloq_data_store_service/s3_file_downloader.cpp (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • eloq_data_store_service/rocksdb_cloud_data_store.cpp
🧰 Additional context used
🧬 Code graph analysis (3)
eloq_data_store_service/data_store_service.h (8)
eloq_data_store_service/data_store_factory.h (1)
  • EloqDS (29-92)
eloq_data_store_service/eloq_store_data_store_factory.h (1)
  • EloqDS (30-109)
eloq_data_store_service/data_store_service_config.h (1)
  • EloqDS (98-529)
eloq_data_store_service/rocksdb_data_store_factory.h (1)
  • EloqDS (32-119)
eloq_data_store_service/rocksdb_cloud_data_store_factory.h (1)
  • EloqDS (32-123)
eloq_data_store_service/s3_file_downloader.h (1)
  • EloqDS (29-67)
eloq_data_store_service/thread_worker_pool.h (1)
  • EloqDS (34-58)
eloq_data_store_service/data_store_service.cpp (4)
  • SyncFileCache (1301-1352)
  • SyncFileCache (1301-1305)
  • DetermineFilesToKeep (2959-3004)
  • DetermineFilesToKeep (2959-2961)
eloq_data_store_service/data_store_service.cpp (2)
eloq_data_store_service/data_store_service_util.h (1)
  • shard_id_ (118-118)
eloq_data_store_service/rocksdb_cloud_data_store.cpp (1)
  • dir_ite (855-855)
eloq_data_store_service/s3_file_downloader.cpp (1)
eloq_data_store_service/s3_file_downloader.h (1)
  • S3FileDownloader (32-65)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Cursor Bugbot
🔇 Additional comments (7)
eloq_data_store_service/data_store_service.h (1)

29-30: LGTM! Missing <map> include now resolved.

The previously flagged missing <map> include has been added at line 30, resolving the compilation issue for DetermineFilesToKeep signature at lines 815-817.

eloq_data_store_service/CMakeLists.txt (1)

402-406: LGTM! S3 downloader conditionally included for RocksDB Cloud S3 backend.

The addition of s3_file_downloader.cpp to the build is correctly guarded by the ELOQDSS_ROCKSDB_CLOUD_S3 condition, ensuring it's only compiled when needed.

eloq_data_store_service/data_store_service.cpp (5)

2898-2939: LGTM! S3 downloader creation is well-structured.

The method correctly:

  • Validates factory and bucket configuration
  • Constructs proper S3 URLs with path handling
  • Falls back to default credential provider when credentials are empty

2959-3004: LGTM! File retention logic correctly prioritizes older SST files.

The method correctly sorts files by ascending file number and accumulates them until the cache size limit is reached. The implementation matches the documented behavior.

Note: If a single file exceeds cache_size_limit, no files will be kept. This is the documented behavior, but ensure this edge case is acceptable for your use case.


235-250: LGTM! Worker cleanup properly signals shutdown.

The destructor correctly:

  • Signals file cache sync worker to stop via condition variable
  • Waits for worker shutdown
  • Shuts down file sync worker

336-349: LGTM! File cache sync workers initialized correctly for RocksDB Cloud S3.

The initialization:

  • Correctly guarded by compile-time flag
  • Retrieves sync interval from cluster manager
  • Creates both primary and standby workers
  • Submits the periodic sync task

382-386: Ensure is_file_sync_running_ is cleared on all ProcessSyncFileCache exit paths.

This wait loop prevents concurrent DB operations during file sync. However, if is_file_sync_running_ is not properly cleared (see comment on line 1410), this will hang indefinitely. Ensure the flag is cleared on all return paths in ProcessSyncFileCache.

return;
}

is_file_sync_running_.store(true, std::memory_order_release);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Set is_file_sync_running_ to false on all early return paths.

The flag is set to true at line 1410 but only cleared at line 1517. Early returns at lines 1406, 1415, and 1450 leave the flag stuck at true, causing ConnectAndStartDataStore (lines 383-386) to wait indefinitely.

Move the flag setting to the start of the method and use RAII or ensure all return paths clear it:

+    is_file_sync_running_.store(true, std::memory_order_release);
+    
     const auto *request = req->GetRequest();
     
     // Get storage path from factory (even though DB is closed, path still exists)
     if (data_store_factory_ == nullptr)
     {
         LOG(ERROR) << "DataStoreFactory is null, cannot process file cache sync";
+        is_file_sync_running_.store(false, std::memory_order_release);
         req->Finish();
         return;
     }

Apply similar pattern to all early returns (lines 1372, 1406, 1450).

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In eloq_data_store_service/data_store_service.cpp around lines 1372–1517,
is_file_sync_running_ is set to true at line 1410 but not cleared on multiple
early-return paths (lines 1372, 1406, 1415, 1450), leaving the flag stuck and
causing ConnectAndStartDataStore (lines 383–386) to block; move the flag
initialization to the start of the method and use RAII (e.g., a scope
guard/unique object whose destructor sets is_file_sync_running_ to false) or
explicitly clear the flag on every early return so that all exit paths reset
is_file_sync_running_ to false.

Comment on lines +1413 to +1417
if (shard_status_.load(std::memory_order_acquire) != DSShardStatus::Closed)
{
LOG(WARNING) << "Shard status is not closed, skipping file sync";
break;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Return immediately when shard status changes during file sync.

The check at lines 1413-1417 breaks the deletion loop when shard status is no longer Closed, but execution continues to the download phase. If the shard is being opened, all file operations should stop immediately. Change break to early return:

         if (shard_status_.load(std::memory_order_acquire) != DSShardStatus::Closed)
         {
             LOG(WARNING) << "Shard status is not closed, skipping file sync";
-            break;
+            is_file_sync_running_.store(false, std::memory_order_release);
+            req->Finish();
+            return;
         }

Apply the same pattern at lines 1472-1476 in the download loop.

🤖 Prompt for AI Agents
In eloq_data_store_service/data_store_service.cpp around lines 1413-1417 and
similarly at 1472-1476, the loops check shard_status_ and currently use break
which exits only the inner loop and allows subsequent file operations (like
downloads) to continue; change those break statements to an immediate return
from the function so all file processing halts as soon as shard_status_ is no
longer Closed, ensuring no further deletions or downloads occur when the shard
is opening.

@liunyl liunyl merged commit 87ee52b into main Nov 20, 2025
1 of 2 checks passed
@liunyl liunyl deleted the standby_warm branch November 20, 2025 06:36
liunyl added a commit that referenced this pull request Nov 27, 2025
File-cache synchronization across primary and standby nodes to warm up local caches.
Automatic S3-backed retrieval of missing cache files with optional credentials and custom endpoint support.
Configurable cache-sync interval (default 30s) to tune refresh frequency.
SST cache size limit and deterministic retention policy to manage local cache contents.
Collection of local cached SST metadata for coordinated syncing.
@coderabbitai coderabbitai bot mentioned this pull request Dec 12, 2025
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