Skip to content
This repository was archived by the owner on Oct 18, 2023. It is now read-only.

Commit 40cb9be

Browse files
authored
bottomless: checkpoint before initializing bottomless (#726)
* bottomless: checkpoint before initializing bottomless Due to a bug in wallog recovery, we need to checkpoint the database *strictly before* we initialize bottomless. A proper fix should be to use our virtual WAL methods for checkpointing, but there's an initialization cycle and resolving it will be a larger patch - a connection with WAL methods wants us to already have the replication logger created, and replication logger wants to perform a checkpoint on creation. As a mid-term solution, we just perform the forbidden regular checkpoint before bottomless is ever launched. Combined with the fact that bottomless treats existing databases as the source of truth, it just creates a new backup generation and continues working properly. The following scenario was buggy before: 1. We leave the db in the state where some WAL frames still exist in data-wal file 2. We restart sqld 3. bottomless is initialized, it reuses the existing db and WAL frames and uploads them to S3, to avoid creating a potentially costly snapshot 4. ReplicationLogger::new() incorrectly calls sqlite3_wal_checkpoint which swipes data from under bottomless. 5. Bottomless thinks it hasn't checkpointed and continues to write WAL frames. As a result, it writes garbage to S3, because the db was checkpointed outside of bottomless control * fmt fix
1 parent 99ff0c9 commit 40cb9be

File tree

3 files changed

+21
-3
lines changed

3 files changed

+21
-3
lines changed

bottomless/src/replicator.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1302,8 +1302,11 @@ impl Replicator {
13021302
},
13031303
};
13041304

1305-
tracing::info!("Restoring from generation {}", generation);
1306-
self.restore_from(generation, timestamp).await
1305+
let (action, recovered) = self.restore_from(generation, timestamp).await?;
1306+
tracing::info!(
1307+
"Restoring from generation {generation}: action={action:?}, recovered={recovered}"
1308+
);
1309+
Ok((action, recovered))
13071310
}
13081311

13091312
pub async fn get_last_consistent_frame(&self, generation: &Uuid) -> Result<u32> {

sqld/src/namespace/mod.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -657,6 +657,18 @@ impl Namespace<PrimaryDatabase> {
657657

658658
tokio::fs::create_dir_all(&db_path).await?;
659659

660+
// FIXME: due to a bug in logger::checkpoint_db we call regular checkpointing code
661+
// instead of our virtual WAL one. It's a bit tangled to fix right now, because
662+
// we need WAL context for checkpointing, and WAL context needs the ReplicationLogger...
663+
// So instead we checkpoint early, *before* bottomless gets initialized. That way
664+
// we're sure bottomless won't try to back up any existing WAL frames and will instead
665+
// treat the existing db file as the source of truth.
666+
if config.bottomless_replication.is_some() {
667+
tracing::debug!("Checkpointing before initializing bottomless");
668+
crate::replication::primary::logger::checkpoint_db(&db_path.join("data"))?;
669+
tracing::debug!("Checkpointed before initializing bottomless");
670+
}
671+
660672
let bottomless_replicator = if let Some(options) = &config.bottomless_replication {
661673
let options = make_bottomless_options(options, name.clone());
662674
let (replicator, did_recover) =

sqld/src/replication/primary/logger.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ unsafe impl WalHook for ReplicationLoggerHook {
7777
assert_eq!(page_size, 4096);
7878
let wal_ptr = wal as *mut _;
7979
let last_valid_frame = wal.hdr.mxFrame;
80+
tracing::trace!("Last valid frame before applying: {last_valid_frame}");
8081
let ctx = Self::wal_extract_ctx(wal);
8182

8283
let mut frame_count = 0;
@@ -948,7 +949,9 @@ impl ReplicationLogger {
948949
}
949950
}
950951

951-
fn checkpoint_db(data_path: &Path) -> anyhow::Result<()> {
952+
// FIXME: calling rusqlite::Connection's checkpoint here is a bug,
953+
// we need to always call our virtual WAL methods.
954+
pub fn checkpoint_db(data_path: &Path) -> anyhow::Result<()> {
952955
let wal_path = match data_path.parent() {
953956
Some(path) => path.join("data-wal"),
954957
None => return Ok(()),

0 commit comments

Comments
 (0)