1use super::block_fetch::load_segment_index_for_reorganization;
24use super::build::{
25 build_manifest_tables_from_rows, debug_assert_manifest_table_segments_do_not_overlap,
26 MetadataTableSegmentation,
27};
28use super::error::ManifestLoadError;
29use super::flush::{ensure_metadata_publication_budget, next_manifest_id_after};
30use super::load::{
31 load_namespace_manifest_envelope_if_present, load_verified_manifest_tables,
32 validate_direntry_child_bind_index, validate_revision_by_inode_desc_index,
33};
34use super::publish::{publish_metadata_root, write_namespace_manifest, ManifestPublicationOutcome};
35use super::runs::{
36 flatten_manifest_tables, l0_run_count, MetadataLsmPolicy, MetadataRunManifest,
37 CHECKPOINT_BASE_RUN_LEVEL, CHECKPOINT_L0_RUN_LEVEL,
38};
39use super::scan::VerifiedMetadataTables;
40use crate::context::MutationContext;
41use crate::error::{CoreError, MetadataProjectionLoadError, Result};
42use crate::limits::CONTENTION_RETRY_LIMIT;
43use crate::namespace::basis::resolve_retention_floor_seq;
44use crate::namespace::control::{read_head_object, read_metadata_root_object_if_present};
45use crate::timing::{MonotonicTimer, StdMonotonicTimer};
46use loonfs_api::wire::manifest::{
47 ActiveDeletionRowAction, MetadataFileRef, MetadataRow, MetadataTableFamily,
48 NamespaceManifestEnvelope, NamespaceManifestPayload,
49};
50use loonfs_api::{ChangeSeq, InodeId, ManifestId, ManifestObjectId, NamespaceId};
51use loonfs_objectstore::keys::metadata_manifest_object;
52use loonfs_objectstore::ObjectStore;
53use std::collections::{BTreeMap, BTreeSet};
54
55const REORGANIZE_FAMILY_GROUPS: [&[MetadataTableFamily]; 6] = [
60 &[
61 MetadataTableFamily::DirentryBinds,
62 MetadataTableFamily::DirentryChildBinds,
63 MetadataTableFamily::DirentryUnbinds,
64 ],
65 &[
66 MetadataTableFamily::Revisions,
67 MetadataTableFamily::RevisionsByInodeDesc,
68 ],
69 &[MetadataTableFamily::Inodes],
70 &[MetadataTableFamily::Tombstones],
71 &[MetadataTableFamily::ActiveDeletions],
74 &[MetadataTableFamily::CommitReceipts],
75];
76
77#[derive(Debug, Clone, PartialEq, Eq)]
79pub enum MetadataReorganizeOutcome {
80 NotNeeded { l0_runs: usize },
83 UnitPublished {
86 families: Vec<MetadataTableFamily>,
87 folded_l0_rows: u64,
88 input_runs: usize,
89 decoded_input_rows: u64,
90 decoded_input_bytes: u64,
91 manifest_id: ManifestId,
92 },
93 BudgetExhausted {
96 families: Vec<MetadataTableFamily>,
97 l0_runs: usize,
98 },
99 Superseded,
103}
104
105#[derive(Debug, Clone, PartialEq, Eq)]
106pub struct MetadataReorganizeReport {
107 pub namespace_id: NamespaceId,
108 pub outcome: MetadataReorganizeOutcome,
109}
110
111#[tracing::instrument(
116 level = "info",
117 name = "loonfs.phase",
118 err,
119 skip_all,
120 fields(phase = "reorganize_metadata", key_class = "manifest")
121)]
122pub(crate) async fn reorganize_metadata_step<S: ObjectStore + ?Sized>(
123 store: &S,
124 namespace_id: &NamespaceId,
125 context: &MutationContext,
126 policy: MetadataLsmPolicy,
127) -> Result<MetadataReorganizeReport> {
128 let timer = StdMonotonicTimer::default();
129 reorganize_metadata_step_with_timer(store, namespace_id, context, policy, &timer).await
130}
131
132pub(super) async fn reorganize_metadata_step_with_timer<S: ObjectStore + ?Sized>(
133 store: &S,
134 namespace_id: &NamespaceId,
135 context: &MutationContext,
136 policy: MetadataLsmPolicy,
137 timer: &dyn MonotonicTimer,
138) -> Result<MetadataReorganizeReport> {
139 let publication_started_ms = timer.monotonic_now_ms();
143 let Some(root) = read_metadata_root_object_if_present(store, namespace_id)
146 .await
147 .map_err(CoreError::load_head)?
148 .map(|loaded| loaded.envelope.state)
149 else {
150 return Ok(MetadataReorganizeReport {
151 namespace_id: namespace_id.clone(),
152 outcome: MetadataReorganizeOutcome::NotNeeded { l0_runs: 0 },
153 });
154 };
155 let tables = load_verified_manifest_tables(store, namespace_id, &root.manifest_object_id)
156 .await
157 .map_err(|error| {
158 CoreError::MetadataProjection(MetadataProjectionLoadError::ManifestLoad(error))
159 })?;
160 let previous = tables.manifest();
161
162 let l0_runs = l0_run_count(&previous.payload);
163 if l0_runs < policy.max_l0_runs.get()
164 && !manifest_has_partial_reorganization(tables.scan_runs.as_ref())
165 {
166 return Ok(MetadataReorganizeReport {
167 namespace_id: namespace_id.clone(),
168 outcome: MetadataReorganizeOutcome::NotNeeded { l0_runs },
169 });
170 }
171 let Some(group) = select_family_group(&previous.payload) else {
172 return Ok(MetadataReorganizeReport {
174 namespace_id: namespace_id.clone(),
175 outcome: MetadataReorganizeOutcome::NotNeeded { l0_runs },
176 });
177 };
178 let Some(input) = select_reorganization_input(&tables, group, policy)
179 .await
180 .map_err(|error| {
181 CoreError::MetadataProjection(MetadataProjectionLoadError::ManifestLoad(error))
182 })?
183 else {
184 return Ok(MetadataReorganizeReport {
185 namespace_id: namespace_id.clone(),
186 outcome: MetadataReorganizeOutcome::BudgetExhausted {
187 families: group.to_vec(),
188 l0_runs,
189 },
190 });
191 };
192
193 let mut rows_by_family = BTreeMap::<MetadataTableFamily, Vec<MetadataRow>>::new();
197 for family in group {
198 let rows = tables
199 .scan_prefix_in_runs(&input.runs, *family, "")
200 .await
201 .map_err(|error| {
202 CoreError::MetadataProjection(MetadataProjectionLoadError::ManifestLoad(error))
203 })?;
204 rows_by_family.insert(*family, rows);
205 }
206 if group.contains(&MetadataTableFamily::DirentryBinds) {
209 validate_direntry_child_bind_index(
210 root.manifest_object_id.as_ref(),
211 rows_by_family
212 .get(&MetadataTableFamily::DirentryBinds)
213 .map_or(&[], Vec::as_slice),
214 rows_by_family
215 .get(&MetadataTableFamily::DirentryChildBinds)
216 .map_or(&[], Vec::as_slice),
217 )
218 .map_err(|error| {
219 CoreError::MetadataProjection(MetadataProjectionLoadError::ManifestLoad(error))
220 })?;
221 }
222 if group.contains(&MetadataTableFamily::Revisions) {
223 validate_revision_by_inode_desc_index(
224 root.manifest_object_id.as_ref(),
225 rows_by_family
226 .get(&MetadataTableFamily::Revisions)
227 .map_or(&[], Vec::as_slice),
228 rows_by_family
229 .get(&MetadataTableFamily::RevisionsByInodeDesc)
230 .map_or(&[], Vec::as_slice),
231 )
232 .map_err(|error| {
233 CoreError::MetadataProjection(MetadataProjectionLoadError::ManifestLoad(error))
234 })?;
235 }
236 let head = read_head_object(store, namespace_id)
237 .await
238 .map_err(CoreError::load_head)?
239 .envelope
240 .state;
241 let floor_seq = resolve_retention_floor_seq(store, &head)
242 .await
243 .map_err(CoreError::load_head)?;
244 drop_rows_below_retention_floor(&mut rows_by_family, floor_seq)?;
245
246 let run_tables = build_manifest_tables_from_rows(
247 store,
248 namespace_id,
249 previous.payload.head_seq,
250 CHECKPOINT_BASE_RUN_LEVEL,
251 |family| rows_by_family.remove(&family).unwrap_or_default(),
252 MetadataTableSegmentation::Base {
253 max_rows_per_segment: policy.max_rows_per_segment,
254 },
255 )
256 .await?;
257 debug_assert_manifest_table_segments_do_not_overlap(&run_tables);
258
259 let mut metadata_files: Vec<_> = previous
260 .payload
261 .metadata_files
262 .iter()
263 .filter(|descriptor| {
264 !group.contains(&descriptor.family)
265 || !input
266 .run_ids
267 .contains(&(descriptor.run_seq, descriptor.level))
268 })
269 .cloned()
270 .collect();
271 metadata_files.extend(flatten_manifest_tables(run_tables));
272 let base_seq = metadata_files
276 .iter()
277 .map(|descriptor| descriptor.run_seq)
278 .min()
279 .unwrap_or(previous.payload.base_seq);
280
281 let manifest =
282 write_reorganized_manifest(store, namespace_id, previous, metadata_files, base_seq, {
283 let mut payload_floor = previous.payload.retention_floor_seq;
284 if floor_seq > payload_floor {
285 payload_floor = floor_seq;
286 }
287 payload_floor
288 })
289 .await?;
290
291 ensure_metadata_publication_budget(timer, publication_started_ms, namespace_id)?;
292 match publish_metadata_root(
293 store,
294 namespace_id,
295 &manifest,
296 Some(root.manifest_object_id.clone()),
297 context.now_ms,
298 )
299 .await?
300 {
301 ManifestPublicationOutcome::Published(_) => Ok(MetadataReorganizeReport {
302 namespace_id: namespace_id.clone(),
303 outcome: MetadataReorganizeOutcome::UnitPublished {
304 families: group.to_vec(),
305 folded_l0_rows: input.folded_l0_rows,
306 input_runs: input.runs.len(),
307 decoded_input_rows: input.decoded_rows,
308 decoded_input_bytes: input.decoded_bytes,
309 manifest_id: manifest.payload.manifest_id,
310 },
311 }),
312 ManifestPublicationOutcome::Superseded(_) | ManifestPublicationOutcome::RootCasRaceLost => {
313 Ok(MetadataReorganizeReport {
314 namespace_id: namespace_id.clone(),
315 outcome: MetadataReorganizeOutcome::Superseded,
316 })
317 }
318 }
319}
320
321struct ReorganizationInput {
322 runs: Vec<MetadataRunManifest>,
323 run_ids: BTreeSet<(ChangeSeq, u32)>,
324 folded_l0_rows: u64,
325 decoded_rows: u64,
326 decoded_bytes: u64,
327}
328
329async fn select_reorganization_input<S: ObjectStore + ?Sized>(
335 tables: &VerifiedMetadataTables<'_, S>,
336 group: &[MetadataTableFamily],
337 policy: MetadataLsmPolicy,
338) -> std::result::Result<Option<ReorganizationInput>, ManifestLoadError> {
339 let mut candidates = tables
340 .scan_runs
341 .iter()
342 .filter(|run| run_has_group_rows(run, group))
343 .collect::<Vec<_>>();
344 candidates.sort_by(|left, right| {
345 let left_is_l0 = left.level == CHECKPOINT_L0_RUN_LEVEL;
346 let right_is_l0 = right.level == CHECKPOINT_L0_RUN_LEVEL;
347 left_is_l0
348 .cmp(&right_is_l0)
349 .then(left.run_seq.cmp(&right.run_seq))
350 .then(right.level.cmp(&left.level))
351 });
352 let candidate_count = candidates.len();
353 let row_budget =
354 u64::try_from(policy.max_decoded_input_rows_per_step.get()).unwrap_or(u64::MAX);
355 let byte_budget =
356 u64::try_from(policy.max_decoded_input_bytes_per_step.get()).unwrap_or(u64::MAX);
357
358 let mut runs = Vec::new();
359 let mut decoded_rows = 0u64;
360 let mut decoded_bytes = 0u64;
361 let mut folded_l0_rows = 0u64;
362 for run in candidates
363 .into_iter()
364 .take(policy.max_input_runs_per_step.get())
365 {
366 let run_rows = group_run_descriptors(run, group)
367 .map(|descriptor| descriptor.row_count)
368 .sum::<u64>();
369 if decoded_rows.saturating_add(run_rows) > row_budget {
370 break;
371 }
372 let run_bytes = decoded_group_run_bytes(tables, run, group).await?;
373 if decoded_bytes.saturating_add(run_bytes) > byte_budget {
374 break;
375 }
376 if run.level == CHECKPOINT_L0_RUN_LEVEL {
377 folded_l0_rows = folded_l0_rows.saturating_add(run_rows);
378 }
379 decoded_rows = decoded_rows.saturating_add(run_rows);
380 decoded_bytes = decoded_bytes.saturating_add(run_bytes);
381 runs.push(run.clone());
382 }
383
384 let selected_l0 = runs.iter().any(|run| run.level == CHECKPOINT_L0_RUN_LEVEL);
385 let makes_progress = selected_l0 && (runs.len() > 1 || candidate_count == 1);
386 if !makes_progress {
387 return Ok(None);
388 }
389 let run_ids = runs.iter().map(|run| (run.run_seq, run.level)).collect();
390 Ok(Some(ReorganizationInput {
391 runs,
392 run_ids,
393 folded_l0_rows,
394 decoded_rows,
395 decoded_bytes,
396 }))
397}
398
399fn manifest_has_partial_reorganization(runs: &[MetadataRunManifest]) -> bool {
404 let Some(oldest_l0_seq) = runs
405 .iter()
406 .filter(|run| run.level == CHECKPOINT_L0_RUN_LEVEL)
407 .map(|run| run.run_seq)
408 .min()
409 else {
410 return false;
411 };
412 runs.iter()
413 .any(|run| run.level != CHECKPOINT_L0_RUN_LEVEL && run.run_seq >= oldest_l0_seq)
414}
415
416fn run_has_group_rows(run: &MetadataRunManifest, group: &[MetadataTableFamily]) -> bool {
417 group_run_descriptors(run, group).next().is_some()
418}
419
420fn group_run_descriptors<'a>(
421 run: &'a MetadataRunManifest,
422 group: &'a [MetadataTableFamily],
423) -> impl Iterator<Item = &'a MetadataFileRef> {
424 run.tables
425 .iter()
426 .filter(|table| group.contains(&table.family))
427 .flat_map(|table| &table.segments)
428}
429
430async fn decoded_group_run_bytes<S: ObjectStore + ?Sized>(
431 tables: &VerifiedMetadataTables<'_, S>,
432 run: &MetadataRunManifest,
433 group: &[MetadataTableFamily],
434) -> std::result::Result<u64, ManifestLoadError> {
435 let mut decoded_bytes = 0u64;
436 for descriptor in group_run_descriptors(run, group) {
437 let index = load_segment_index_for_reorganization(
438 tables.store,
439 tables.table_cache,
440 &tables.block_memo,
441 descriptor,
442 )
443 .await?;
444 for entry in index.iter() {
445 decoded_bytes = decoded_bytes.saturating_add(u64::from(entry.block.decoded_len));
446 }
447 }
448 Ok(decoded_bytes)
449}
450
451fn select_family_group(
454 payload: &NamespaceManifestPayload,
455) -> Option<&'static [MetadataTableFamily]> {
456 REORGANIZE_FAMILY_GROUPS
457 .into_iter()
458 .map(|group| (group_l0_rows(payload, group), group))
459 .filter(|(rows, _)| *rows > 0)
460 .max_by(|(left_rows, left), (right_rows, right)| {
461 left_rows.cmp(right_rows).then_with(|| {
462 position_of(right).cmp(&position_of(left))
465 })
466 })
467 .map(|(_, group)| group)
468}
469
470fn position_of(group: &[MetadataTableFamily]) -> usize {
471 REORGANIZE_FAMILY_GROUPS
472 .iter()
473 .position(|candidate| candidate.as_ptr() == group.as_ptr())
474 .unwrap_or(usize::MAX)
475}
476
477fn group_l0_rows(payload: &NamespaceManifestPayload, group: &[MetadataTableFamily]) -> u64 {
478 payload
479 .metadata_files
480 .iter()
481 .filter(|descriptor| {
482 descriptor.level == CHECKPOINT_L0_RUN_LEVEL && group.contains(&descriptor.family)
483 })
484 .map(|descriptor| descriptor.row_count)
485 .sum()
486}
487
488async fn write_reorganized_manifest<S: ObjectStore + ?Sized>(
489 store: &S,
490 namespace_id: &NamespaceId,
491 previous: &NamespaceManifestEnvelope,
492 metadata_files: Vec<MetadataFileRef>,
493 base_seq: ChangeSeq,
494 retention_floor_seq: ChangeSeq,
495) -> Result<NamespaceManifestEnvelope> {
496 let manifest_id = next_manifest_id_after(previous.payload.manifest_id)?;
497 for _allocation_attempt in 0..CONTENTION_RETRY_LIMIT {
498 let manifest_object_id = ManifestObjectId::generate(manifest_id);
499 let manifest_key = metadata_manifest_object(namespace_id.as_str(), &manifest_object_id);
500 match load_namespace_manifest_envelope_if_present(
501 store,
502 namespace_id,
503 &manifest_object_id,
504 &manifest_key,
505 )
506 .await
507 {
508 Ok(Some(_existing)) => continue,
509 Ok(None) => {}
510 Err(error) => {
511 return Err(CoreError::MetadataProjection(
512 MetadataProjectionLoadError::ManifestLoad(error),
513 ))
514 }
515 }
516 let manifest = NamespaceManifestEnvelope::from_payload(NamespaceManifestPayload {
517 namespace_id: namespace_id.clone(),
518 manifest_id,
519 manifest_object_id,
520 head_seq: previous.payload.head_seq,
521 head_commit_id: previous.payload.head_commit_id.clone(),
522 base_seq,
523 writer_epoch: previous.payload.writer_epoch,
524 next_inode_id: previous.payload.next_inode_id,
525 retention_floor_seq,
526 metadata_files: metadata_files.clone(),
527 })
528 .map_err(|err| {
529 CoreError::Internal(format!("failed to build reorganized manifest: {err}"))
530 })?;
531 match write_namespace_manifest(store, &manifest).await {
532 Ok(()) => return Ok(manifest),
533 Err(MetadataProjectionLoadError::ManifestLoad(
534 ManifestLoadError::ManifestConflict { .. },
535 )) => continue,
536 Err(error) => return Err(CoreError::MetadataProjection(error)),
537 }
538 }
539 Err(CoreError::Internal(
540 "reorganized manifest allocation retry exhausted".to_owned(),
541 ))
542}
543
544pub(super) fn drop_rows_below_retention_floor(
552 rows_by_family: &mut BTreeMap<MetadataTableFamily, Vec<MetadataRow>>,
553 retention_floor_seq: ChangeSeq,
554) -> Result<()> {
555 let mut unbound_at_floor = BTreeSet::new();
562 for row in rows_by_family
563 .get(&MetadataTableFamily::DirentryUnbinds)
564 .into_iter()
565 .flatten()
566 {
567 if let MetadataRow::DirentryUnbind {
568 parent_inode_id,
569 name_key,
570 bind_seq,
571 bind_delta_index,
572 unbind_seq,
573 ..
574 } = row
575 {
576 if *unbind_seq <= retention_floor_seq {
577 unbound_at_floor.insert((
578 *parent_inode_id,
579 name_key.clone(),
580 *bind_seq,
581 *bind_delta_index,
582 ));
583 }
584 }
585 }
586 let mut latest_bind_at_floor = BTreeMap::new();
587 for row in rows_by_family
588 .get(&MetadataTableFamily::DirentryBinds)
589 .into_iter()
590 .flatten()
591 {
592 if let MetadataRow::DirentryBind {
593 parent_inode_id,
594 name_key,
595 bind_seq,
596 bind_delta_index,
597 ..
598 } = row
599 {
600 if *bind_seq <= retention_floor_seq {
601 let candidate = (*bind_seq, *bind_delta_index);
602 let latest = latest_bind_at_floor
603 .entry((*parent_inode_id, name_key.clone()))
604 .or_insert(candidate);
605 if candidate > *latest {
606 *latest = candidate;
607 }
608 }
609 }
610 }
611 for row in rows_by_family
617 .get(&MetadataTableFamily::DirentryBinds)
618 .into_iter()
619 .flatten()
620 {
621 if let MetadataRow::DirentryBind {
622 parent_inode_id,
623 name_key,
624 bind_seq,
625 bind_delta_index,
626 ..
627 } = row
628 {
629 if *bind_seq <= retention_floor_seq
630 && latest_bind_at_floor.get(&(*parent_inode_id, name_key.clone()))
631 != Some(&(*bind_seq, *bind_delta_index))
632 && !unbound_at_floor.contains(&(
633 *parent_inode_id,
634 name_key.clone(),
635 *bind_seq,
636 *bind_delta_index,
637 ))
638 {
639 return Err(CoreError::NamespaceCorrupt(format!(
640 "bind at seq `{bind_seq}` delta {bind_delta_index} for parent `{parent_inode_id}` is superseded at or below the retention floor without an unbind; refusing to drop rows"
641 )));
642 }
643 }
644 }
645
646 let retain_bind = |row: &MetadataRow| match row {
647 MetadataRow::DirentryBind {
648 parent_inode_id,
649 name_key,
650 bind_seq,
651 bind_delta_index,
652 ..
653 } => {
654 *bind_seq > retention_floor_seq
655 || (latest_bind_at_floor.get(&(*parent_inode_id, name_key.clone()))
656 == Some(&(*bind_seq, *bind_delta_index))
657 && !unbound_at_floor.contains(&(
658 *parent_inode_id,
659 name_key.clone(),
660 *bind_seq,
661 *bind_delta_index,
662 )))
663 }
664 _ => true,
665 };
666 for family in [
667 MetadataTableFamily::DirentryBinds,
668 MetadataTableFamily::DirentryChildBinds,
669 ] {
670 if let Some(rows) = rows_by_family.get_mut(&family) {
671 rows.retain(retain_bind);
672 }
673 }
674 if let Some(rows) = rows_by_family.get_mut(&MetadataTableFamily::DirentryUnbinds) {
675 rows.retain(|row| match row {
676 MetadataRow::DirentryUnbind { unbind_seq, .. } => *unbind_seq > retention_floor_seq,
677 _ => true,
678 });
679 }
680
681 if let Some(rows) = rows_by_family.get_mut(&MetadataTableFamily::ActiveDeletions) {
691 let revoked: BTreeSet<(ChangeSeq, InodeId)> = rows
692 .iter()
693 .filter_map(|row| match row {
694 MetadataRow::ActiveDeletion {
695 root_inode_id,
696 deleted_at_seq,
697 action: ActiveDeletionRowAction::Removed { .. },
698 } => Some((*deleted_at_seq, *root_inode_id)),
699 _ => None,
700 })
701 .collect();
702 rows.retain(|row| match row {
703 MetadataRow::ActiveDeletion {
704 root_inode_id,
705 deleted_at_seq,
706 action,
707 } => match action {
708 ActiveDeletionRowAction::Removed { .. } => false,
709 ActiveDeletionRowAction::Listed { .. } => {
710 !revoked.contains(&(*deleted_at_seq, *root_inode_id))
711 }
712 },
713 _ => true,
714 });
715 }
716
717 if let Some(rows) = rows_by_family.get_mut(&MetadataTableFamily::CommitReceipts) {
723 rows.retain(|row| match row {
724 MetadataRow::CommitReceipt { committed_seq, .. } => {
725 *committed_seq >= retention_floor_seq
726 }
727 _ => true,
728 });
729 }
730 Ok(())
731}