Skip to content

Commit 427421b

Browse files
Bottomless skip snapshot (#1238)
* skipp inject_replication_index on checkpoint * bottomless: LIBSQL_BOTTOMLESS_SKIP_SNAPSHOT param * restore required when generation is empty but dependency exists * add tracing, limit busy handler retries --------- Co-authored-by: ad hoc <postma.marin@protonmail.com>
1 parent ef44612 commit 427421b

7 files changed

Lines changed: 49 additions & 8 deletions

File tree

bottomless/src/bottomless_wal.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::ffi::c_int;
22
use std::sync::{Arc, Mutex};
3+
use std::time::Instant;
34

45
use crate::completion_progress::SavepointTracker;
56
use libsql_sys::ffi::{SQLITE_BUSY, SQLITE_IOERR_WRITE};
@@ -91,6 +92,7 @@ impl<T: Wal> WrapWal<T> for BottomlessWalWrapper {
9192
Ok(num_frames)
9293
}
9394

95+
#[tracing::instrument(skip_all, fields(in_wal = in_wal, backfilled = backfilled))]
9496
fn checkpoint(
9597
&mut self,
9698
wrapped: &mut T,
@@ -104,8 +106,9 @@ impl<T: Wal> WrapWal<T> for BottomlessWalWrapper {
104106
in_wal: Option<&mut i32>,
105107
backfilled: Option<&mut i32>,
106108
) -> Result<()> {
109+
let before = Instant::now();
107110
{
108-
tracing::trace!("bottomless checkpoint");
111+
tracing::trace!("bottomless checkpoint: {mode:?}");
109112

110113
/* In order to avoid partial checkpoints, passive checkpoint
111114
** mode is not allowed. Only TRUNCATE checkpoints are accepted,
@@ -143,13 +146,15 @@ impl<T: Wal> WrapWal<T> for BottomlessWalWrapper {
143146
);
144147
return Err(Error::new(SQLITE_IOERR_WRITE));
145148
}
149+
tracing::debug!("commited after {:?}", before.elapsed());
146150
if let Err(e) = runtime.block_on(replicator.wait_until_snapshotted()) {
147151
tracing::error!(
148152
"Failed to wait for S3 replicator to confirm database snapshot backup: {}",
149153
e
150154
);
151155
return Err(Error::new(SQLITE_IOERR_WRITE));
152156
}
157+
tracing::debug!("snapshotted after {:?}", before.elapsed());
153158

154159
Ok(())
155160
})??;
@@ -166,14 +171,16 @@ impl<T: Wal> WrapWal<T> for BottomlessWalWrapper {
166171
backfilled,
167172
)?;
168173

174+
tracing::debug!("underlying checkpoint call after {:?}", before.elapsed());
175+
169176
#[allow(clippy::await_holding_lock)]
170177
// uncontended -> only gets called under a libSQL write lock
171178
{
172179
let runtime = tokio::runtime::Handle::current();
173180
self.try_with_replicator(|replicator| {
174181
if let Err(e) = runtime.block_on(async move {
175182
replicator.new_generation().await;
176-
replicator.snapshot_main_db_file().await
183+
replicator.snapshot_main_db_file(false).await
177184
}) {
178185
tracing::error!("Failed to snapshot the main db file during checkpoint: {e}");
179186
return Err(Error::new(SQLITE_IOERR_WRITE));
@@ -182,6 +189,8 @@ impl<T: Wal> WrapWal<T> for BottomlessWalWrapper {
182189
})??;
183190
}
184191

192+
tracing::debug!("checkpoint finnished after {:?}", before.elapsed());
193+
185194
Ok(())
186195
}
187196
}

bottomless/src/replicator.rs

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ pub struct Replicator {
6969
join_set: JoinSet<()>,
7070
upload_progress: Arc<Mutex<CompletionProgress>>,
7171
last_uploaded_frame_no: Receiver<u32>,
72+
skip_snapshot: bool,
7273
}
7374

7475
#[derive(Debug)]
@@ -113,6 +114,8 @@ pub struct Options {
113114
pub s3_upload_max_parallelism: usize,
114115
/// Max number of retries for S3 operations
115116
pub s3_max_retries: u32,
117+
/// Skip snapshot upload per checkpoint.
118+
pub skip_snapshot: bool,
116119
}
117120

118121
impl Options {
@@ -202,6 +205,17 @@ impl Options {
202205
other
203206
),
204207
};
208+
let skip_snapshot = match env_var_or("LIBSQL_BOTTOMLESS_SKIP_SNAPSHOT", false)
209+
.to_lowercase()
210+
.as_ref()
211+
{
212+
"yes" | "true" | "1" | "y" | "t" => true,
213+
"no" | "false" | "0" | "n" | "f" => false,
214+
other => bail!(
215+
"Invalid LIBSQL_BOTTOMLESS_SKIP_SNAPSHOT environment variable: {}",
216+
other
217+
),
218+
};
205219
let s3_max_retries = env_var_or("LIBSQL_BOTTOMLESS_S3_MAX_RETRIES", 10).parse::<u32>()?;
206220
let cipher = match encryption_cipher {
207221
Some(cipher) => Cipher::from_str(&cipher)?,
@@ -226,6 +240,7 @@ impl Options {
226240
region,
227241
bucket_name,
228242
s3_max_retries,
243+
skip_snapshot,
229244
})
230245
}
231246
}
@@ -386,6 +401,7 @@ impl Replicator {
386401
encryption_config: options.encryption_config,
387402
max_frames_per_batch: options.max_frames_per_batch,
388403
s3_upload_max_parallelism: options.s3_upload_max_parallelism,
404+
skip_snapshot: options.skip_snapshot,
389405
join_set,
390406
upload_progress,
391407
last_uploaded_frame_no,
@@ -901,7 +917,12 @@ impl Replicator {
901917
// Sends the main database file to S3 - if -wal file is present, it's replicated
902918
// too - it means that the local file was detected to be newer than its remote
903919
// counterpart.
904-
pub async fn snapshot_main_db_file(&mut self) -> Result<Option<JoinHandle<()>>> {
920+
pub async fn snapshot_main_db_file(&mut self, force: bool) -> Result<Option<JoinHandle<()>>> {
921+
if self.skip_snapshot && !force {
922+
tracing::trace!("database snapshot skipped");
923+
let _ = self.snapshot_notifier.send(Ok(self.generation().ok()));
924+
return Ok(None);
925+
}
905926
if !self.main_db_exists_and_not_empty().await {
906927
let generation = self.generation()?;
907928
tracing::debug!(
@@ -1301,6 +1322,14 @@ impl Replicator {
13011322
);
13021323
match wal_pages.cmp(&last_consistent_frame) {
13031324
std::cmp::Ordering::Equal => {
1325+
if local_counter == [0u8; 4] && wal_pages == 0 {
1326+
if self.get_dependency(&generation).await?.is_some() {
1327+
// empty generation and empty local state, but we have a dependency
1328+
// to previous generation: restore required
1329+
return Ok(None);
1330+
}
1331+
}
1332+
13041333
tracing::info!(
13051334
"Remote generation is up-to-date, reusing it in this session"
13061335
);

libsql-server/src/connection/connection_manager.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,7 @@ impl WrapWal<Sqlite3Wal> for ManagedConnectionWalWrapper {
258258
} else {
259259
mode
260260
};
261+
tracing::debug!("attempted checkpoint mode: {mode:?}");
261262
let ret = wrapped.checkpoint(
262263
db,
263264
mode,

libsql-server/src/connection/libsql.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -417,8 +417,9 @@ impl<W: Wal> Connection<W> {
417417
);
418418

419419
unsafe {
420-
extern "C" fn do_nothing(_: *mut c_void, _: c_int) -> c_int {
421-
1
420+
const MAX_RETRIES: c_int = 8;
421+
extern "C" fn do_nothing(_: *mut c_void, n: c_int) -> c_int {
422+
(n < MAX_RETRIES) as _
422423
}
423424
libsql_sys::ffi::sqlite3_busy_handler(
424425
conn.handle(),

libsql-server/src/namespace/meta_store.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ pub async fn metastore_connection_maker(
126126
max_batch_interval: config.backup_interval,
127127
s3_upload_max_parallelism: 32,
128128
s3_max_retries: 10,
129+
skip_snapshot: false,
129130
};
130131
let mut replicator = bottomless::replicator::Replicator::with_options(
131132
db_path.join("data").to_str().unwrap(),
@@ -137,7 +138,7 @@ pub async fn metastore_connection_maker(
137138
match action {
138139
bottomless::replicator::RestoreAction::SnapshotMainDbFile => {
139140
replicator.new_generation().await;
140-
if let Some(_handle) = replicator.snapshot_main_db_file().await? {
141+
if let Some(_handle) = replicator.snapshot_main_db_file(true).await? {
141142
tracing::trace!(
142143
"got snapshot handle after restore with generation upgrade"
143144
);

libsql-server/src/namespace/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -924,7 +924,7 @@ pub async fn init_bottomless_replicator(
924924
match action {
925925
bottomless::replicator::RestoreAction::SnapshotMainDbFile => {
926926
replicator.new_generation().await;
927-
if let Some(_handle) = replicator.snapshot_main_db_file().await? {
927+
if let Some(_handle) = replicator.snapshot_main_db_file(true).await? {
928928
tracing::trace!("got snapshot handle after restore with generation upgrade");
929929
}
930930
// Restoration process only leaves the local WAL file if it was

libsql-server/src/replication/primary/replication_logger_wal.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ impl ReplicationLoggerWalWrapper {
121121
self.buffer.clear();
122122
}
123123

124-
pub fn logger(&self) -> Arc<ReplicationLogger> {
124+
pub(crate) fn logger(&self) -> Arc<ReplicationLogger> {
125125
self.logger.clone()
126126
}
127127
}

0 commit comments

Comments
 (0)