rust_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 rust_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 = rust_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    pub fn restore_from_latest_backup<D: AsRef<Path>, W: AsRef<Path>>(
138        &mut self,
139        db_dir: D,
140        wal_dir: W,
141        opts: &RestoreOptions,
142    ) -> Result<(), Error> {
143        let c_db_dir = to_cpath(db_dir)?;
144        let c_wal_dir = to_cpath(wal_dir)?;
145
146        unsafe {
147            ffi_try!(ffi::rocksdb_backup_engine_restore_db_from_latest_backup(
148                self.inner,
149                c_db_dir.as_ptr(),
150                c_wal_dir.as_ptr(),
151                opts.inner,
152            ));
153        }
154        Ok(())
155    }
156
157    /// Restore from a specified backup
158    ///
159    /// The specified backup id should be passed in as an additional parameter.
160    pub fn restore_from_backup<D: AsRef<Path>, W: AsRef<Path>>(
161        &mut self,
162        db_dir: D,
163        wal_dir: W,
164        opts: &RestoreOptions,
165        backup_id: u32,
166    ) -> Result<(), Error> {
167        let c_db_dir = to_cpath(db_dir)?;
168        let c_wal_dir = to_cpath(wal_dir)?;
169
170        unsafe {
171            ffi_try!(ffi::rocksdb_backup_engine_restore_db_from_backup(
172                self.inner,
173                c_db_dir.as_ptr(),
174                c_wal_dir.as_ptr(),
175                opts.inner,
176                backup_id,
177            ));
178        }
179        Ok(())
180    }
181
182    /// Checks that each file exists and that the size of the file matches our
183    /// expectations. it does not check file checksum.
184    ///
185    /// If this BackupEngine created the backup, it compares the files' current
186    /// sizes against the number of bytes written to them during creation.
187    /// Otherwise, it compares the files' current sizes against their sizes when
188    /// the BackupEngine was opened.
189    pub fn verify_backup(&self, backup_id: u32) -> Result<(), Error> {
190        unsafe {
191            ffi_try!(ffi::rocksdb_backup_engine_verify_backup(
192                self.inner, backup_id,
193            ));
194        }
195        Ok(())
196    }
197
198    /// Get a list of all backups together with information on timestamp of the backup
199    /// and the size (please note that sum of all backups' sizes is bigger than the actual
200    /// size of the backup directory because some data is shared by multiple backups).
201    /// Backups are identified by their always-increasing IDs.
202    ///
203    /// You can perform this function safely, even with other BackupEngine performing
204    /// backups on the same directory
205    pub fn get_backup_info(&self) -> Vec<BackupEngineInfo> {
206        unsafe {
207            let i = ffi::rocksdb_backup_engine_get_backup_info(self.inner);
208
209            let n = ffi::rocksdb_backup_engine_info_count(i);
210
211            let mut info = Vec::with_capacity(n as usize);
212            for index in 0..n {
213                info.push(BackupEngineInfo {
214                    timestamp: ffi::rocksdb_backup_engine_info_timestamp(i, index),
215                    backup_id: ffi::rocksdb_backup_engine_info_backup_id(i, index),
216                    size: ffi::rocksdb_backup_engine_info_size(i, index),
217                    num_files: ffi::rocksdb_backup_engine_info_number_files(i, index),
218                });
219            }
220
221            // destroy backup info object
222            ffi::rocksdb_backup_engine_info_destroy(i);
223
224            info
225        }
226    }
227}
228
229impl BackupEngineOptions {
230    /// Initializes `BackupEngineOptions` with the directory to be used for storing/accessing the
231    /// backup files.
232    pub fn new<P: AsRef<Path>>(backup_dir: P) -> Result<Self, Error> {
233        let backup_dir = backup_dir.as_ref();
234        let c_backup_dir = CString::new(backup_dir.to_string_lossy().as_bytes()).map_err(|_| {
235            Error::new(
236                "Failed to convert backup_dir to CString \
237                     when constructing BackupEngineOptions"
238                    .to_owned(),
239            )
240        })?;
241
242        unsafe {
243            let opts = ffi::rocksdb_backup_engine_options_create(c_backup_dir.as_ptr());
244            assert!(!opts.is_null(), "Could not create RocksDB backup options");
245
246            Ok(Self { inner: opts })
247        }
248    }
249
250    /// Sets the number of operations (such as file copies or file checksums) that `RocksDB` may
251    /// perform in parallel when executing a backup or restore.
252    ///
253    /// Default: 1
254    pub fn set_max_background_operations(&mut self, max_background_operations: i32) {
255        unsafe {
256            ffi::rocksdb_backup_engine_options_set_max_background_operations(
257                self.inner,
258                max_background_operations,
259            );
260        }
261    }
262
263    /// Sets whether to use fsync(2) to sync file data and metadata to disk after every file write,
264    /// guaranteeing that backups will be consistent after a reboot or if machine crashes. Setting
265    /// it to false will speed things up a bit, but some (newer) backups might be inconsistent. In
266    /// most cases, everything should be fine, though.
267    ///
268    /// Default: true
269    ///
270    /// Documentation: <https://github.com/facebook/rocksdb/wiki/How-to-backup-RocksDB#advanced-usage>
271    pub fn set_sync(&mut self, sync: bool) {
272        unsafe {
273            ffi::rocksdb_backup_engine_options_set_sync(self.inner, c_uchar::from(sync));
274        }
275    }
276
277    /// Returns the value of the `sync` option.
278    pub fn get_sync(&mut self) -> bool {
279        let val_u8 = unsafe { ffi::rocksdb_backup_engine_options_get_sync(self.inner) };
280        val_u8 != 0
281    }
282}
283
284impl RestoreOptions {
285    /// Sets `keep_log_files`. If true, restore won't overwrite the existing log files in wal_dir.
286    /// It will also move all log files from archive directory to wal_dir. Use this option in
287    /// combination with BackupEngineOptions::backup_log_files = false for persisting in-memory
288    /// databases.
289    ///
290    /// Default: false
291    pub fn set_keep_log_files(&mut self, keep_log_files: bool) {
292        unsafe {
293            ffi::rocksdb_restore_options_set_keep_log_files(self.inner, i32::from(keep_log_files));
294        }
295    }
296}
297
298impl Default for RestoreOptions {
299    fn default() -> Self {
300        unsafe {
301            let opts = ffi::rocksdb_restore_options_create();
302            assert!(!opts.is_null(), "Could not create RocksDB restore options");
303
304            Self { inner: opts }
305        }
306    }
307}
308
309impl Drop for BackupEngine {
310    fn drop(&mut self) {
311        unsafe {
312            ffi::rocksdb_backup_engine_close(self.inner);
313        }
314    }
315}
316
317impl Drop for BackupEngineOptions {
318    fn drop(&mut self) {
319        unsafe {
320            ffi::rocksdb_backup_engine_options_destroy(self.inner);
321        }
322    }
323}
324
325impl Drop for RestoreOptions {
326    fn drop(&mut self) {
327        unsafe {
328            ffi::rocksdb_restore_options_destroy(self.inner);
329        }
330    }
331}
332
333#[cfg(test)]
334mod tests {
335    use super::BackupEngineOptions;
336
337    #[test]
338    fn test_sync() {
339        let dir = tempfile::Builder::new()
340            .prefix("rocksdb-test-sync")
341            .tempdir()
342            .expect("Failed to create temporary path for db.");
343
344        let mut opts = BackupEngineOptions::new(dir.path()).unwrap();
345        assert!(opts.get_sync());
346        opts.set_sync(false);
347        assert!(!opts.get_sync());
348    }
349}