Skip to content

[Feature] Doris Cluster Snapshot Backup #61464

@kaori-seasons

Description

@kaori-seasons

Search before asking

  • I had searched in the issues and found no similar issues.

Description

1. Background & Objectives

1.1 Business Scenarios

In Doris Cloud mode, users require periodic snapshot backups of cluster metadata to support:

  • Disaster Recovery: Restore FE metadata to a specific point in time when corruption occurs
  • Instance Cloning: Clone read-only/writable/rollback instances from a snapshot for testing and data analysis
  • Compliance Auditing: Meet data backup SLA requirements for industries such as finance and healthcare

1.2 Framework Status

Doris already provides a complete snapshot framework skeleton, but all business methods are Not implemented stubs:

Layer Component Status
C++ Meta-Service SnapshotManager base class 18 virtual methods, all returning UNDEFINED_ERR or no-op
C++ Meta-Service MetaServiceImpl RPC delegation 7 RPC handlers with parameter validation + delegation logic implemented
Java FE CloudSnapshotHandler base class submitJob / refreshAutoSnapshotJob / cloneSnapshot all throw NotImplementedException
Java FE SQL Commands AdminCreate/Set/Drop ClusterSnapshot — 3 commands completed
Proto cloud.proto All RPC message definitions ready (7 RPCs + 14 messages)
TxnKv Keys keys.h snapshot_full_key / snapshot_reference_key defined
RPC Channel MetaServiceProxy / MetaServiceClient 7 snapshot methods wrapped
BE Display SchemaClusterSnapshotsScanner information_schema.cluster_snapshots implemented
Config Config.java 6 cloud_snapshot_* / multi_part_upload_* config items defined
Tests meta_service_snapshot_test.cpp Only BeginSnapshotTest exists (DISABLED)

1.3 Design Goals

  • G-1: Implement all 18 virtual methods of SnapshotManager, covering the complete snapshot lifecycle
  • G-2: Implement a CloudSnapshotHandler subclass, completing FE-side scheduling, upload, and recovery logic
  • G-3: Do not modify any existing Proto definitions or RPC signatures — develop entirely based on framework extension points
  • G-4: All core flows covered by unit tests + integration tests, with 100% executable test cases
  • G-5: Support S3/OSS/GCS/HDFS multi-storage backends via the StorageVaultAccessor abstraction layer

2. Technical Infrastructure Analysis

2.1 Framework Extension Point Verification

Extension Point 1: C++ SnapshotManager (Meta-Service Side)

Verified through source code — all business methods in the base class default to returning UNDEFINED_ERR:

// cloud/src/snapshot/snapshot_manager.cpp (L72-L77)
void SnapshotManager::begin_snapshot(std::string_view instance_id,
                                     const BeginSnapshotRequest& request,
                                     BeginSnapshotResponse* response) {
    response->mutable_status()->set_code(MetaServiceCode::UNDEFINED_ERR);
    response->mutable_status()->set_msg("Not implemented");
}

Injection point confirmed: meta_server.cpp L82-83 and recycler.cpp L594 are the only locations where SnapshotManager is instantiated. Customization only requires replacing these two locations:

// cloud/src/meta-service/meta_server.cpp (L82-83)
auto snapshot_mgr = std::make_shared<SnapshotManager>(txn_kv_);
auto meta_service = std::make_unique<MetaServiceImpl>(txn_kv_, rc_mgr, rate_limiter, std::move(snapshot_mgr));

// cloud/src/recycler/recycler.cpp (L594)
snapshot_manager_ = std::make_shared<SnapshotManager>(txn_kv_);

Extension Point 2: Java CloudSnapshotHandler (FE Scheduling Side)

Dynamically loaded via reflection, with the class name controlled by Config.cloud_snapshot_handler_class:

// CloudSnapshotHandler.java (L42-53)
public static CloudSnapshotHandler getInstance() {
    Class<CloudSnapshotHandler> theClass = (Class<CloudSnapshotHandler>) Class.forName(
            Config.cloud_snapshot_handler_class);
    Constructor<CloudSnapshotHandler> constructor = theClass.getDeclaredConstructor();
    return constructor.newInstance();
}

CloudEnv calls the handler's initialize() during initialize(), and starts the background daemon in startMasterOnlyDaemonThreads().

2.2 TxnKv Key Format Confirmation

// keys.h (L110-111)
0x03 "snapshot" ${instance_id} "full" ${timestamp}                      -> SnapshotPB
0x03 "snapshot" ${instance_id} "reference" ${timestamp} ${instance_id}  -> ${empty_value}
  • Full Key: Stores snapshot metadata SnapshotPB, with Versionstamp as the timestamp suffix
  • Reference Key: Records instance IDs derived from this snapshot, used for reference counting and deletion protection

Existing utility functions:

// keys.h (L539-545)
versioned::snapshot_full_key({instance_id})
versioned::snapshot_reference_key({instance_id, versionstamp, ref_instance_id})
versioned::snapshot_reference_key_prefix(instance_id)
versioned::snapshot_key_prefix(instance_id)

2.3 Snapshot State Machine Confirmation

// cloud.proto (L821-830)
enum SnapshotStatus {
    SNAPSHOT_PREPARE = 0;   // Creating
    SNAPSHOT_NORMAL = 1;    // Ready
    SNAPSHOT_ABORTED = 2;   // Aborted
    SNAPSHOT_RECYCLED = 3;  // Marked for recycling
}

State transitions: PREPARE → NORMAL (success) | PREPARE → ABORTED (failure) | NORMAL → RECYCLED (deletion/expiration)

2.4 Existing RPC Parameter Validation Logic

MetaServiceImpl has already completed unified cloud_unique_id → instance_id conversion and rate limiting. Subclass implementations do not need to repeat this:

// meta_service_snapshot.cpp (L27-49) — using begin_snapshot as an example
void MetaServiceImpl::begin_snapshot(...) {
    RPC_PREPROCESS(begin_snapshot, get, put, del);
    // cloud_unique_id validation
    instance_id = get_instance_id(resource_mgr_, request->cloud_unique_id());
    RPC_RATE_LIMIT(begin_snapshot);
    // Delegate to SnapshotManager
    snapshot_manager_->begin_snapshot(instance_id, *request, response);
}

2.5 Object Storage Access Interface Confirmation

StorageVaultAccessor provides a complete storage operation abstraction:

// storage_vault_accessor.h (L51-97)
class StorageVaultAccessor {
    virtual int delete_prefix(const std::string& path_prefix, int64_t expiration_time = 0) = 0;
    virtual int delete_directory(const std::string& dir_path) = 0;
    virtual int delete_file(const std::string& path) = 0;
    virtual int list_directory(const std::string& dir_path, std::unique_ptr<ListIterator>* res) = 0;
    virtual int put_file(const std::string& path, const std::string& content) = 0;
    virtual int exists(const std::string& path) = 0;
    virtual int abort_multipart_upload(const std::string& path, const std::string& upload_id) = 0;
};

The abort_multipart_upload method is built-in and can be used directly to clean up multipart uploads during abort_snapshot.

2.6 FE Configuration Items Status

Config Item Default Value Description
cloud_snapshot_handler_class "...CloudSnapshotHandler" Handler implementation class name
cloud_snapshot_handler_interval_second 3600 Background scheduling interval (seconds)
cloud_snapshot_timeout_seconds 600 PREPARE state timeout (seconds)
cloud_auto_snapshot_max_reversed_num 35 Maximum retained auto-snapshots
cloud_auto_snapshot_min_interval_seconds 3600 Minimum auto-snapshot interval (seconds)
multi_part_upload_part_size_in_bytes 256MB Multipart upload chunk size
multi_part_upload_max_seconds 3600 Multipart upload timeout (seconds)
multi_part_upload_pool_size 10 Multipart upload thread pool size

3. Technical Constraints & Design Principles

3.1 Hard Constraints

ID Constraint Impact
C-1 SnapshotManager base class txn_kv_ is private Subclass must receive TxnKv reference via constructor, or base class must be changed to protected
C-2 Versionstamp depends on FDB transaction commit begin_snapshot must use atomic versionstamp operations within an FDB transaction
C-3 CloudSnapshotHandler loaded via reflection Subclass must have a no-argument constructor
C-4 MetaServiceImpl RPC preprocessing already completes parameter validation SnapshotManager methods do not need to re-validate cloud_unique_id
C-5 Proto message definitions cannot be modified All field semantics are finalized; no fields may be added or removed
C-6 MasterDaemon.runOneCycle() waits for Catalog readiness runAfterCatalogReady() executes after FE image loading is complete
C-7 snapshot_full_key uses encode_versioned_key encoding Range scans must use snapshot_key_prefix + range queries

3.2 Design Trade-offs

Trade-off Decision Rationale
Subclass vs. direct base class modification Inherit via subclass Aligns with framework design intent; does not affect other consumers
FE Image upload: synchronous vs. asynchronous Asynchronous thread pool Large Image uploads may take tens of minutes; cannot block the main thread
Snapshot concurrency: parallel vs. serial Serial (single-thread pool) Avoids consistency issues from concurrent checkpoints
Versionstamp generation: client vs. server Server-side (FDB atomic op) Ensures globally monotonic ordering; avoids clock drift
Auto-snapshot expiration: scheduled scan vs. event-driven Scheduled scan Reuses MasterDaemon scheduling framework; simple and reliable
Clone types: READ_ONLY only vs. all Implement all Proto already defines READ_ONLY/WRITABLE/ROLLBACK; full support required

4. Core Implementation Plan

4.1 Layered Implementation Strategy

graph TB
    subgraph "Phase 1: C++ SnapshotManager Core Implementation"
        A1["DorisSnapshotManager extends SnapshotManager"]
        A2["begin_snapshot: validation → build SnapshotPB → FDB atomic write"]
        A3["update_snapshot: update upload_file/upload_id"]
        A4["commit_snapshot: PREPARE→NORMAL + write finish_at/sizes"]
        A5["abort_snapshot: →ABORTED + abort multipart upload"]
        A6["drop_snapshot: →RECYCLED"]
        A7["list_snapshot: range scan + SnapshotInfoPB conversion"]
        A8["clone_instance: metadata clone + reference recording"]
    end

    subgraph "Phase 2: C++ Operational Capabilities"
        B1["recycle_snapshots: TTL + max_reserved cleanup"]
        B2["recycle_snapshot_meta_and_data: object store + KV dual deletion"]
        B3["check_snapshots / inverted_check: consistency verification"]
        B4["set_multi_version_status: MVCC state management"]
        B5["migrate_to_versioned_keys: key migration"]
        B6["compact_snapshot_chains: snapshot chain compression"]
    end

    subgraph "Phase 3: Java CloudSnapshotHandler Implementation"
        C1["DorisCloudSnapshotHandler extends CloudSnapshotHandler"]
        C2["initialize(): load persisted state + init S3 client"]
        C3["runAfterCatalogReady(): auto-snapshot trigger check"]
        C4["submitJob(ttl, label): five-step workflow"]
        C5["refreshAutoSnapshotJob(): reload scheduling config"]
        C6["cloneSnapshot(file): restore FE from snapshot"]
    end

    subgraph "Phase 4: Testing + Integration"
        D1["C++ unit tests: cover all SnapshotManager methods"]
        D2["Java unit tests: mock MetaServiceProxy"]
        D3["Integration tests: end-to-end SQL command verification"]
        D4["Regression tests: regression-test/suites"]
    end

    A1 --> A2 --> A3 --> A4 --> A5 --> A6 --> A7 --> A8
    A8 --> B1 --> B2 --> B3 --> B4 --> B5 --> B6
    B6 --> C1 --> C2 --> C3 --> C4 --> C5 --> C6
    C6 --> D1 --> D2 --> D3 --> D4
Loading

4.2 Snapshot Lifecycle Core Workflow

sequenceDiagram
    participant User as User/Scheduler
    participant Handler as DorisCloudSnapshotHandler
    participant Proxy as MetaServiceProxy
    participant SnapMgr as DorisSnapshotManager
    participant FDB as FoundationDB
    participant ObjStore as Object Storage

    User->>Handler: submitJob(ttl, label)
    Handler->>Proxy: beginSnapshot(cloud_unique_id, label, timeout, ttl)
    Proxy->>SnapMgr: begin_snapshot(instance_id, req, resp)
    SnapMgr->>FDB: atomic write SnapshotPB{PREPARE} + get versionstamp
    SnapMgr-->>Handler: {snapshot_id, image_url, obj_info}

    Handler->>Handler: Catalog.checkpoint() to get last_journal_id
    Handler->>ObjStore: multipart upload FE Image to image_url

    loop Each part
        Handler->>Proxy: updateSnapshot(snapshot_id, upload_file, upload_id)
        Proxy->>SnapMgr: update_snapshot()
        SnapMgr->>FDB: update SnapshotPB.upload_file/upload_id
    end

    Handler->>Proxy: commitSnapshot(snapshot_id, image_url, journal_id, sizes)
    Proxy->>SnapMgr: commit_snapshot()
    SnapMgr->>FDB: update SnapshotPB{NORMAL, finish_at}

    Note over Handler,ObjStore: Error path
    Handler->>Proxy: abortSnapshot(snapshot_id, reason)
    Proxy->>SnapMgr: abort_snapshot()
    SnapMgr->>FDB: update SnapshotPB{ABORTED}
    SnapMgr->>ObjStore: abort_multipart_upload(path, upload_id)
Loading

5. C++ DorisSnapshotManager Method-Level Design

5.1 Class Definition

File: cloud/src/snapshot/doris_snapshot_manager.h

class DorisSnapshotManager : public SnapshotManager {
public:
    DorisSnapshotManager(std::shared_ptr<TxnKv> txn_kv);
    ~DorisSnapshotManager() override = default;

    void begin_snapshot(std::string_view instance_id, const BeginSnapshotRequest& request,
                        BeginSnapshotResponse* response) override;
    void update_snapshot(std::string_view instance_id, const UpdateSnapshotRequest& request,
                         UpdateSnapshotResponse* response) override;
    void commit_snapshot(std::string_view instance_id, const CommitSnapshotRequest& request,
                         CommitSnapshotResponse* response) override;
    void abort_snapshot(std::string_view instance_id, const AbortSnapshotRequest& request,
                        AbortSnapshotResponse* response) override;
    void drop_snapshot(std::string_view instance_id, const DropSnapshotRequest& request,
                       DropSnapshotResponse* response) override;
    void list_snapshot(std::string_view instance_id, const ListSnapshotRequest& request,
                       ListSnapshotResponse* response) override;
    void clone_instance(const CloneInstanceRequest& request,
                        CloneInstanceResponse* response) override;

    std::pair<MetaServiceCode, std::string> set_multi_version_status(
            std::string_view instance_id, MultiVersionStatus multi_version_status) override;

    int check_snapshots(InstanceChecker* checker) override;
    int inverted_check_snapshots(InstanceChecker* checker) override;
    int check_mvcc_meta_key(InstanceChecker* checker) override;
    int inverted_check_mvcc_meta_key(InstanceChecker* checker) override;
    int check_meta(MetaChecker* meta_checker) override;
    int recycle_snapshots(InstanceRecycler* recycler) override;
    int recycle_snapshot_meta_and_data(std::string_view instance_id,
                                       std::string_view resource_id,
                                       StorageVaultAccessor* accessor,
                                       Versionstamp snapshot_version,
                                       const SnapshotPB& snapshot_pb) override;
    int migrate_to_versioned_keys(InstanceDataMigrator* migrator) override;
    int compact_snapshot_chains(InstanceChainCompactor* compactor) override;

private:
    std::shared_ptr<TxnKv> txn_kv_;  // Independently held reference

    // Internal helper methods
    int read_snapshot_pb(std::string_view instance_id, const Versionstamp& vs, SnapshotPB* pb);
    int write_snapshot_pb(std::string_view instance_id, const Versionstamp& vs, const SnapshotPB& pb);
    int read_instance_info(std::string_view instance_id, InstanceInfoPB* info);
    int get_storage_vault_accessor(const InstanceInfoPB& instance, const std::string& resource_id,
                                   std::unique_ptr<StorageVaultAccessor>* accessor);
    bool validate_ip_address(const std::string& ip);
    std::string build_image_url(const InstanceInfoPB& instance, const std::string& snapshot_id);
};

5.2 Method Implementation Specifications

Method Key Input Fields Core Implementation Logic Key Return Fields Error Codes
begin_snapshot snapshot_label, timeout_seconds, ttl_seconds, auto_snapshot, request_ip 1. Validate parameters (timeout>0, ttl>0, label non-empty); 2. Optionally validate IP format (IPv4/IPv6); 3. Read InstanceInfoPB to obtain obj_info or storage_vault config; 4. Use FDB atomic versionstamp operation to write SnapshotPB{PREPARE, create_at=now()}; 5. Construct image_url = <prefix>/snapshot/<snapshot_id>/image/ snapshot_id, image_url, obj_info INVALID_ARGUMENT / ALREADY_EXISTED
update_snapshot snapshot_id, upload_file, upload_id 1. Parse snapshot_id to Versionstamp; 2. Read SnapshotPB, verify status==PREPARE; 3. Update upload_file / upload_id; 4. Write back to TxnKv status only INVALID_ARGUMENT / SNAPSHOT_NOT_FOUND
commit_snapshot snapshot_id, image_url, last_journal_id, snapshot_meta_image_size, snapshot_logical_data_size 1. Read SnapshotPB, verify status==PREPARE; 2. Timeout check now < create_at + timeout_seconds; 3. Update status to NORMAL, write finish_at, last_journal_id, sizes; 4. Write back to TxnKv status only SNAPSHOT_EXPIRED / SNAPSHOT_NOT_FOUND
abort_snapshot snapshot_id, reason 1. Read SnapshotPB; 2. If upload_file/upload_id exist, abort multipart upload via StorageVaultAccessor; 3. Status→ABORTED, write reason; 4. Write back to TxnKv status only SNAPSHOT_NOT_FOUND
drop_snapshot snapshot_id 1. Read SnapshotPB; 2. Only allow deletion of NORMAL/ABORTED snapshots; 3. Check if reference keys exist (if derived instances exist, only mark RECYCLED; otherwise can clean immediately) status only INVALID_ARGUMENT / SNAPSHOT_NOT_FOUND
list_snapshot required_snapshot_id, include_aborted, instance_id 1. Range scan TxnKv with snapshot_key_prefix(instance_id) as prefix; 2. Deserialize each SnapshotPB to SnapshotInfoPB; 3. Query reference keys to populate derived_instance_ids; 4. Filter by include_aborted; 5. Sort by creation time descending repeated SnapshotInfoPB
clone_instance from_snapshot_id, from_instance_id, new_instance_id, clone_type 1. Read source snapshot SnapshotPB (verify NORMAL); 2. Read source instance InstanceInfoPB; 3. Construct new instance metadata by clone_type; 4. Write snapshot_reference_key to record derivation; 5. Write new instance InstanceInfoPB obj_info, image_url INVALID_ARGUMENT / SNAPSHOT_NOT_FOUND
recycle_snapshots InstanceRecycler* Scan all SnapshotPBs: a) TTL expired (now > create_at + ttl_seconds) → mark RECYCLED; b) status==RECYCLED → call recycle_snapshot_meta_and_data(); c) NORMAL count > max_reserved → mark oldest as RECYCLED 0/non-zero
recycle_snapshot_meta_and_data instance_id, resource_id, StorageVaultAccessor*, Versionstamp, SnapshotPB 1. accessor->delete_directory(image_url) to delete object storage files; 2. Delete snapshot_full_key from TxnKv; 3. Delete all associated snapshot_reference_keys 0/non-zero

5.3 begin_snapshot Key Implementation (Simulated Walkthrough)

Input: instance_id="inst_001", label="daily_backup", timeout=3600, ttl=86400, auto=true

Step 1: Validation
  - timeout_seconds=3600 > 0 ✓
  - ttl_seconds=86400 > 0 ✓
  - label="daily_backup" non-empty ✓

Step 2: Read Instance Info
  - key: instance_key({instance_id}) → InstanceInfoPB
  - Obtain obj_info[0] = {bucket="my-bucket", prefix="doris-data", endpoint="s3.cn-north.amazonaws.com"}
  - resource_id = obj_info[0].id

Step 3: FDB Atomic Write
  - snapshot_full_key = versioned::snapshot_full_key({instance_id})
  - Construct SnapshotPB:
      status = SNAPSHOT_PREPARE
      type = SNAPSHOT_REFERENCE
      instance_id = "inst_001"
      create_at = 1742313600 (current UNIX timestamp)
      timeout_seconds = 3600
      ttl_seconds = 86400
      auto = true
      label = "daily_backup"
      resource_id = obj_info[0].id
  - txn->atomic_set_versionstamped_key(snapshot_full_key, SnapshotPB)
  - txn->commit() → obtain versionstamp

Step 4: Construct Response
  - snapshot_id = serialize_snapshot_id(versionstamp) → "0a1b2c3d4e5f6789a0b1"
  - image_url = "doris-data/snapshot/0a1b2c3d4e5f6789a0b1/image/"
  - obj_info = instance's ObjectStoreInfoPB

Result: response = {snapshot_id, image_url, obj_info} ✓

5.4 clone_instance Three Clone Types Implementation

Field READ_ONLY WRITABLE ROLLBACK
ready_only true false false
source_instance_id Source instance ID Source instance ID Source instance ID
source_snapshot_id Snapshot ID Snapshot ID Snapshot ID
original_instance_id Earliest source instance ID
successor_instance_id Previous inherited instance ID
obj_info Inherited from source Uses new obj_info from request Inherited from source
storage_vault Inherited from source Uses new vault from request Inherited from source
Data visibility Read-only source data Independent copy Rolled back to snapshot point

6. Java DorisCloudSnapshotHandler Detailed Design

6.1 Class Definition

File: fe/fe-core/src/main/java/org/apache/doris/cloud/snapshot/DorisCloudSnapshotHandler.java

public class DorisCloudSnapshotHandler extends CloudSnapshotHandler {

    private ExecutorService snapshotExecutor;  // Single-thread serial execution
    private volatile SnapshotState currentSnapshot;  // Currently in-progress snapshot

    @Override
    public void initialize() {
        snapshotExecutor = Executors.newSingleThreadExecutor(
            new ThreadFactoryBuilder().setNameFormat("snapshot-worker-%d").setDaemon(true).build());
        // Load FE persisted snapshot state (if any)
    }

    @Override
    protected void runAfterCatalogReady() { /* Auto-snapshot trigger logic */ }

    @Override
    public void submitJob(long ttl, String label) throws Exception { /* Manual snapshot submission */ }

    @Override
    public synchronized void refreshAutoSnapshotJob() throws Exception { /* Reload auto-snapshot config */ }

    @Override
    public void cloneSnapshot(String clusterSnapshotFile) throws Exception { /* Restore from snapshot */ }
}

6.2 Method Implementation Specifications

Method Core Implementation Logic
initialize() 1. Create single-thread snapshotExecutor; 2. Initialize object storage client (based on AK/SK/Endpoint from obj_info); 3. Load FE persisted SnapshotState (if an in-progress snapshot exists, check whether abort is needed)
runAfterCatalogReady() 1. Execute only on Master (Env.isMaster() check); 2. Call MetaServiceProxy.getInstance().getSnapshotProperties() to get switch_status / max_reserved / interval; 3. Skip if switch_status != SNAPSHOT_SWITCH_ON; 4. Call listSnapshot(false) to get NORMAL list; 5. Trigger if now - latest snapshot time >= snapshot_interval_seconds; 6. Abort PREPARE snapshots exceeding cloud_snapshot_timeout_seconds
submitJob(ttl, label) 1. Verify no in-progress PREPARE snapshot; 2. Submit async task to snapshotExecutor; 3. Async task executes five-step workflow: beginSnapshot → checkpoint → multipartUpload → updateSnapshot → commitSnapshot; 4. Call abortSnapshot on any step failure
refreshAutoSnapshotJob() 1. Read latest auto-snapshot configuration from MetaService; 2. If scheduling interval changed, call setInterval() to update MasterDaemon.intervalMs
cloneSnapshot(file) 1. Parse clusterSnapshotFile to obtain snapshot_id; 2. Call MetaServiceProxy.cloneInstance(); 3. Download FE Image locally from obj_info + image_url; 4. FE framework automatically rebuilds metadata from the Image

6.3 FE Image Upload Five-Step Workflow

Step 1: beginSnapshot(cloud_unique_id, label, timeout=600, ttl)
  → Obtain snapshot_id, image_url, obj_info

Step 2: Catalog.checkpoint()
  → Obtain last_journal_id, image file path

Step 3: S3 Multipart Upload Initialization
  → Create S3Client based on obj_info
  → initiateMultipartUpload(image_url + "image.xxx")
  → Obtain upload_id

Step 4: updateSnapshot(snapshot_id, upload_file, upload_id)
  → Record upload info for fault recovery

Step 5: Chunked Upload
  → Split by multi_part_upload_part_size_in_bytes (256MB)
  → Parallel upload (pool size = multi_part_upload_pool_size)
  → completeMultipartUpload

Step 6: commitSnapshot(snapshot_id, image_url, last_journal_id, meta_size, data_size)
  → Snapshot complete

6.4 Auto-Snapshot Trigger Decision Flow

flowchart TD
    A["runAfterCatalogReady() triggered every interval seconds"] --> B{Env.isMaster?}
    B -- No --> Z["Skip"]
    B -- Yes --> C["getSnapshotProperties()"]
    C --> D{switch_status == ON?}
    D -- No --> Z
    D -- Yes --> E["listSnapshot(false) get NORMAL list"]
    E --> F{NORMAL list empty?}
    F -- Yes --> G["Trigger snapshot immediately"]
    F -- No --> H{now - latest.finish_at >= interval?}
    H -- No --> I["Check PREPARE timeout"]
    H -- Yes --> J{NORMAL count >= max_reserved?}
    J -- No --> G
    J -- Yes --> K["Recycle oldest NORMAL snapshot"]
    K --> G
    I --> L{PREPARE timed out?}
    L -- Yes --> M["abortSnapshot + trigger new snapshot"]
    L -- No --> Z
    G --> N["submitJob(auto_ttl, auto_label)"]
Loading

6.5 Configuration Injection

The FE side needs to update the cloud_snapshot_handler_class default value in Config.java (or configure via fe.conf):

# fe.conf
cloud_snapshot_handler_class = org.apache.doris.cloud.snapshot.DorisCloudSnapshotHandler

7. Recycling & Cleanup Mechanism

7.1 Recycling Trigger Conditions

Condition Judgment Logic Action
TTL expired (manual snapshot) now > snapshot.create_at + snapshot.ttl_seconds Mark RECYCLED
Exceeds max retention (auto snapshot) NORMAL count > instance.max_reserved_snapshot Mark oldest NORMAL as RECYCLED
Already marked RECYCLED snapshot.status == SNAPSHOT_RECYCLED Execute actual cleanup
ABORTED state snapshot.status == SNAPSHOT_ABORTED Clean up residual uploads + delete KV
PREPARE timeout now > snapshot.create_at + snapshot.timeout_seconds Mark ABORTED

7.2 Cleanup Workflow

flowchart TD
    A["Recycler background instance scan"] --> B["InstanceRecycler.recycle()"]
    B --> C["DorisSnapshotManager.recycle_snapshots(recycler)"]
    C --> D["Range scan TxnKv: snapshot_key_prefix(instance_id)"]
    D --> E{Iterate each SnapshotPB}
    E --> F{TTL expired or PREPARE timeout?}
    F -- Yes --> G["Mark status as RECYCLED/ABORTED"]
    F -- No --> H{Status == RECYCLED or ABORTED?}
    H -- Yes --> I["recycle_snapshot_meta_and_data()"]
    I --> J["accessor->delete_directory(image_url)"]
    I --> K["Delete snapshot_full_key"]
    I --> L["Delete associated snapshot_reference_keys"]
    H -- No --> M{NORMAL count > max_reserved?}
    M -- Yes --> N["Mark oldest NORMAL as RECYCLED"]
    N --> I
    M -- No --> O["Skip"]
    E --> P{Continue to next?}
    P -- Yes --> E
    P -- No --> Q["Done"]
Loading

8. Consistency Verification Design

8.1 Forward Verification (check_snapshots)

Goal: Ensure all NORMAL-state SnapshotPBs in TxnKv have corresponding files in object storage.

for each SnapshotPB where status == NORMAL:
    if accessor->exists(snapshot.image_url) != 0:
        LOG(ERROR) << "Snapshot " << snapshot_id << " image not found at " << image_url
        error_count++

8.2 Inverse Verification (inverted_check_snapshots)

Goal: Ensure all files under the snapshot path in object storage have corresponding SnapshotPB metadata.

accessor->list_directory("snapshot/")
for each file in listing:
    extract snapshot_id from path
    if read_snapshot_pb(instance_id, parse_versionstamp(snapshot_id)) fails:
        LOG(ERROR) << "Orphan snapshot file: " << file.path
        orphan_count++

8.3 MVCC Metadata Verification (check_mvcc_meta_key / inverted_check_mvcc_meta_key)

Verifies consistency of multi-version keys (0x03 keyspace) for partition/tablet/rowset metadata across the snapshot chain.


9. Injection Point Modification Checklist

9.1 meta_server.cpp Modification

// Before:
auto snapshot_mgr = std::make_shared<SnapshotManager>(txn_kv_);

// After:
auto snapshot_mgr = std::make_shared<DorisSnapshotManager>(txn_kv_);

9.2 recycler.cpp Modification

// Before:
snapshot_manager_ = std::make_shared<SnapshotManager>(txn_kv_);

// After:
snapshot_manager_ = std::make_shared<DorisSnapshotManager>(txn_kv_);

9.3 CMakeLists.txt Modification

Add new source file in cloud/CMakeLists.txt:

set(SNAPSHOT_SRCS
    src/snapshot/snapshot_manager.cpp
    src/snapshot/doris_snapshot_manager.cpp  # New
)

10. File Change Manifest

10.1 New C++ Files

File Description
cloud/src/snapshot/doris_snapshot_manager.h DorisSnapshotManager class definition
cloud/src/snapshot/doris_snapshot_manager.cpp All 18 method implementations
cloud/test/doris_snapshot_manager_test.cpp Complete unit tests

10.2 Modified C++ Files

File Modification
cloud/src/meta-service/meta_server.cpp L82: SnapshotManagerDorisSnapshotManager
cloud/src/recycler/recycler.cpp L594: SnapshotManagerDorisSnapshotManager
cloud/CMakeLists.txt Add doris_snapshot_manager.cpp to build list

10.3 New Java Files

File Description
fe/fe-core/src/main/java/org/apache/doris/cloud/snapshot/DorisCloudSnapshotHandler.java Handler implementation
fe/fe-core/src/test/java/org/apache/doris/cloud/snapshot/DorisCloudSnapshotHandlerTest.java Unit tests

10.4 Modified Java Files

File Modification
fe/fe-common/src/main/java/org/apache/doris/common/Config.java Update cloud_snapshot_handler_class default value

10.5 Test Files

File Description
cloud/test/doris_snapshot_manager_test.cpp C++ unit tests (all methods)
cloud/test/meta_service_snapshot_test.cpp Enable and extend existing DISABLED tests
regression-test/suites/cloud_p0/snapshot/ Groovy integration tests

Use case

No response

Related issues

No response

Are you willing to submit PR?

  • Yes I am willing to submit a PR!

Code of Conduct

Metadata

Metadata

Assignees

No one assigned

    Labels

    kind/featureCategorizes issue or PR as related to a new feature.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions