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}