Skip to main content

ckb_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::{DB, Error, ffi};
17
18use libc::{c_int, c_uchar};
19use std::ffi::CString;
20use std::path::Path;
21
22/// Represents information of a backup including timestamp of the backup
23/// and the size (please note that sum of all backups' sizes is bigger than the actual
24/// size of the backup directory because some data is shared by multiple backups).
25/// Backups are identified by their always-increasing IDs.
26pub struct BackupEngineInfo {
27    /// Timestamp of the backup
28    pub timestamp: i64,
29    /// ID of the backup
30    pub backup_id: u32,
31    /// Size of the backup
32    pub size: u64,
33    /// Number of files related to the backup
34    pub num_files: u32,
35}
36
37pub struct BackupEngine {
38    inner: *mut ffi::rocksdb_backup_engine_t,
39}
40
41pub struct BackupEngineOptions {
42    inner: *mut ffi::rocksdb_options_t,
43}
44
45pub struct RestoreOptions {
46    inner: *mut ffi::rocksdb_restore_options_t,
47}
48
49impl BackupEngine {
50    /// Open a backup engine with the specified options.
51    pub fn open<P: AsRef<Path>>(opts: &BackupEngineOptions, path: P) -> Result<Self, Error> {
52        let path = path.as_ref();
53        let cpath = if let Ok(e) = CString::new(path.to_string_lossy().as_bytes()) {
54            e
55        } else {
56            return Err(Error::new(
57                "Failed to convert path to CString \
58                     when opening backup engine"
59                    .to_owned(),
60            ));
61        };
62
63        let be: *mut ffi::rocksdb_backup_engine_t;
64        unsafe {
65            be = ffi_try!(ffi::rocksdb_backup_engine_open(opts.inner, cpath.as_ptr()));
66        }
67
68        if be.is_null() {
69            return Err(Error::new("Could not initialize backup engine.".to_owned()));
70        }
71
72        Ok(Self { inner: be })
73    }
74
75    /// Captures the state of the database in the latest backup.
76    ///
77    /// Note: no flush before backup is performed. User might want to
78    /// use `create_new_backup_flush` instead.
79    pub fn create_new_backup(&mut self, db: &DB) -> Result<(), Error> {
80        self.create_new_backup_flush(db, false)
81    }
82
83    /// Captures the state of the database in the latest backup.
84    ///
85    /// Set flush_before_backup=true to avoid losing unflushed key/value
86    /// pairs from the memtable.
87    pub fn create_new_backup_flush(
88        &mut self,
89        db: &DB,
90        flush_before_backup: bool,
91    ) -> Result<(), Error> {
92        unsafe {
93            ffi_try!(ffi::rocksdb_backup_engine_create_new_backup_flush(
94                self.inner,
95                db.inner,
96                flush_before_backup as c_uchar,
97            ));
98            Ok(())
99        }
100    }
101
102    pub fn purge_old_backups(&mut self, num_backups_to_keep: usize) -> Result<(), Error> {
103        unsafe {
104            ffi_try!(ffi::rocksdb_backup_engine_purge_old_backups(
105                self.inner,
106                num_backups_to_keep as u32,
107            ));
108            Ok(())
109        }
110    }
111
112    /// Restore from the latest backup
113    ///
114    /// # Arguments
115    ///
116    /// * `db_dir` - A path to the database directory
117    /// * `wal_dir` - A path to the wal directory
118    /// * `opts` - Restore options
119    ///
120    /// # Examples
121    ///
122    /// ```ignore
123    /// use rocksdb::backup::{BackupEngine, BackupEngineOptions};
124    /// let backup_opts = BackupEngineOptions::default();
125    /// let mut backup_engine = BackupEngine::open(&backup_opts, &backup_path).unwrap();
126    /// let mut restore_option = rocksdb::backup::RestoreOptions::default();
127    /// restore_option.set_keep_log_files(true); /// true to keep log files
128    /// if let Err(e) = backup_engine.restore_from_latest_backup(&db_path, &wal_dir, &restore_option) {
129    ///     error!("Failed to restore from the backup. Error:{:?}", e);
130    ///     return Err(e.to_string());
131    ///  }
132    /// ```
133    pub fn restore_from_latest_backup<D: AsRef<Path>, W: AsRef<Path>>(
134        &mut self,
135        db_dir: D,
136        wal_dir: W,
137        opts: &RestoreOptions,
138    ) -> Result<(), Error> {
139        let db_dir = db_dir.as_ref();
140        let c_db_dir = if let Ok(c) = CString::new(db_dir.to_string_lossy().as_bytes()) {
141            c
142        } else {
143            return Err(Error::new(
144                "Failed to convert db_dir to CString \
145                     when restoring from latest backup"
146                    .to_owned(),
147            ));
148        };
149
150        let wal_dir = wal_dir.as_ref();
151        let c_wal_dir = if let Ok(c) = CString::new(wal_dir.to_string_lossy().as_bytes()) {
152            c
153        } else {
154            return Err(Error::new(
155                "Failed to convert wal_dir to CString \
156                     when restoring from latest backup"
157                    .to_owned(),
158            ));
159        };
160
161        unsafe {
162            ffi_try!(ffi::rocksdb_backup_engine_restore_db_from_latest_backup(
163                self.inner,
164                c_db_dir.as_ptr(),
165                c_wal_dir.as_ptr(),
166                opts.inner,
167            ));
168        }
169        Ok(())
170    }
171
172    /// Restore from a specified backup
173    ///
174    /// The specified backup id should be passed in as an additional parameter.
175    pub fn restore_from_backup<D: AsRef<Path>, W: AsRef<Path>>(
176        &mut self,
177        db_dir: D,
178        wal_dir: W,
179        opts: &RestoreOptions,
180        backup_id: u32,
181    ) -> Result<(), Error> {
182        let db_dir = db_dir.as_ref();
183        let c_db_dir = if let Ok(c) = CString::new(db_dir.to_string_lossy().as_bytes()) {
184            c
185        } else {
186            return Err(Error::new(
187                "Failed to convert db_dir to CString \
188                     when restoring from latest backup"
189                    .to_owned(),
190            ));
191        };
192
193        let wal_dir = wal_dir.as_ref();
194        let c_wal_dir = if let Ok(c) = CString::new(wal_dir.to_string_lossy().as_bytes()) {
195            c
196        } else {
197            return Err(Error::new(
198                "Failed to convert wal_dir to CString \
199                     when restoring from latest backup"
200                    .to_owned(),
201            ));
202        };
203
204        unsafe {
205            ffi_try!(ffi::rocksdb_backup_engine_restore_db_from_backup(
206                self.inner,
207                c_db_dir.as_ptr(),
208                c_wal_dir.as_ptr(),
209                opts.inner,
210                backup_id,
211            ));
212        }
213        Ok(())
214    }
215
216    /// Checks that each file exists and that the size of the file matches our
217    /// expectations. it does not check file checksum.
218    ///
219    /// If this BackupEngine created the backup, it compares the files' current
220    /// sizes against the number of bytes written to them during creation.
221    /// Otherwise, it compares the files' current sizes against their sizes when
222    /// the BackupEngine was opened.
223    pub fn verify_backup(&self, backup_id: u32) -> Result<(), Error> {
224        unsafe {
225            ffi_try!(ffi::rocksdb_backup_engine_verify_backup(
226                self.inner, backup_id,
227            ));
228        }
229        Ok(())
230    }
231
232    /// Get a list of all backups together with information on timestamp of the backup
233    /// and the size (please note that sum of all backups' sizes is bigger than the actual
234    /// size of the backup directory because some data is shared by multiple backups).
235    /// Backups are identified by their always-increasing IDs.
236    ///
237    /// You can perform this function safely, even with other BackupEngine performing
238    /// backups on the same directory
239    pub fn get_backup_info(&self) -> Vec<BackupEngineInfo> {
240        unsafe {
241            let i = ffi::rocksdb_backup_engine_get_backup_info(self.inner);
242
243            let n = ffi::rocksdb_backup_engine_info_count(i);
244
245            let mut info = Vec::with_capacity(n as usize);
246            for index in 0..n {
247                info.push(BackupEngineInfo {
248                    timestamp: ffi::rocksdb_backup_engine_info_timestamp(i, index),
249                    backup_id: ffi::rocksdb_backup_engine_info_backup_id(i, index),
250                    size: ffi::rocksdb_backup_engine_info_size(i, index),
251                    num_files: ffi::rocksdb_backup_engine_info_number_files(i, index),
252                });
253            }
254
255            // destroy backup info object
256            ffi::rocksdb_backup_engine_info_destroy(i);
257
258            info
259        }
260    }
261}
262
263impl BackupEngineOptions {
264    //
265}
266
267impl RestoreOptions {
268    pub fn set_keep_log_files(&mut self, keep_log_files: bool) {
269        unsafe {
270            ffi::rocksdb_restore_options_set_keep_log_files(self.inner, keep_log_files as c_int);
271        }
272    }
273}
274
275impl Default for BackupEngineOptions {
276    fn default() -> Self {
277        unsafe {
278            let opts = ffi::rocksdb_options_create();
279            if opts.is_null() {
280                panic!("Could not create RocksDB backup options");
281            }
282            Self { inner: opts }
283        }
284    }
285}
286
287impl Default for RestoreOptions {
288    fn default() -> Self {
289        unsafe {
290            let opts = ffi::rocksdb_restore_options_create();
291            if opts.is_null() {
292                panic!("Could not create RocksDB restore options");
293            }
294            Self { inner: opts }
295        }
296    }
297}
298
299impl Drop for BackupEngine {
300    fn drop(&mut self) {
301        unsafe {
302            ffi::rocksdb_backup_engine_close(self.inner);
303        }
304    }
305}
306
307impl Drop for BackupEngineOptions {
308    fn drop(&mut self) {
309        unsafe {
310            ffi::rocksdb_options_destroy(self.inner);
311        }
312    }
313}
314
315impl Drop for RestoreOptions {
316    fn drop(&mut self) {
317        unsafe {
318            ffi::rocksdb_restore_options_destroy(self.inner);
319        }
320    }
321}
322
323#[test]
324fn restore_from_latest() {
325    use crate::TemporaryDBPath;
326    use crate::ops::{Get, Open, Put};
327
328    // create backup
329    let path = TemporaryDBPath::new();
330    let restore_path = TemporaryDBPath::new();
331    {
332        let db = DB::open_default(&path).unwrap();
333        assert!(db.put(b"k1", b"v1111").is_ok());
334        let value = db.get(b"k1");
335        assert_eq!(value.unwrap().unwrap().to_utf8().unwrap(), "v1111");
336        {
337            let backup_path = TemporaryDBPath::new();
338            let backup_opts = BackupEngineOptions::default();
339            let mut backup_engine = BackupEngine::open(&backup_opts, &backup_path).unwrap();
340            assert!(backup_engine.create_new_backup(&db).is_ok());
341
342            // check backup info
343            let info = backup_engine.get_backup_info();
344            assert!(!info.is_empty());
345            info.iter().for_each(|i| {
346                assert!(backup_engine.verify_backup(i.backup_id).is_ok());
347                assert!(i.size > 0);
348            });
349
350            let mut restore_option = RestoreOptions::default();
351            restore_option.set_keep_log_files(false); // true to keep log files
352            let restore_status = backup_engine.restore_from_latest_backup(
353                &restore_path,
354                &restore_path,
355                &restore_option,
356            );
357            assert!(restore_status.is_ok());
358
359            let db_restore = DB::open_default(&restore_path).unwrap();
360            let value = db_restore.get(b"k1");
361            assert_eq!(value.unwrap().unwrap().to_utf8().unwrap(), "v1111");
362        }
363    }
364}
365
366#[test]
367fn restore_from_backup() {
368    use crate::TemporaryDBPath;
369    use crate::ops::{Get, Open, Put};
370
371    // create backup
372    let path = TemporaryDBPath::new();
373    let restore_path = TemporaryDBPath::new();
374    {
375        let db = DB::open_default(&path).unwrap();
376        assert!(db.put(b"k1", b"v1111").is_ok());
377        let value = db.get(b"k1");
378        assert_eq!(value.unwrap().unwrap().to_utf8().unwrap(), "v1111");
379        {
380            let backup_path = TemporaryDBPath::new();
381            let backup_opts = BackupEngineOptions::default();
382            let mut backup_engine = BackupEngine::open(&backup_opts, &backup_path).unwrap();
383            assert!(backup_engine.create_new_backup(&db).is_ok());
384
385            // check backup info
386            let info = backup_engine.get_backup_info();
387            assert!(!info.is_empty());
388            info.iter().for_each(|i| {
389                assert!(backup_engine.verify_backup(i.backup_id).is_ok());
390                assert!(i.size > 0);
391            });
392
393            let backup_id = info.first().unwrap().backup_id;
394            let mut restore_option = RestoreOptions::default();
395            restore_option.set_keep_log_files(false); // true to keep log files
396            let restore_status = backup_engine.restore_from_backup(
397                &restore_path,
398                &restore_path,
399                &restore_option,
400                backup_id,
401            );
402            assert!(restore_status.is_ok());
403
404            let db_restore = DB::open_default(&restore_path).unwrap();
405            let value = db_restore.get(b"k1");
406            assert_eq!(value.unwrap().unwrap().to_utf8().unwrap(), "v1111");
407        }
408    }
409}