use tracing::info;
use super::format::{COLUMNAR_CKPT_FORMAT_VERSION, ColumnarCheckpointFile};
use super::paths::{columnar_ckpt_dir, columnar_ckpt_gen_dir, parse_columnar_ckpt_stem};
use crate::data::executor::checkpoint_decode_error::CheckpointDecodeError;
use crate::data::executor::core_loop::CoreLoop;
use crate::types::{DatabaseId, Lsn, TenantId};
struct RestoredColumnar {
engine: nodedb_columnar::MutationEngine,
segments: Vec<Vec<u8>>,
surrogates: nodedb_columnar::mutation::snapshot::FlushedSurrogateTable,
}
type DecodedColumnarGeneration = Vec<((DatabaseId, TenantId, String), RestoredColumnar)>;
impl CoreLoop {
pub fn load_columnar_checkpoints(&mut self) -> crate::Result<()> {
let ckpt_dir = columnar_ckpt_dir(&self.data_dir, self.core_id);
if !ckpt_dir.exists() {
return Ok(());
}
let Some(manifest) = self.read_columnar_manifest(&ckpt_dir)? else {
return Ok(());
};
let gen_dir = columnar_ckpt_gen_dir(&ckpt_dir, manifest.generation);
let decoded = self.decode_columnar_generation(&gen_dir)?;
let collections = decoded.len();
let mut segments = 0usize;
let mut geometry_rows = 0usize;
for (key, restored) in decoded {
let RestoredColumnar {
engine,
segments: blobs,
surrogates,
} = restored;
geometry_rows += self.restore_columnar_geometry_indexes(&key, &engine, &blobs);
segments += blobs.len();
self.columnar_flushed_segments.insert(key.clone(), blobs);
self.columnar_flushed_surrogates
.insert(key.clone(), surrogates);
self.columnar_engines.insert(key, engine);
}
self.floors
.replay_floors
.columnar
.set(Lsn::new(manifest.durable_through_lsn));
self.floors.columnar_durable_lsn = Lsn::new(manifest.durable_through_lsn);
info!(
core = self.core_id,
generation = manifest.generation,
collections,
segments,
geometry_rows,
durable_through_lsn = manifest.durable_through_lsn,
"columnar checkpoint restored"
);
Ok(())
}
fn decode_columnar_generation(
&self,
gen_dir: &std::path::Path,
) -> Result<DecodedColumnarGeneration, CheckpointDecodeError> {
let entries =
std::fs::read_dir(gen_dir).map_err(|source| CheckpointDecodeError::ScanDir {
dir: gen_dir.to_path_buf(),
source,
})?;
let mut decoded = DecodedColumnarGeneration::new();
for entry in entries {
let entry = entry.map_err(|source| CheckpointDecodeError::DirEntry { source })?;
let path = entry.path();
if path.extension().and_then(|e| e.to_str()) != Some("ckpt") {
continue;
}
let stem = path.file_stem().and_then(|s| s.to_str()).unwrap_or("");
let (database_id, tenant_id, collection) =
parse_columnar_ckpt_stem(stem).ok_or_else(|| {
CheckpointDecodeError::UnparseableFilename {
stem: stem.to_string(),
}
})?;
let bytes = nodedb_wal::segment::read_checkpoint_framed(&path).map_err(|source| {
CheckpointDecodeError::ReadFile {
path: path.clone(),
source,
}
})?;
let file =
zerompk::from_msgpack::<ColumnarCheckpointFile>(&bytes).map_err(|source| {
CheckpointDecodeError::MsgpackDecode {
path: path.clone(),
source,
}
})?;
if file.format_version != COLUMNAR_CKPT_FORMAT_VERSION {
return Err(CheckpointDecodeError::FormatVersion {
path: path.clone(),
found: file.format_version,
expected: COLUMNAR_CKPT_FORMAT_VERSION,
});
}
let (engine, segments, surrogates) =
nodedb_columnar::MutationEngine::from_snapshot(file.engine).map_err(|source| {
CheckpointDecodeError::EngineNotRebuildable {
path: path.clone(),
source: Box::new(source),
}
})?;
let mut surrogates = surrogates;
if surrogates.len() != segments.len() {
if !surrogates.is_empty() {
return Err(CheckpointDecodeError::SurrogateLockstepMismatch {
path: path.clone(),
segments: segments.len(),
surrogates: surrogates.len(),
});
}
surrogates = segments.iter().map(|_| Vec::new()).collect();
}
decoded.push((
(
DatabaseId::new(database_id),
TenantId::new(tenant_id),
collection,
),
RestoredColumnar {
engine,
segments,
surrogates,
},
));
}
Ok(decoded)
}
}