Skip to content

Commit 95a57c6

Browse files
feat(raft): implement snapshot creation and restore (#255)
* feat(raft): implement snapshot creation and restore Implement core snapshot functionality to establish checkpoint → tar → restore * fix * feat(raft): implement snapshot install hot-swapping with ArcSwap Solves P0/P1 issues from PR #255 review: - P0: Active DB handle problem - use placeholder Storage swap to release old Storage before restoring checkpoint, avoiding RocksDB lock conflict - P0: Metadata validation - validate checkpoint metadata against SnapshotMeta - P1: Windows compatibility - atomic file replacement with cfg conditionals Key changes: - Add ArcSwap pattern: GlobalStorage wraps ArcSwap<Storage> for hot-swapping - StorageServerPauseController for coordinating pause during snapshot install - KiwiStateMachine.swap() atomically switches to new Storage after install - PauseController trait integration between StorageServer and RaftNode Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fmt * fix(storage): share ArcSwap across all components and deduplicate drop/close logic Address PR review comments: 1. GlobalStorage now uses Arc<ArcSwap<Storage>> so all clones share the same ArcSwap instance. Previously, clone() created a new ArcSwap, causing StorageServer and Raft to use separate containers - swap() in StateMachine was invisible to StorageServer. 2. Extract release_resources() private method to deduplicate cleanup logic between Drop::drop() and close(). Both now call this shared method, eliminating code redundancy. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 60cd512 commit 95a57c6

22 files changed

Lines changed: 1602 additions & 97 deletions

Cargo.lock

Lines changed: 65 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ rand = "0.8"
7474
uuid = { version = "1.0", features = ["v4", "serde"] }
7575
openraft = { version = "0.9", features = ["serde", "storage-v2"] }
7676
proptest = "1.5"
77+
arc-swap = "1.7"
7778

7879
## workspaces members
7980
engine = { path = "src/engine" }

src/common/runtime/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ storage = { workspace = true }
2121
bytes = { workspace = true }
2222
snafu = { workspace = true }
2323
chrono = { version = "0.4", features = ["serde"] }
24+
arc-swap = { workspace = true }
2425

2526
[dev-dependencies]
2627
tokio-test = "0.4"
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
// Copyright (c) 2024-present, arana-db Community. All rights reserved.
2+
//
3+
// Licensed to the Apache Software Foundation (ASF) under one or more
4+
// contributor license agreements. See the NOTICE file distributed with
5+
// this work for additional information regarding copyright ownership.
6+
// The ASF licenses this file to You under the Apache License, Version 2.0
7+
// (the "License"); you may not use this file except in compliance with
8+
// the License. You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing, software
13+
// distributed under the License is distributed on an "AS IS" BASIS,
14+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
// See the License for the specific language governing permissions and
16+
// limitations under the License.
17+
18+
//! Global Storage wrapper using ArcSwap for hot-swapping Storage during snapshot installation.
19+
//!
20+
//! This module provides `GlobalStorage` which wraps `ArcSwap<Storage>`. All components
21+
//! that need access to Storage should use `GlobalStorage::load()` to get the current
22+
//! instance. During snapshot installation, `swap()` atomically switches to the new Storage.
23+
24+
use std::sync::Arc;
25+
26+
use arc_swap::ArcSwap;
27+
use storage::storage::Storage;
28+
29+
/// Global storage wrapper that enables hot-swapping Storage during snapshot installation.
30+
///
31+
/// Uses `Arc<ArcSwap<Storage>>` internally so that cloning shares the same ArcSwap instance.
32+
/// All holders get the current Storage instance via `load()`, which is a cheap atomic read (~1-2ns).
33+
/// During snapshot installation, `swap()` atomically switches to the new Storage, and all
34+
/// clones see the update immediately.
35+
pub struct GlobalStorage {
36+
inner: Arc<ArcSwap<Storage>>,
37+
}
38+
39+
impl GlobalStorage {
40+
/// Create a new GlobalStorage with the initial Storage instance.
41+
pub fn new(storage: Storage) -> Self {
42+
Self {
43+
inner: Arc::new(ArcSwap::from(Arc::new(storage))),
44+
}
45+
}
46+
47+
/// Create from an existing Arc<Storage>.
48+
pub fn from_arc(storage: Arc<Storage>) -> Self {
49+
Self {
50+
inner: Arc::new(ArcSwap::from(storage)),
51+
}
52+
}
53+
54+
/// Get the current Storage instance.
55+
/// This is a cheap atomic read (~1-2ns overhead).
56+
pub fn load(&self) -> Arc<Storage> {
57+
self.inner.load_full()
58+
}
59+
60+
/// Swap to a new Storage instance atomically.
61+
/// Used during snapshot installation to switch to restored data.
62+
/// All clones of this GlobalStorage will see the new Storage immediately.
63+
pub fn swap(&self, new_storage: Arc<Storage>) {
64+
self.inner.swap(new_storage);
65+
}
66+
67+
/// Get the underlying Arc<ArcSwap<Storage>> for passing to Raft.
68+
/// Raft needs Arc<ArcSwap<Storage>> for KiwiStateMachine.
69+
pub fn arc_swap(&self) -> Arc<ArcSwap<Storage>> {
70+
Arc::clone(&self.inner)
71+
}
72+
73+
/// Get db_instance_num from current Storage.
74+
pub fn db_instance_num(&self) -> usize {
75+
self.load().db_instance_num
76+
}
77+
78+
/// Get db_id from current Storage.
79+
pub fn db_id(&self) -> usize {
80+
self.load().db_id
81+
}
82+
}
83+
84+
impl Clone for GlobalStorage {
85+
fn clone(&self) -> Self {
86+
Self {
87+
inner: Arc::clone(&self.inner), // Share the same ArcSwap!
88+
}
89+
}
90+
}

src/common/runtime/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
pub mod config;
2424
pub mod error;
2525
pub mod error_logging;
26+
pub mod global_storage;
2627
pub mod manager;
2728
pub mod message;
2829
pub mod metrics;
@@ -46,6 +47,7 @@ pub use error_logging::{
4647
CorrelationId, ErrorCategory, ErrorEvent, ErrorLogger, ErrorLoggingConfig, ErrorMetrics,
4748
ErrorRates, RuntimeContext, get_global_error_logger, init_global_error_logger,
4849
};
50+
pub use global_storage::GlobalStorage;
4951
pub use manager::{RuntimeHealth as ManagerRuntimeHealth, RuntimeManager, RuntimeStats};
5052
pub use message::{
5153
BackpressureConfig, ChannelStats, CircuitBreaker, MessageChannel, QueueStats, QueuedRequest,
@@ -63,5 +65,5 @@ pub use metrics::{
6365
};
6466
pub use storage_server::{
6567
BackgroundTaskConfig, BackgroundTaskManager, BackgroundTaskStats, BatchConfig, BatchProcessor,
66-
BatchStats, RocksDbStats, StorageServer, StorageServerConfig,
68+
BatchStats, RocksDbStats, StorageServer, StorageServerConfig, StorageServerPauseController,
6769
};

0 commit comments

Comments
 (0)