Skip to main content

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::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    _outlive: Env,
40}
41
42pub struct BackupEngineOptions {
43    inner: *mut ffi::rocksdb_backup_engine_options_t,
44}
45
46pub struct RestoreOptions {
47    inner: *mut ffi::rocksdb_restore_options_t,
48}
49
50// BackupEngine is a simple pointer wrapper, so it's safe to send to another thread
51// since the underlying RocksDB backup engine is thread-safe.
52unsafe impl Send for BackupEngine {}
53
54impl BackupEngine {
55    /// Open a backup engine with the specified options and RocksDB Env.
56    pub fn open(opts: &BackupEngineOptions, env: &Env) -> Result<Self, Error> {
57        let be: *mut ffi::rocksdb_backup_engine_t;
58        unsafe {
59            be = ffi_try!(ffi::rocksdb_backup_engine_open_opts(
60                opts.inner,
61                env.0.inner
62            ));
63        }
64
65        if be.is_null() {
66            return Err(Error::new("Could not initialize backup engine.".to_owned()));
67        }
68
69        Ok(Self {
70            inner: be,
71            _outlive: env.clone(),
72        })
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<T: ThreadMode, D: DBInner>(
80        &mut self,
81        db: &DBCommon<T, D>,
82    ) -> Result<(), Error> {
83        self.create_new_backup_flush(db, false)
84    }
85
86    /// Captures the state of the database in the latest backup.
87    ///
88    /// Set flush_before_backup=true to avoid losing unflushed key/value
89    /// pairs from the memtable.
90    pub fn create_new_backup_flush<T: ThreadMode, D: DBInner>(
91        &mut self,
92        db: &DBCommon<T, D>,
93        flush_before_backup: bool,
94    ) -> Result<(), Error> {
95        unsafe {
96            ffi_try!(ffi::rocksdb_backup_engine_create_new_backup_flush(
97                self.inner,
98                db.inner.inner(),
99                c_uchar::from(flush_before_backup),
100            ));
101            Ok(())
102        }
103    }
104
105    pub fn purge_old_backups(&mut self, num_backups_to_keep: usize) -> Result<(), Error> {
106        unsafe {
107            ffi_try!(ffi::rocksdb_backup_engine_purge_old_backups(
108                self.inner,
109                num_backups_to_keep as u32,
110            ));
111            Ok(())
112        }
113    }
114
115    /// Restore from the latest backup
116    ///
117    /// # Arguments
118    ///
119    /// * `db_dir` - A path to the database directory
120    /// * `wal_dir` - A path to the wal directory
121    /// * `opts` - Restore options
122    ///
123    /// # Examples
124    ///
125    /// ```ignore
126    /// use rocksdb::backup::{BackupEngine, BackupEngineOptions};
127    /// let backup_opts = BackupEngineOptions::default();
128    /// let mut backup_engine = BackupEngine::open(&backup_opts, &backup_path).unwrap();
129    /// let mut restore_option = rocksdb::backup::RestoreOptions::default();
130    /// restore_option.set_keep_log_files(true); /// true to keep log files
131    /// if let Err(e) = backup_engine.restore_from_latest_backup(&db_path, &wal_dir, &restore_option) {
132    ///     error!("Failed to restore from the backup. Error: {:?}", e);
133    ///     return Err(e.to_string());
134    /// }
135    /// ```
136    pub fn restore_from_latest_backup<D: AsRef<Path>, W: AsRef<Path>>(
137        &mut self,
138        db_dir: D,
139        wal_dir: W,
140        opts: &RestoreOptions,
141    ) -> Result<(), Error> {
142        let c_db_dir = to_cpath(db_dir)?;
143        let c_wal_dir = to_cpath(wal_dir)?;
144
145        unsafe {
146            ffi_try!(ffi::rocksdb_backup_engine_restore_db_from_latest_backup(
147                self.inner,
148                c_db_dir.as_ptr(),
149                c_wal_dir.as_ptr(),
150                opts.inner,
151            ));
152        }
153        Ok(())
154    }
155
156    /// Restore from a specified backup
157    ///
158    /// The specified backup id should be passed in as an additional parameter.
159    pub fn restore_from_backup<D: AsRef<Path>, W: AsRef<Path>>(
160        &mut self,
161        db_dir: D,
162        wal_dir: W,
163        opts: &RestoreOptions,
164        backup_id: u32,
165    ) -> Result<(), Error> {
166        let c_db_dir = to_cpath(db_dir)?;
167        let c_wal_dir = to_cpath(wal_dir)?;
168
169        unsafe {
170            ffi_try!(ffi::rocksdb_backup_engine_restore_db_from_backup(
171                self.inner,
172                c_db_dir.as_ptr(),
173                c_wal_dir.as_ptr(),
174                opts.inner,
175                backup_id,
176            ));
177        }
178        Ok(())
179    }
180
181    /// Checks that each file exists and that the size of the file matches our
182    /// expectations. it does not check file checksum.
183    ///
184    /// If this BackupEngine created the backup, it compares the files' current
185    /// sizes against the number of bytes written to them during creation.
186    /// Otherwise, it compares the files' current sizes against their sizes when
187    /// the BackupEngine was opened.
188    pub fn verify_backup(&self, backup_id: u32) -> Result<(), Error> {
189        unsafe {
190            ffi_try!(ffi::rocksdb_backup_engine_verify_backup(
191                self.inner, backup_id,
192            ));
193        }
194        Ok(())
195    }
196
197    /// Get a list of all backups together with information on timestamp of the backup
198    /// and the size (please note that sum of all backups' sizes is bigger than the actual
199    /// size of the backup directory because some data is shared by multiple backups).
200    /// Backups are identified by their always-increasing IDs.
201    ///
202    /// You can perform this function safely, even with other BackupEngine performing
203    /// backups on the same directory
204    pub fn get_backup_info(&self) -> Vec<BackupEngineInfo> {
205        unsafe {
206            let i = ffi::rocksdb_backup_engine_get_backup_info(self.inner);
207
208            let n = ffi::rocksdb_backup_engine_info_count(i);
209
210            let mut info = Vec::with_capacity(n as usize);
211            for index in 0..n {
212                info.push(BackupEngineInfo {
213                    timestamp: ffi::rocksdb_backup_engine_info_timestamp(i, index),
214                    backup_id: ffi::rocksdb_backup_engine_info_backup_id(i, index),
215                    size: ffi::rocksdb_backup_engine_info_size(i, index),
216                    num_files: ffi::rocksdb_backup_engine_info_number_files(i, index),
217                });
218            }
219
220            // destroy backup info object
221            ffi::rocksdb_backup_engine_info_destroy(i);
222
223            info
224        }
225    }
226}
227
228impl BackupEngineOptions {
229    /// Initializes `BackupEngineOptions` with the directory to be used for storing/accessing the
230    /// backup files.
231    pub fn new<P: AsRef<Path>>(backup_dir: P) -> Result<Self, Error> {
232        let c_backup_dir = to_cpath(backup_dir)?;
233
234        unsafe {
235            let opts = ffi::rocksdb_backup_engine_options_create(c_backup_dir.as_ptr());
236            assert!(!opts.is_null(), "Could not create RocksDB backup options");
237
238            Ok(Self { inner: opts })
239        }
240    }
241
242    /// Sets the number of operations (such as file copies or file checksums) that `RocksDB` may
243    /// perform in parallel when executing a backup or restore.
244    ///
245    /// Default: 1
246    pub fn set_max_background_operations(&mut self, max_background_operations: i32) {
247        unsafe {
248            ffi::rocksdb_backup_engine_options_set_max_background_operations(
249                self.inner,
250                max_background_operations,
251            );
252        }
253    }
254
255    /// Sets whether to use fsync(2) to sync file data and metadata to disk after every file write,
256    /// guaranteeing that backups will be consistent after a reboot or if machine crashes. Setting
257    /// it to false will speed things up a bit, but some (newer) backups might be inconsistent. In
258    /// most cases, everything should be fine, though.
259    ///
260    /// Default: true
261    ///
262    /// Documentation: <https://github.com/facebook/rocksdb/wiki/How-to-backup-RocksDB#advanced-usage>
263    pub fn set_sync(&mut self, sync: bool) {
264        unsafe {
265            ffi::rocksdb_backup_engine_options_set_sync(self.inner, c_uchar::from(sync));
266        }
267    }
268
269    /// Returns the value of the `sync` option.
270    pub fn get_sync(&mut self) -> bool {
271        let val_u8 = unsafe { ffi::rocksdb_backup_engine_options_get_sync(self.inner) };
272        val_u8 != 0
273    }
274}
275
276impl RestoreOptions {
277    /// Sets `keep_log_files`. If true, restore won't overwrite the existing log files in wal_dir.
278    /// It will also move all log files from archive directory to wal_dir. Use this option in
279    /// combination with BackupEngineOptions::backup_log_files = false for persisting in-memory
280    /// databases.
281    ///
282    /// Default: false
283    pub fn set_keep_log_files(&mut self, keep_log_files: bool) {
284        unsafe {
285            ffi::rocksdb_restore_options_set_keep_log_files(self.inner, i32::from(keep_log_files));
286        }
287    }
288}
289
290impl Default for RestoreOptions {
291    fn default() -> Self {
292        unsafe {
293            let opts = ffi::rocksdb_restore_options_create();
294            assert!(!opts.is_null(), "Could not create RocksDB restore options");
295
296            Self { inner: opts }
297        }
298    }
299}
300
301impl Drop for BackupEngine {
302    fn drop(&mut self) {
303        unsafe {
304            ffi::rocksdb_backup_engine_close(self.inner);
305        }
306    }
307}
308
309impl Drop for BackupEngineOptions {
310    fn drop(&mut self) {
311        unsafe {
312            ffi::rocksdb_backup_engine_options_destroy(self.inner);
313        }
314    }
315}
316
317impl Drop for RestoreOptions {
318    fn drop(&mut self) {
319        unsafe {
320            ffi::rocksdb_restore_options_destroy(self.inner);
321        }
322    }
323}
324
325#[cfg(test)]
326mod tests {
327    use super::BackupEngineOptions;
328
329    #[test]
330    fn test_sync() {
331        let dir = tempfile::Builder::new()
332            .prefix("rocksdb-test-sync")
333            .tempdir()
334            .expect("Failed to create temporary path for db.");
335
336        let mut opts = BackupEngineOptions::new(dir.path()).unwrap();
337        assert!(opts.get_sync());
338        opts.set_sync(false);
339        assert!(!opts.get_sync());
340    }
341}