sfzhou_rocksdb/backup.rs
1// Copyright 2016 Alex Regueiro
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14//
15
16use crate::env::Env;
17use crate::{db::DBInner, ffi, ffi_util::to_cpath, DBCommon, Error, ThreadMode};
18
19use libc::c_uchar;
20use std::ffi::CString;
21use std::path::Path;
22
23/// Represents information of a backup including timestamp of the backup
24/// and the size (please note that sum of all backups' sizes is bigger than the actual
25/// size of the backup directory because some data is shared by multiple backups).
26/// Backups are identified by their always-increasing IDs.
27pub struct BackupEngineInfo {
28 /// Timestamp of the backup
29 pub timestamp: i64,
30 /// ID of the backup
31 pub backup_id: u32,
32 /// Size of the backup
33 pub size: u64,
34 /// Number of files related to the backup
35 pub num_files: u32,
36}
37
38pub struct BackupEngine {
39 inner: *mut ffi::rocksdb_backup_engine_t,
40 _outlive: Env,
41}
42
43pub struct BackupEngineOptions {
44 inner: *mut ffi::rocksdb_backup_engine_options_t,
45}
46
47pub struct RestoreOptions {
48 inner: *mut ffi::rocksdb_restore_options_t,
49}
50
51// BackupEngine is a simple pointer wrapper, so it's safe to send to another thread
52// since the underlying RocksDB backup engine is thread-safe.
53unsafe impl Send for BackupEngine {}
54
55impl BackupEngine {
56 /// Open a backup engine with the specified options and RocksDB Env.
57 pub fn open(opts: &BackupEngineOptions, env: &Env) -> Result<Self, Error> {
58 let be: *mut ffi::rocksdb_backup_engine_t;
59 unsafe {
60 be = ffi_try!(ffi::rocksdb_backup_engine_open_opts(
61 opts.inner,
62 env.0.inner
63 ));
64 }
65
66 if be.is_null() {
67 return Err(Error::new("Could not initialize backup engine.".to_owned()));
68 }
69
70 Ok(Self {
71 inner: be,
72 _outlive: env.clone(),
73 })
74 }
75
76 /// Captures the state of the database in the latest backup.
77 ///
78 /// Note: no flush before backup is performed. User might want to
79 /// use `create_new_backup_flush` instead.
80 pub fn create_new_backup<T: ThreadMode, D: DBInner>(
81 &mut self,
82 db: &DBCommon<T, D>,
83 ) -> Result<(), Error> {
84 self.create_new_backup_flush(db, false)
85 }
86
87 /// Captures the state of the database in the latest backup.
88 ///
89 /// Set flush_before_backup=true to avoid losing unflushed key/value
90 /// pairs from the memtable.
91 pub fn create_new_backup_flush<T: ThreadMode, D: DBInner>(
92 &mut self,
93 db: &DBCommon<T, D>,
94 flush_before_backup: bool,
95 ) -> Result<(), Error> {
96 unsafe {
97 ffi_try!(ffi::rocksdb_backup_engine_create_new_backup_flush(
98 self.inner,
99 db.inner.inner(),
100 c_uchar::from(flush_before_backup),
101 ));
102 Ok(())
103 }
104 }
105
106 pub fn purge_old_backups(&mut self, num_backups_to_keep: usize) -> Result<(), Error> {
107 unsafe {
108 ffi_try!(ffi::rocksdb_backup_engine_purge_old_backups(
109 self.inner,
110 num_backups_to_keep as u32,
111 ));
112 Ok(())
113 }
114 }
115
116 /// Restore from the latest backup
117 ///
118 /// # Arguments
119 ///
120 /// * `db_dir` - A path to the database directory
121 /// * `wal_dir` - A path to the wal directory
122 /// * `opts` - Restore options
123 ///
124 /// # Examples
125 ///
126 /// ```ignore
127 /// use sfzhou_rocksdb::backup::{BackupEngine, BackupEngineOptions};
128 /// let backup_opts = BackupEngineOptions::default();
129 /// let mut backup_engine = BackupEngine::open(&backup_opts, &backup_path).unwrap();
130 /// let mut restore_option = sfzhou_rocksdb::backup::RestoreOptions::default();
131 /// restore_option.set_keep_log_files(true); /// true to keep log files
132 /// if let Err(e) = backup_engine.restore_from_latest_backup(&db_path, &wal_dir, &restore_option) {
133 /// error!("Failed to restore from the backup. Error:{:?}", e);
134 /// return Err(e.to_string());
135 /// }
136 /// ```
137
138 pub fn restore_from_latest_backup<D: AsRef<Path>, W: AsRef<Path>>(
139 &mut self,
140 db_dir: D,
141 wal_dir: W,
142 opts: &RestoreOptions,
143 ) -> Result<(), Error> {
144 let c_db_dir = to_cpath(db_dir)?;
145 let c_wal_dir = to_cpath(wal_dir)?;
146
147 unsafe {
148 ffi_try!(ffi::rocksdb_backup_engine_restore_db_from_latest_backup(
149 self.inner,
150 c_db_dir.as_ptr(),
151 c_wal_dir.as_ptr(),
152 opts.inner,
153 ));
154 }
155 Ok(())
156 }
157
158 /// Restore from a specified backup
159 ///
160 /// The specified backup id should be passed in as an additional parameter.
161 pub fn restore_from_backup<D: AsRef<Path>, W: AsRef<Path>>(
162 &mut self,
163 db_dir: D,
164 wal_dir: W,
165 opts: &RestoreOptions,
166 backup_id: u32,
167 ) -> Result<(), Error> {
168 let c_db_dir = to_cpath(db_dir)?;
169 let c_wal_dir = to_cpath(wal_dir)?;
170
171 unsafe {
172 ffi_try!(ffi::rocksdb_backup_engine_restore_db_from_backup(
173 self.inner,
174 c_db_dir.as_ptr(),
175 c_wal_dir.as_ptr(),
176 opts.inner,
177 backup_id,
178 ));
179 }
180 Ok(())
181 }
182
183 /// Checks that each file exists and that the size of the file matches our
184 /// expectations. it does not check file checksum.
185 ///
186 /// If this BackupEngine created the backup, it compares the files' current
187 /// sizes against the number of bytes written to them during creation.
188 /// Otherwise, it compares the files' current sizes against their sizes when
189 /// the BackupEngine was opened.
190 pub fn verify_backup(&self, backup_id: u32) -> Result<(), Error> {
191 unsafe {
192 ffi_try!(ffi::rocksdb_backup_engine_verify_backup(
193 self.inner, backup_id,
194 ));
195 }
196 Ok(())
197 }
198
199 /// Get a list of all backups together with information on timestamp of the backup
200 /// and the size (please note that sum of all backups' sizes is bigger than the actual
201 /// size of the backup directory because some data is shared by multiple backups).
202 /// Backups are identified by their always-increasing IDs.
203 ///
204 /// You can perform this function safely, even with other BackupEngine performing
205 /// backups on the same directory
206 pub fn get_backup_info(&self) -> Vec<BackupEngineInfo> {
207 unsafe {
208 let i = ffi::rocksdb_backup_engine_get_backup_info(self.inner);
209
210 let n = ffi::rocksdb_backup_engine_info_count(i);
211
212 let mut info = Vec::with_capacity(n as usize);
213 for index in 0..n {
214 info.push(BackupEngineInfo {
215 timestamp: ffi::rocksdb_backup_engine_info_timestamp(i, index),
216 backup_id: ffi::rocksdb_backup_engine_info_backup_id(i, index),
217 size: ffi::rocksdb_backup_engine_info_size(i, index),
218 num_files: ffi::rocksdb_backup_engine_info_number_files(i, index),
219 });
220 }
221
222 // destroy backup info object
223 ffi::rocksdb_backup_engine_info_destroy(i);
224
225 info
226 }
227 }
228}
229
230impl BackupEngineOptions {
231 /// Initializes `BackupEngineOptions` with the directory to be used for storing/accessing the
232 /// backup files.
233 pub fn new<P: AsRef<Path>>(backup_dir: P) -> Result<Self, Error> {
234 let backup_dir = backup_dir.as_ref();
235 let c_backup_dir = CString::new(backup_dir.to_string_lossy().as_bytes()).map_err(|_| {
236 Error::new(
237 "Failed to convert backup_dir to CString \
238 when constructing BackupEngineOptions"
239 .to_owned(),
240 )
241 })?;
242
243 unsafe {
244 let opts = ffi::rocksdb_backup_engine_options_create(c_backup_dir.as_ptr());
245 assert!(!opts.is_null(), "Could not create RocksDB backup options");
246
247 Ok(Self { inner: opts })
248 }
249 }
250
251 /// Sets the number of operations (such as file copies or file checksums) that `RocksDB` may
252 /// perform in parallel when executing a backup or restore.
253 ///
254 /// Default: 1
255 pub fn set_max_background_operations(&mut self, max_background_operations: i32) {
256 unsafe {
257 ffi::rocksdb_backup_engine_options_set_max_background_operations(
258 self.inner,
259 max_background_operations,
260 );
261 }
262 }
263}
264
265impl RestoreOptions {
266 /// Sets `keep_log_files`. If true, restore won't overwrite the existing log files in wal_dir.
267 /// It will also move all log files from archive directory to wal_dir. Use this option in
268 /// combination with BackupEngineOptions::backup_log_files = false for persisting in-memory
269 /// databases.
270 ///
271 /// Default: false
272 pub fn set_keep_log_files(&mut self, keep_log_files: bool) {
273 unsafe {
274 ffi::rocksdb_restore_options_set_keep_log_files(self.inner, i32::from(keep_log_files));
275 }
276 }
277}
278
279impl Default for RestoreOptions {
280 fn default() -> Self {
281 unsafe {
282 let opts = ffi::rocksdb_restore_options_create();
283 assert!(!opts.is_null(), "Could not create RocksDB restore options");
284
285 Self { inner: opts }
286 }
287 }
288}
289
290impl Drop for BackupEngine {
291 fn drop(&mut self) {
292 unsafe {
293 ffi::rocksdb_backup_engine_close(self.inner);
294 }
295 }
296}
297
298impl Drop for BackupEngineOptions {
299 fn drop(&mut self) {
300 unsafe {
301 ffi::rocksdb_backup_engine_options_destroy(self.inner);
302 }
303 }
304}
305
306impl Drop for RestoreOptions {
307 fn drop(&mut self) {
308 unsafe {
309 ffi::rocksdb_restore_options_destroy(self.inner);
310 }
311 }
312}