···
use bincode::Options as BincodeOptions;
use links::CollectedLink;
use metrics::{counter, describe_counter, describe_histogram, histogram, Unit};
+
use ratelimit::Ratelimiter;
+
use rocksdb::backup::{BackupEngine, BackupEngineOptions};
AsColumnFamilyRef, ColumnFamilyDescriptor, DBWithThreadMode, IteratorMode, MergeOperands,
MultiThreaded, Options, PrefixRange, ReadOptions, WriteBatch,
···
use std::collections::{HashMap, HashSet};
use std::marker::PhantomData;
+
use std::path::{Path, PathBuf};
atomic::{AtomicU64, Ordering},
+
use std::time::{Duration, Instant};
+
use tokio_util::sync::CancellationToken;
static DID_IDS_CF: &str = "did_ids";
static TARGET_IDS_CF: &str = "target_ids";
···
did_id_table: IdTable<Did, DidIdValue, true>,
target_id_table: IdTable<TargetKey, TargetId, false>,
+
backup_task: Arc<Option<thread::JoinHandle<Result<()>>>>,
trait IdTableValue: ValueFromRocks + Clone {
···
+
backup_task: None.into(),
+
auto: Option<(u64, Option<usize>)>,
+
stay_alive: CancellationToken,
+
let task = if let Some((interval_hrs, copies)) = auto {
+
eprintln!("backups: starting background task...");
+
self.backup_task(path, interval_hrs, copies, stay_alive)
+
eprintln!("backups: starting a one-off backup...");
+
let db = self.db.clone();
+
move || Self::do_backup(db, path)
+
self.backup_task = Arc::new(Some(task));
+
stay_alive: CancellationToken,
+
) -> std::thread::JoinHandle<Result<()>> {
+
let db = self.db.clone();
+
thread::spawn(move || {
+
Ratelimiter::builder(1, Duration::from_secs(interval_hrs * 60 * 60)).build()?;
+
let minimum_sleep = Duration::from_secs(1);
+
if let Err(sleep) = limit.try_wait() {
+
eprintln!("backups: background: next backup scheduled in {sleep:?}");
+
let waiting = Instant::now();
+
let remaining = sleep - waiting.elapsed();
+
if stay_alive.is_cancelled() {
+
} else if remaining <= Duration::ZERO {
+
} else if remaining < minimum_sleep {
+
thread::sleep(remaining);
+
thread::sleep(minimum_sleep);
+
eprintln!("backups: background: starting backup...");
+
if let Err(e) = Self::do_backup(db.clone(), &path) {
+
eprintln!("backups: background: backup failed: {e:?}");
+
eprintln!("backups: background: backup succeeded yay");
+
if let Some(copies) = copies {
+
eprintln!("backups: background: trimming to {copies} saved backups...");
+
if let Err(e) = Self::trim_backups(copies, &path) {
+
eprintln!("backups: background: failed to trim backups: {e:?}");
+
eprintln!("backups: background: trimming worked!")
+
fn do_backup(db: Arc<DBWithThreadMode<MultiThreaded>>, path: impl AsRef<Path>) -> Result<()> {
+
BackupEngine::open(&BackupEngineOptions::new(path)?, &rocksdb::Env::new()?)?;
+
eprintln!("backups: starting backup...");
+
let t0 = Instant::now();
+
if let Err(e) = engine.create_new_backup(&db) {
+
eprintln!("backups: oh no, backup failed: {e:?}");
+
eprintln!("backups: yay, backup worked?");
+
"backups: backup finished after {:.2}s",
+
t0.elapsed().as_secs_f32()
+
fn trim_backups(num_backups_to_keep: usize, path: impl AsRef<Path>) -> Result<()> {
+
BackupEngine::open(&BackupEngineOptions::new(path)?, &rocksdb::Env::new()?)?;
+
engine.purge_old_backups(num_backups_to_keep)?;
···
eprintln!("rocks: flushing memtables failed: {e:?}");
+
match Arc::get_mut(&mut self.backup_task) {
+
if let Some(task) = maybe_task.take() {
+
eprintln!("waiting for backup task to complete...");
+
if let Err(e) = task.join() {
+
eprintln!("failed to join backup task: {e:?}");
+
None => eprintln!("rocks: failed to get backup task, likely a bug."),
self.db.cancel_all_background_work(true);