use crate::Result;
use crate::errors::PagedbError;
use crate::segment::reader::SegmentReader;
use crate::segment::types::SegmentPageKind;
use crate::segment::writer::SegmentWriter;
use crate::txn::db::Db;
use crate::vfs::{Vfs, VfsFile};
use super::helpers::{find_segment_name_inner, list_all_segments_inner, replace_segment_compact};
use super::types::CompactStats;
pub async fn compact_now<V: Vfs + Clone>(db: &Db<V>) -> Result<CompactStats> {
use tracing::Instrument;
compact_now_inner(db)
.instrument(tracing::debug_span!("compaction.run"))
.await
}
#[allow(clippy::too_many_lines)]
async fn compact_now_inner<V: Vfs + Clone>(db: &Db<V>) -> Result<CompactStats> {
db.ensure_usable()?;
if !matches!(db.mode, crate::txn::mode::DbMode::Standalone) {
return Err(PagedbError::Unsupported);
}
let mut result = CompactStats::default();
let mut state = db.writer.lock().await;
db.ensure_usable()?;
#[cfg(test)]
db.notify_writer_waiting();
let visibility_guard = db.visibility_gate.write().await;
db.free_page_cache.lock().clear();
let has_readers = {
let readers = db.tracked_readers.lock();
!readers.is_empty()
};
if has_readers {
return Ok(result);
}
if state.free_list_root_page_id != 0 {
let repack = super::repack::atomic_dense_repack(db, &mut state, &visibility_guard).await?;
result.main_db_pages_reclaimed = repack.pages_reclaimed;
result.bytes_truncated = repack.bytes_truncated;
}
let all_segments = list_all_segments_inner(&db.pager, db.realm_id, &state).await?;
for meta in all_segments {
let live = crate::segment::writer::live_path(&meta.segment_id);
let file_size = match db.vfs.open(&live, crate::vfs::types::OpenMode::Read).await {
Ok(f) => f.len().await.unwrap_or(meta.total_bytes),
Err(_) => continue,
};
let threshold = meta.total_bytes.saturating_add(meta.total_bytes / 20);
if file_size <= threshold {
continue;
}
let mmap_limit = u64::try_from(db.options.mmap_view_scratch_bytes).unwrap_or(u64::MAX);
let reader = SegmentReader::open_internal(
db.pager.clone(),
meta.clone(),
db.mmap_bytes_in_use.clone(),
mmap_limit,
)
.await?;
db.vfs.mkdir_all("seg/.staging").await?;
let new_segment_id = crate::crypto::random::segment_id()?;
let mut writer = SegmentWriter::create_internal(
db.pager.clone(),
meta.realm_id,
new_segment_id,
db.file_id,
meta.segment_kind,
)
.await?;
for page_id in 1..meta.page_count.saturating_sub(1) {
match reader.read_page(page_id).await {
Ok(page_bytes) => {
writer
.append_page(SegmentPageKind::Data, &page_bytes)
.await?;
}
Err(PagedbError::NotFound) => {}
Err(e) => return Err(e),
}
}
let new_meta = writer.seal().await?;
let seg_name =
find_segment_name_inner(&db.pager, db.realm_id, &state, &meta.segment_id).await?;
replace_segment_compact(
db,
&mut state,
&visibility_guard,
&seg_name,
&meta.segment_id,
&new_meta,
)
.await?;
result.segments_repacked += 1;
}
Ok(result)
}