Skip to content

Conversation

@YiXR
Copy link
Collaborator

@YiXR YiXR commented Dec 15, 2025

Description

This PR introduce the abstract base classes for cache tier management.

CacheTier

mooncake-store/include/tiered_cache/cache_tier.h

Responsibilities

This is an abstract base class that defines the unified interface a single cache tier (e.g., DRAM, SSD) must implement. It is responsible for managing the data lifecycle on a specific storage medium.

Core Interface:

  • Allocate: Finds free space of size bytes. Does NOT copy data.
  • Free: Releases space at offset. Used when writes fail or explicitly freeing anonymous blocks.

DataCopier

mooncake-store/include/tiered_cache/data_copier.h

Responsibilities:

Responsible for efficiently copying data between different memory types (MemoryType). This is a core utility class that decouples the data movement logic.

Core Functionality:

  • Built via DataCopierBuilder, allowing for the registration of direct copy functions between different memory types (e.g., DRAM -> VRAM).
  • Provides a unified Copy interface. When a direct copy path is unavailable, it automatically employs a fallback mechanism using DRAM as an intermediate buffer (e.g., VRAM -> DRAM -> SSD) (when implementing a new type, compilation requires copy functions between the new type and DRAM). This greatly simplifies the integration of new storage media.
    • Interaction: Implementations of TieredBackend and CacheTier hold and use a DataCopier instance to execute all data copy operations, whether it's writing new data or moving data between tiers.

TieredBackend

mooncake-store/include/tiered_cache/tiered_backend.h

Responsibilities:

Serves as the Data Plane for the tiered cache. It does not contain any complex caching policies (e.g., LRU) and is solely responsible for faithfully executing data operation instructions.

Core Data Structures

DataSource

A generic descriptor for source data.

struct DataSource {
    const void* ptr;  // Pointer to source buffer
    size_t size;      // Data size
    MemoryType type;  // Source memory type (DRAM, NVME, etc.)
};

TieredLocation

Describes the physical location of a data segment.

struct TieredLocation {
    uint64_t tier_id;
    struct DataSource data;
};

AllocationHandle

/**
 * @struct AllocationEntry
 * @brief The internal state of an allocation.
 * acts as the "Control Block" for the resource.
 * When the last shared_ptr pointing to this entry dies, the destructor
 * triggers the physical release of the resource via the Backend.
 */
struct AllocationEntry {
    TieredBackend* backend;
    TieredLocation loc;

    AllocationEntry(TieredBackend* b, TieredLocation l) : backend(b), loc(l) {}

    // Destructor: Automatically releases the resource if valid
    ~AllocationEntry();
};

/**
 * @typedef AllocationHandle
 * @brief A reference-counted handle to a storage resource.
 */
using AllocationHandle = std::shared_ptr<AllocationEntry>;

Decomposed APIs

The traditional atomic Put operation is broken down into three granular steps:

  • Allocate
    Reserves storage space locally without writing data.
AllocationHandle Allocate(
    size_t size, std::optional<uint64_t> preferred_tier);

Usage: Called by the AllocationStrategy to find free space.
Returns: true if space is reserved; false otherwise.

  • Write (local operation)
    Writes data to a specific, reserved location.
bool Write(AllocationHandle handle, const DataSource& source);

Usage: Performs local write operation. If need remote write, please tranfer data into AllocationHandle by using TE.
Returns: true on success. If failed, the caller must free the allocated space.

  • Commit
    Registers the key in the local metadata index.
bool Commit(const std::string& key, AllocationHandle handle);

Usage: Called after data is successfully written.
Behavior: It binds the key to the offset internally. This operation is designed to be infallible (must succeed). If an internal error occurs (e.g., OOM), it raises a fatal error.

  • Delete
    Delete the key in the local metadata index.
bool Delete(const std::string& key,
                           std::optional<uint64_t> tier_id)

If tier_id is specified, removes only the replica on that tier. If nullopt, removes ALL replicas for this key.

Allocation Workflows

This decoupled design supports two distinct allocation patterns required by the new architecture.

Each backend can only use one allocation strategy after mooncake store start up. If the backend wants to use Master-Centric Allocation, it must realize all the func to register it's info and allocator to Master.

Pattern A: Client-Centric Allocation (Local Decision)

In this mode, the Client decides where to place data (e.g., "Try DRAM first, then SSD") and later informs the Master.

Workflow

  • Decision
    The ClientCentricStrategy calls Allocate(size, preferred_tier=xxx).
    TieredBackend checks local capacity and returns a AllocationHandle.

  • Execution
    The strategy calls Write(data, AllocationHandle), this is a local write operation.
    Please use TE to transfer data if you want to do remote write.

  • Local Commit
    The strategy calls Commit(key, AllocationHandle).
    The key is now visible to local Get() requests.

  • Global Sync
    The Client sends an asynchronous RPC RegisterAllocation to the Master.
    Master updates its directory: "Key X is located at Client Y, Tier Z".

Pattern B: Master-Centric Allocation (Global Decision)

In this mode, the Master manages all resources globally (Client should still call MountSegment() to register all the resources to Master) and tells the Client exactly where to write.
Client doesn't need to save metadata for this backend anyway.

Workflow:

  • Request
    The Client sends an RPC PutStart to the Master.

  • Decision
    Master checks its global resource view and returns {tier_id: 1, offset: 0x2000}.

  • Execution
    Data can be write by TE into tier directly.

Data Migration & Consistency

Migrates a key between tiers using a "Make-Before-Break" protocol to ensure strong consistency.

bool CopyData(const std::string& key, const DataSource& source,
                             uint64_t dest_tier_id,
                             MetadataSyncCallback sync_cb);

If DataSource is Master-Centric Allocation, Master must 'tell' Client where the data is.

Workflow:

  • Allocate & Write
    Data is copied to the destination tier.

  • Sync Callback
    sync_cb is invoked.

  • Action
    The Client sends an RPC to the Master: "Update Key location to Tier New".

  • Safety
    If this RPC fails, CopyData returns false and rolls back the new copy. The old data remains valid.

  • Commit
    Local metadata is updated to point to the new tier.

  • Delete
    Only after the Master is updated and local commit succeeds, the old replica need to be deleted.
    If DataSource is Master-Centric Allocation, Master will delete the key by itself.
    If DataSource is Client-Centric Allocation, Client must call Delete(key) after CopyData.

sequenceDiagram
    participant Client
    participant TieredBackend
    participant CacheTier
    participant DataCopier
    participant MemoryPool

    Client->>TieredBackend: Allocate(size, preferred_tier)
    activate TieredBackend
    TieredBackend->>TieredBackend: GetSortedTiers()
    TieredBackend->>CacheTier: Allocate(size)
    activate CacheTier
    CacheTier->>MemoryPool: allocate
    MemoryPool-->>CacheTier: ptr
    CacheTier-->>TieredBackend: AllocationHandle(tier_id, DataSource)
    deactivate CacheTier
    TieredBackend-->>Client: AllocationHandle
    deactivate TieredBackend

    Client->>TieredBackend: Write(source_data, handle)
    activate TieredBackend
    TieredBackend->>DataCopier: Copy(source, dest_from_handle)
    activate DataCopier
    DataCopier->>DataCopier: FindCopier(src_type, dest_type)
    alt Direct Path Exists
        DataCopier->>DataCopier: Execute direct copy
    else No Direct Path
        note over DataCopier: Both non-DRAM?
        DataCopier->>TieredBackend: Allocate temp DRAM buffer
        DataCopier->>DataCopier: Copy src → DRAM
        DataCopier->>DataCopier: Copy DRAM → dest
    end
    DataCopier-->>TieredBackend: success/failure
    deactivate DataCopier
    TieredBackend-->>Client: bool
    deactivate TieredBackend

    Client->>TieredBackend: Commit(key, handle)
    activate TieredBackend
    TieredBackend->>TieredBackend: Update metadata_index<br/>(per-key replicas)
    TieredBackend-->>Client: bool
    deactivate TieredBackend
Loading
sequenceDiagram
    participant Client
    participant TieredBackend
    participant MetadataIndex
    participant CacheTier1
    participant CacheTier2

    Client->>TieredBackend: Get(key, optional_tier_id)
    activate TieredBackend
    TieredBackend->>MetadataIndex: Lookup replicas for key
    activate MetadataIndex
    MetadataIndex-->>TieredBackend: replica list sorted by priority
    deactivate MetadataIndex
    
    alt tier_id specified
        TieredBackend->>CacheTier1: Retrieve from specific tier
    else best available
        TieredBackend->>CacheTier1: Retrieve from highest-priority tier
    end
    activate CacheTier1
    CacheTier1-->>TieredBackend: AllocationHandle
    deactivate CacheTier1
    TieredBackend-->>Client: AllocationHandle
    deactivate TieredBackend

    Client->>TieredBackend: Delete(key, optional_tier_id)
    activate TieredBackend
    TieredBackend->>MetadataIndex: Remove replica from key
    activate MetadataIndex
    MetadataIndex->>CacheTier2: Free allocation
    activate CacheTier2
    CacheTier2-->>MetadataIndex: success
    deactivate CacheTier2
    MetadataIndex-->>TieredBackend: bool
    deactivate MetadataIndex
    TieredBackend-->>Client: bool
    deactivate TieredBackend
Loading

Type of Change

  • Types
    • Bug fix
    • New feature
      • Transfer Engine
      • Mooncake Store
      • Mooncake EP
      • Integration
      • P2P Store
      • Python Wheel
    • Breaking change
    • CI/CD
    • Documentation update
    • Other
Cohort / File(s) Summary
Tier Abstraction & Metadatamooncake-store/include/tiered_cache/cache_tier.h, mooncake-store/include/tiered_cache/tiered_backend.h Defines MemoryType enum (DRAM, NVME, UNKNOWN), DataSource struct, abstract CacheTier interface with lifecycle (Init, Allocate, Free) and introspection methods; introduces tiered storage data structures (TieredLocation, TierView, AllocationEntry, MetadataEntry) and TieredBackend orchestrator for multi-tier allocation, replication, metadata indexing, and tier management.
Copy Function Registrymooncake-store/include/tiered_cache/copier_registry.h, mooncake-store/src/tiered_cache/copier_registry.cpp Implements singleton CopierRegistry for registering memory-type and direct-path copy operations; provides CopierRegistrar helper for static-initialization-time registration of copiers.
Data Copier with Fallbackmooncake-store/include/tiered_cache/data_copier.h, mooncake-store/src/tiered_cache/data_copier.cpp Implements DataCopierBuilder (populates copy matrix from global registry, validates DRAM path availability) and DataCopier (executes direct or two-step DRAM-based fallback copy between memory types).
Backend Implementationmooncake-store/src/tiered_cache/tiered_backend.cpp Implements TieredBackend lifecycle, allocation with tier selection, write/copy/commit operations with per-key metadata and replica tracking, concurrent Get/Delete with multi-layer locking, and tier introspection.

How Has This Been Tested?

Checklist

  • I have performed a self-review of my own code.
  • I have updated the documentation.
  • I have added tests to prove my changes are effective.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @YiXR, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request establishes the foundational architecture for a new tiered cache backend within mooncake-store. It introduces key abstractions for managing different storage tiers, a flexible data copying utility for heterogeneous memory types, and a central backend component to orchestrate data operations. The design emphasizes a decoupled approach to data allocation and management, allowing for sophisticated and distributed caching strategies to be built on top, significantly enhancing the system's ability to handle diverse storage media and allocation policies.

Highlights

  • Abstract Cache Tier Interface: Introduced CacheTier as an abstract base class, defining a unified interface for managing data lifecycle on individual storage mediums (e.g., DRAM, SSD) with core operations like Get, Delete, Contains, and decoupled Allocate, WriteAt, BindKey, and Free methods.
  • Data Copier Mechanism: Implemented DataCopier and DataCopierBuilder to efficiently handle data movement between heterogeneous MemoryTypes, supporting direct copy paths and a fallback mechanism using DRAM as an intermediate buffer.
  • Tiered Backend Data Plane: Introduced TieredBackend as the core data plane for the tiered cache, responsible for executing data operations without complex caching policies, and managing TieredLocation and DataSource structures.
  • Decoupled Allocation APIs: The traditional Put operation is broken down into Allocate, Write, and Commit to support flexible allocation strategies, including Client-Centric (local decision) and Master-Centric (global decision) workflows.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This PR introduces the foundational abstract classes and data plane logic for a tiered cache backend. The design is well-decomposed into CacheTier, DataCopier, and TieredBackend, supporting flexible allocation strategies. The code is generally well-structured. However, I've found several critical issues related to resource management and error handling, particularly in the DataCopier and TieredBackend implementations. There are also some parts of the implementation that are incomplete, preventing the new backend from being functional. My review includes suggestions to fix these critical bugs and address the incomplete implementation.

Comment on lines 82 to 87
std::unique_ptr<char[]> temp_dram_buffer(new char[src.size]);
if (!temp_dram_buffer) {
LOG(ERROR) << "Failed to allocate temporary DRAM buffer for "
"fallback copy.";
return false;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

critical

The allocation new char[src.size] will throw a std::bad_alloc exception on failure, but the code checks for a nullptr return value as if new(std::nothrow) was used. This check will never be true for a standard new expression, and an unhandled std::bad_alloc exception will terminate the program. To correctly handle allocation failure without exceptions, you should use the nothrow version of new.

Suggested change
std::unique_ptr<char[]> temp_dram_buffer(new char[src.size]);
if (!temp_dram_buffer) {
LOG(ERROR) << "Failed to allocate temporary DRAM buffer for "
"fallback copy.";
return false;
}
std::unique_ptr<char[]> temp_dram_buffer(new (std::nothrow) char[src.size]);
if (!temp_dram_buffer) {
LOG(ERROR) << "Failed to allocate temporary DRAM buffer for "
"fallback copy.";
return false;
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

fixed

Comment on lines 176 to 179
if (!dest_tier->WriteAt(dest_loc.offset, source)) {
LOG(ERROR) << "MoveData failed: Write error.";
// dest_tier->Free(dest_loc.offset);
return false;
Copy link
Contributor

Choose a reason for hiding this comment

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

critical

In the MoveData function, if WriteAt fails, the space allocated in the destination tier is not freed. The call to dest_tier->Free() is commented out, which will lead to a resource leak. This rollback logic is critical for correctness. Note that the Free method requires both an offset and a size.

    if (!dest_tier->WriteAt(dest_loc.offset, source)) {
        LOG(ERROR) << "MoveData failed: Write error.";
        dest_tier->Free(dest_loc.offset, source.size);
        return false;

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

fixed

Comment on lines 183 to 337
if (sync_cb) {
bool sync_success = sync_cb(key, dest_loc);
if (!sync_success) {
LOG(ERROR) << "MoveData aborted: Master sync failed.";
// Rollback: Free the newly allocated data
// dest_tier->Free(dest_loc.offset);
// Keep the source data intact
return false;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

critical

If the sync_cb callback fails, the logic to roll back the operation by freeing the newly allocated block in the destination tier is commented out. This will cause a resource leak, as the space will be allocated but never used or tracked. The rollback is essential for maintaining a consistent state. The Free method requires both an offset and a size.

    if (sync_cb) {
        bool sync_success = sync_cb(key, dest_loc);
        if (!sync_success) {
            LOG(ERROR) << "MoveData aborted: Master sync failed.";
            // Rollback: Free the newly allocated data
            dest_tier->Free(dest_loc.offset, source.size);
            // Keep the source data intact
            return false;
        }
    }

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

fixed

Comment on lines +40 to +55
// TODO: Logic to instantiate specific CacheTier types (DRAM/SSD) goes
// here. For example: std::unique_ptr<CacheTier> tier =
// CacheTierFactory::Create(tier_config); tier->Init(this, engine);
// tiers_[id] = std::move(tier);

// Placeholder for compilation if Factory is not ready
// tiers_[id] = std::make_unique<DramTier>();

tier_info_[id] = {priority, tags};
Copy link
Contributor

Choose a reason for hiding this comment

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

high

The Init function parses tier configurations but doesn't instantiate any CacheTier objects, leaving the tiers_ map empty. This makes the TieredBackend non-functional, as core operations like Allocate, Put, and MoveData will fail or do nothing. While the TODO indicates this is known, for this feature to be testable and usable, at least a placeholder or mock tier implementation should be instantiated here based on the configuration.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

will be realized later.

Comment on lines 112 to 115
if (it == tiers_.end()) {
throw std::runtime_error(
fmt::format("Commit failed: Invalid tier ID {}", loc.tier_id));
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The Commit method throws a std::runtime_error on failure. However, the PR description states that Commit is designed to be infallible and should raise a fatal error on internal failure. Throwing an exception is inconsistent with this "fatal error" contract. To align with the documented behavior, you should consider using LOG(FATAL) instead of throwing an exception, which will terminate the process and clearly signal a critical, unrecoverable state.

    if (it == tiers_.end()) {
        LOG(FATAL) << "Commit failed: Invalid tier ID " << loc.tier_id;
    }

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

fixed

@YiXR YiXR force-pushed the xinyi/tiered-backend branch 2 times, most recently from c91b75d to efc0278 Compare December 15, 2025 09:59
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces foundational abstract base classes for a tiered cache management system in mooncake-store. The architecture enables flexible data placement across different storage tiers (DRAM, SSD, etc.) with support for both client-centric and master-centric allocation strategies. The design emphasizes RAII-based resource management through reference-counted handles and provides a sophisticated data copying framework with automatic fallback mechanisms.

Key Changes

  • Implements core tiered cache abstractions: CacheTier interface, TieredBackend data plane, and DataCopier utility
  • Introduces decomposed allocation workflow (Allocate → Write → Commit) to support flexible placement strategies
  • Provides Make-Before-Break data migration protocol ensuring strong consistency during tier transitions

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 12 comments.

Show a summary per file
File Description
mooncake-store/include/tiered_cache/cache_tier.h Defines abstract CacheTier interface with decomposed allocation/write/bind operations and MemoryType enum
mooncake-store/include/tiered_cache/tiered_backend.h Declares TieredBackend class with RAII handle-based resource management and data migration support
mooncake-store/include/tiered_cache/data_copier.h Defines DataCopier and DataCopierBuilder for memory-type-aware data copying with DRAM fallback
mooncake-store/include/tiered_cache/copier_registry.h Provides singleton registry for static registration of memory type copy functions
mooncake-store/src/tiered_cache/tiered_backend.cpp Implements backend initialization, allocation, commit, deletion, and migration logic
mooncake-store/src/tiered_cache/data_copier.cpp Implements builder pattern and copy logic with automatic DRAM-based fallback mechanism
mooncake-store/src/tiered_cache/copier_registry.cpp Implements singleton registry and registrar helper for static initialization
mooncake-store/src/CMakeLists.txt Adds three new source files to the build system

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.


/**
* @brief Retrieves data pointer/info for a key.
* implementation relies on internal index (Key -> Offset).
Copy link

Copilot AI Dec 15, 2025

Choose a reason for hiding this comment

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

The documentation for the Get method says "Retrieves data pointer/info for a key" but doesn't document what happens when the key is not found. The method should specify the expected behavior for the return value and output parameters (data and size) when the key doesn't exist.

Suggested change
* implementation relies on internal index (Key -> Offset).
* Implementation relies on internal index (Key -> Offset).
*
* @param key The key to look up.
* @param data [out] On success, set to point to the data for the key; on failure (key not found), set to nullptr.
* @param size [out] On success, set to the size of the data; on failure (key not found), set to 0.
* @return true if the key exists and data/size are set; false if the key does not exist (data is set to nullptr, size to 0).

Copilot uses AI. Check for mistakes.
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

fixed

Comment on lines 141 to 142
// Handle is NOT added to map.
// If caller drops the handle, ref count -> 0, resource freed.
Copy link

Copilot AI Dec 15, 2025

Choose a reason for hiding this comment

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

The comment at lines 141-142 describes unreachable code logic that appears to be leftover from an earlier implementation. Since the function returns false at line 140, these comments about handle behavior are confusing and should be removed as they don't accurately describe the actual code flow.

Suggested change
// Handle is NOT added to map.
// If caller drops the handle, ref count -> 0, resource freed.

Copilot uses AI. Check for mistakes.
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

fixed


if (src_handle->loc.tier_id == dest_tier_id) return true;

auto src_tier = tiers_[src_handle->loc.tier_id].get();
Copy link

Copilot AI Dec 15, 2025

Choose a reason for hiding this comment

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

Accessing tiers_[src_handle->loc.tier_id] without checking if the tier exists could cause undefined behavior. Using operator[] will create a default-constructed entry if the tier_id doesn't exist, leading to a nullptr dereference at .get(). This should use tiers_.find() or tiers_.at() with proper error handling, consistent with the pattern used at lines 105 and 127.

Suggested change
auto src_tier = tiers_[src_handle->loc.tier_id].get();
auto tier_it = tiers_.find(src_handle->loc.tier_id);
if (tier_it == tiers_.end()) {
LOG(ERROR) << "MoveData failed: Source tier_id " << src_handle->loc.tier_id << " not found.";
return false;
}
auto src_tier = tier_it->second.get();

Copilot uses AI. Check for mistakes.
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

fixed

Comment on lines 139 to 140
LOG(WARNING) << "Commit failed: Key " << key << " already exists.";
return false;
Copy link

Copilot AI Dec 15, 2025

Choose a reason for hiding this comment

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

The Commit method returns false when a key already exists, but doesn't provide a way for the caller to distinguish this from other failure modes. According to the design described in the PR, Commit "is designed to be infallible (must succeed). If an internal error occurs (e.g., OOM), it raises a fatal error." However, this implementation treats duplicate keys as a soft error. This inconsistency between the documented behavior and implementation could lead to confusion and resource leaks if callers don't properly handle the returned AllocationHandle.

Suggested change
LOG(WARNING) << "Commit failed: Key " << key << " already exists.";
return false;
LOG(FATAL) << "Commit failed: Key " << key << " already exists. Commit is designed to be infallible.";
// LOG(FATAL) will terminate the process, so no return needed.

Copilot uses AI. Check for mistakes.
/**
* @brief Reserve Space (Allocation)
* Finds free space of `size` bytes. Does NOT copy data.
* * @param size Bytes to allocate.
Copy link

Copilot AI Dec 15, 2025

Choose a reason for hiding this comment

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

The documentation has inconsistent formatting. Line 81 has a double asterisk "* *" at the start which appears to be a typo or formatting error. The subsequent parameter descriptions should follow consistent formatting.

Suggested change
* * @param size Bytes to allocate.
* @param size Bytes to allocate.

Copilot uses AI. Check for mistakes.
/**
* @brief Returns a DataSource descriptor for a key's data.
* Used for data migration (moving data out of this tier).
*/
Copy link

Copilot AI Dec 15, 2025

Choose a reason for hiding this comment

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

The AsDataSource method documentation doesn't specify what should be returned when the key doesn't exist. Based on the usage in tiered_backend.cpp line 196, it appears a DataSource with ptr=nullptr might indicate an error, but this contract should be explicitly documented in the interface.

Suggested change
*/
* @note If the key does not exist, returns a DataSource with ptr == nullptr to indicate an error.

Copilot uses AI. Check for mistakes.
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

fixed

Comment on lines 146 to 147
auto& tier = tiers_[handle->loc.tier_id];
tier->BindKey(key, handle->loc.offset, handle->size);
Copy link

Copilot AI Dec 15, 2025

Choose a reason for hiding this comment

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

Accessing tiers_[handle->loc.tier_id] without checking if the tier exists first could cause undefined behavior. While tiers_.find() is used in other methods (lines 105, 127), this line directly accesses the map using operator[], which will create a default-constructed entry if the tier_id doesn't exist. This should use tiers_.find() or tiers_.at() with error handling.

Suggested change
auto& tier = tiers_[handle->loc.tier_id];
tier->BindKey(key, handle->loc.offset, handle->size);
auto it = tiers_.find(handle->loc.tier_id);
if (it == tiers_.end()) {
LOG(ERROR) << "Commit failed: Tier " << handle->loc.tier_id << " does not exist.";
return false;
}
it->second->BindKey(key, handle->loc.offset, handle->size);

Copilot uses AI. Check for mistakes.
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

fixed

/**
* @typedef AllocationHandle
* @brief A reference-counted handle to a storage resource.
* * Lifecycle Semantics:
Copy link

Copilot AI Dec 15, 2025

Choose a reason for hiding this comment

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

The documentation comment has inconsistent formatting with "* Lifecycle Semantics:" having an extra asterisk. This should be formatted consistently with other documentation comments in the file.

Suggested change
* * Lifecycle Semantics:
* Lifecycle Semantics:

Copilot uses AI. Check for mistakes.
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

fixed

Comment on lines 20 to 29
bool TieredBackend::Init(Json::Value root, TransferEngine* engine) {
// Initialize DataCopier
try {
DataCopierBuilder builder;
data_copier_ = builder.Build();
} catch (const std::logic_error& e) {
LOG(FATAL) << "Failed to build DataCopier: " << e.what();
return false;
}
Copy link

Copilot AI Dec 15, 2025

Choose a reason for hiding this comment

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

The error handling is inconsistent here. After logging a FATAL error at line 26, the function returns false at line 27, but LOG(FATAL) typically terminates the program. Either remove the return statement since it's unreachable, or change LOG(FATAL) to LOG(ERROR) if the intent is to allow the caller to handle the failure gracefully.

Copilot uses AI. Check for mistakes.
Comment on lines 220 to 223
}

src_tier->Delete(key);

Copy link

Copilot AI Dec 15, 2025

Choose a reason for hiding this comment

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

There is a potential race condition in the MoveData operation. After releasing the map_mutex_ at line 220 and before calling src_tier->Delete(key) at line 222, another thread could call Get(key) and receive the new handle, then attempt to use the old tier's data through AsDataSource before it's deleted. The source tier deletion should happen while still holding the lock, or additional synchronization is needed to ensure the old data isn't accessed during deletion.

Suggested change
}
src_tier->Delete(key);
src_tier->Delete(key);
}

Copilot uses AI. Check for mistakes.
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

fixed

@YiXR YiXR force-pushed the xinyi/tiered-backend branch 10 times, most recently from d79029c to 930d91f Compare December 17, 2025 08:50
@YiXR YiXR changed the title [Store] feat: introduce tired backend [Store] feat: introduce tiered backend Dec 17, 2025
@YiXR YiXR force-pushed the xinyi/tiered-backend branch from 930d91f to e1b2f1a Compare December 17, 2025 09:28
Signed-off-by: Xingrui Yi <yixingrui@linux.alibaba.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant