1use crate::backend::Backend;
2use crate::error::{
3 AbortError, BatchAbortError, BatchCommitError, BatchError, BatchPrewriteError, CommitError,
4 GcError, PrewriteError, ReadError,
5};
6use crate::types::{
7 CommittedVersion, Intent, Mutation, PhysicalWrite, ReadGuard, Timestamp, TxnId,
8};
9
10#[derive(Debug, Clone, PartialEq, Eq)]
12pub struct GcStats {
13 pub versions_removed: usize,
15 pub intents_preserved: usize,
17}
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub struct GcBudget {
22 pub max_keys: usize,
24 pub max_versions: usize,
26}
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub struct GcOptions {
31 pub budget: GcBudget,
33 pub collapse_final_tombstones: bool,
38}
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42pub struct KeyGcOptions {
43 pub max_versions_examined: usize,
50 pub collapse_final_tombstones: bool,
57}
58
59#[derive(Debug, Clone, PartialEq, Eq)]
61pub struct KeyGcPlan {
62 pub key: Vec<u8>,
64 pub versions_to_remove: Vec<Timestamp>,
70 pub collapse_tombstone: Option<Timestamp>,
72 pub versions_examined: usize,
74 pub complete: bool,
81}
82
83#[derive(Debug, Clone, PartialEq, Eq)]
85pub struct IncrementalGcCursor {
86 pub next_key: Option<Vec<u8>>,
88}
89
90#[derive(Debug, Clone, PartialEq, Eq)]
92pub struct IncrementalGcResult {
93 pub cursor: IncrementalGcCursor,
95 pub done: bool,
97 pub keys_scanned: usize,
99 pub versions_scanned: usize,
101 pub versions_removed: usize,
103 pub intents_preserved: usize,
105}
106
107pub struct MvccEngine<B: Backend> {
112 backend: B,
113}
114
115impl<B: Backend> MvccEngine<B> {
116 pub fn new(backend: B) -> Self {
118 Self { backend }
119 }
120
121 pub fn backend(&self) -> &B {
123 &self.backend
124 }
125
126 pub fn backend_mut(&mut self) -> &mut B {
128 &mut self.backend
129 }
130
131 pub fn read(&self, key: &[u8], read_ts: Timestamp) -> Result<Option<Vec<u8>>, ReadError> {
136 let version = self
137 .backend
138 .get_visible_committed(key, read_ts)
139 .map_err(ReadError::Backend)?;
140 Ok(version.and_then(|v| v.value))
141 }
142
143 #[allow(clippy::type_complexity)]
149 pub fn read_with_version(
150 &self,
151 key: &[u8],
152 read_ts: Timestamp,
153 ) -> Result<Option<(Timestamp, Option<Vec<u8>>)>, ReadError> {
154 let version = self
155 .backend
156 .get_visible_committed(key, read_ts)
157 .map_err(ReadError::Backend)?;
158 Ok(version.map(|v| (v.commit_ts, v.value)))
159 }
160
161 pub fn read_own_write(
166 &self,
167 key: &[u8],
168 txn_id: TxnId,
169 start_ts: Timestamp,
170 read_ts: Timestamp,
171 ) -> Result<Option<Vec<u8>>, ReadError> {
172 if let Some(intent) = self.backend.get_intent(key).map_err(ReadError::Backend)?
174 && intent.txn_id == txn_id
175 && intent.start_ts == start_ts
176 {
177 return Ok(intent.mutation.value());
178 }
179 self.read(key, read_ts)
181 }
182
183 pub fn prewrite(
189 &mut self,
190 txn_id: TxnId,
191 start_ts: Timestamp,
192 key: Vec<u8>,
193 mutation: Mutation,
194 ) -> Result<(), PrewriteError> {
195 if let Some(intent) = self
197 .backend
198 .get_intent(&key)
199 .map_err(PrewriteError::Backend)?
200 {
201 if intent.txn_id != txn_id {
202 return Err(PrewriteError::KeyLocked {
203 txn_id: intent.txn_id,
204 });
205 }
206 if intent.start_ts != start_ts {
210 return Err(PrewriteError::IntentAlreadyExists);
211 }
212 if intent.mutation != mutation {
214 return Err(PrewriteError::IntentAlreadyExists);
215 }
216 return Ok(());
217 }
218
219 if let Some(latest_ts) = self
221 .backend
222 .get_latest_commit_ts(&key)
223 .map_err(PrewriteError::Backend)?
224 && latest_ts > start_ts
225 {
226 return Err(PrewriteError::WriteConflict);
227 }
228
229 let intent = Intent {
230 key: key.clone(),
231 txn_id,
232 start_ts,
233 mutation,
234 min_commit_ts: None,
235 };
236 self.backend
237 .put_intent(intent)
238 .map_err(PrewriteError::Backend)?;
239 Ok(())
240 }
241
242 pub fn commit(
247 &mut self,
248 txn_id: TxnId,
249 key: &[u8],
250 start_ts: Timestamp,
251 commit_ts: Timestamp,
252 ) -> Result<(), CommitError> {
253 let intent = self
254 .backend
255 .get_intent(key)
256 .map_err(CommitError::Backend)?
257 .ok_or(CommitError::IntentNotFound)?;
258
259 if intent.txn_id != txn_id {
260 return Err(CommitError::TxnIdMismatch);
261 }
262 if intent.start_ts != start_ts {
263 return Err(CommitError::StartTsMismatch);
264 }
265
266 if commit_ts <= start_ts {
267 return Err(CommitError::InvalidCommitTimestamp {
268 start_ts,
269 commit_ts,
270 });
271 }
272
273 if let Some(min_ts) = intent.min_commit_ts
274 && commit_ts < min_ts
275 {
276 return Err(CommitError::CommitTsTooEarly {
277 commit_ts,
278 min_commit_ts: min_ts,
279 });
280 }
281
282 if let Some(latest_ts) = self
284 .backend
285 .get_latest_commit_ts(key)
286 .map_err(CommitError::Backend)?
287 && commit_ts <= latest_ts
288 {
289 if commit_ts == latest_ts {
290 return Err(CommitError::DuplicateCommitTimestamp { commit_ts });
291 } else {
292 return Err(CommitError::CommitTsTooOld {
293 commit_ts,
294 latest_commit_ts: latest_ts,
295 });
296 }
297 }
298
299 let version = CommittedVersion {
301 key: key.to_vec(),
302 commit_ts,
303 value: intent.mutation.value(),
304 };
305 self.backend
306 .commit_intents_batch(vec![version], vec![(key.to_vec(), txn_id, start_ts)])
307 .map_err(CommitError::Backend)?;
308 Ok(())
309 }
310
311 pub fn abort(
315 &mut self,
316 txn_id: TxnId,
317 key: &[u8],
318 start_ts: Timestamp,
319 ) -> Result<(), AbortError> {
320 let removed = self
321 .backend
322 .remove_intent(key, txn_id, start_ts)
323 .map_err(AbortError::Backend)?;
324 let _ = removed;
327 Ok(())
328 }
329
330 pub fn prewrite_batch(
335 &mut self,
336 txn_id: TxnId,
337 start_ts: Timestamp,
338 writes: Vec<PhysicalWrite>,
339 ) -> Result<(), BatchPrewriteError> {
340 if writes.is_empty() {
341 return Err(BatchPrewriteError::EmptyBatch);
342 }
343
344 let mut key_set = std::collections::HashSet::new();
345 for w in &writes {
346 if !key_set.insert(w.key.clone()) {
347 return Err(BatchPrewriteError::DuplicateKeyInBatch { key: w.key.clone() });
348 }
349 }
350
351 let mut existing_count = 0;
352 for w in &writes {
353 if let Some(intent) = self
354 .backend
355 .get_intent(&w.key)
356 .map_err(BatchPrewriteError::Backend)?
357 {
358 if intent.txn_id != txn_id {
359 return Err(BatchPrewriteError::KeyLocked {
360 key: w.key.clone(),
361 txn_id: intent.txn_id,
362 });
363 }
364 let expected_mutation = if let Some(v) = &w.value {
365 Mutation::Put(v.clone())
366 } else {
367 Mutation::Delete
368 };
369 if intent.start_ts != start_ts || intent.mutation != expected_mutation {
370 return Err(BatchPrewriteError::IntentAlreadyExists { key: w.key.clone() });
371 }
372 existing_count += 1;
373 } else if let Some(latest_ts) = self
374 .backend
375 .get_latest_commit_ts(&w.key)
376 .map_err(BatchPrewriteError::Backend)?
377 && latest_ts > start_ts
378 {
379 return Err(BatchPrewriteError::WriteConflict { key: w.key.clone() });
380 }
381 }
382
383 if existing_count == writes.len() {
384 return Ok(());
385 } else if existing_count > 0 {
386 return Err(BatchPrewriteError::PartialBatchReplay);
387 }
388
389 let mut intents = Vec::with_capacity(writes.len());
390 for w in writes {
391 intents.push(Intent {
392 key: w.key,
393 txn_id,
394 start_ts,
395 mutation: if let Some(v) = w.value {
396 Mutation::Put(v)
397 } else {
398 Mutation::Delete
399 },
400 min_commit_ts: None,
401 });
402 }
403 self.backend
404 .put_intents_batch(intents)
405 .map_err(BatchPrewriteError::Backend)?;
406 Ok(())
407 }
408
409 pub fn commit_batch(
413 &mut self,
414 txn_id: TxnId,
415 start_ts: Timestamp,
416 commit_ts: Timestamp,
417 keys: Vec<Vec<u8>>,
418 ) -> Result<(), BatchCommitError> {
419 if keys.is_empty() {
420 return Err(BatchCommitError::EmptyBatch);
421 }
422
423 let mut key_set = std::collections::HashSet::new();
424 for key in &keys {
425 if !key_set.insert(key.clone()) {
426 return Err(BatchCommitError::DuplicateKeyInBatch { key: key.clone() });
427 }
428 }
429
430 if commit_ts <= start_ts {
431 return Err(BatchCommitError::InvalidCommitTimestamp {
432 start_ts,
433 commit_ts,
434 });
435 }
436
437 let mut commits = Vec::with_capacity(keys.len());
438 let mut removed_intents = Vec::with_capacity(keys.len());
439
440 for key in &keys {
441 let intent = self
442 .backend
443 .get_intent(key)
444 .map_err(BatchCommitError::Backend)?
445 .ok_or_else(|| BatchCommitError::IntentNotFound { key: key.clone() })?;
446
447 if intent.txn_id != txn_id {
448 return Err(BatchCommitError::TxnIdMismatch { key: key.clone() });
449 }
450 if intent.start_ts != start_ts {
451 return Err(BatchCommitError::StartTsMismatch { key: key.clone() });
452 }
453 if let Some(min_ts) = intent.min_commit_ts
454 && commit_ts < min_ts
455 {
456 return Err(BatchCommitError::CommitTsTooEarly {
457 key: key.clone(),
458 commit_ts,
459 min_commit_ts: min_ts,
460 });
461 }
462
463 if let Some(latest_ts) = self
464 .backend
465 .get_latest_commit_ts(key)
466 .map_err(BatchCommitError::Backend)?
467 && commit_ts <= latest_ts
468 {
469 return Err(BatchCommitError::CommitTsTooOld {
470 key: key.clone(),
471 commit_ts,
472 latest_commit_ts: latest_ts,
473 });
474 }
475
476 commits.push(CommittedVersion {
477 key: key.clone(),
478 commit_ts,
479 value: intent.mutation.value(),
480 });
481 removed_intents.push((key.clone(), txn_id, start_ts));
482 }
483
484 self.backend
485 .commit_intents_batch(commits, removed_intents)
486 .map_err(BatchCommitError::Backend)?;
487 Ok(())
488 }
489
490 pub fn abort_batch(
494 &mut self,
495 txn_id: TxnId,
496 start_ts: Timestamp,
497 keys: Vec<Vec<u8>>,
498 ) -> Result<(), BatchAbortError> {
499 if keys.is_empty() {
500 return Ok(());
501 }
502
503 let mut key_set = std::collections::HashSet::new();
504 for key in &keys {
505 if !key_set.insert(key.clone()) {
506 return Err(BatchAbortError::DuplicateKeyInBatch { key: key.clone() });
507 }
508 }
509
510 let mut removed_intents = Vec::with_capacity(keys.len());
511 for key in &keys {
512 if let Some(intent) = self
513 .backend
514 .get_intent(key)
515 .map_err(BatchAbortError::Backend)?
516 && intent.txn_id == txn_id
517 && intent.start_ts == start_ts
518 {
519 removed_intents.push((key.clone(), txn_id, start_ts));
520 }
521 }
522
523 self.backend
524 .remove_intents_batch(removed_intents)
525 .map_err(BatchAbortError::Backend)?;
526 Ok(())
527 }
528
529 pub fn apply_direct_batch(
534 &mut self,
535 commit_ts: Timestamp,
536 writes: Vec<PhysicalWrite>,
537 ) -> Result<(), BatchError> {
538 if writes.is_empty() {
539 return Err(BatchError::EmptyBatch);
540 }
541
542 let mut key_set = std::collections::HashSet::new();
543 for w in &writes {
544 if !key_set.insert(w.key.clone()) {
545 return Err(BatchError::DuplicateKeyInBatch { key: w.key.clone() });
546 }
547 }
548
549 for w in &writes {
551 if let Some(intent) = self
553 .backend
554 .get_intent(&w.key)
555 .map_err(BatchError::Backend)?
556 {
557 return Err(BatchError::KeyLocked {
558 key: w.key.clone(),
559 txn_id: intent.txn_id,
560 });
561 }
562
563 if let Some(latest_ts) = self
565 .backend
566 .get_latest_commit_ts(&w.key)
567 .map_err(BatchError::Backend)?
568 && commit_ts <= latest_ts
569 {
570 return Err(BatchError::CommitTsTooOld {
571 key: w.key.clone(),
572 commit_ts,
573 latest_commit_ts: latest_ts,
574 });
575 }
576 }
577
578 let mut commits = Vec::with_capacity(writes.len());
580 for w in writes {
581 commits.push(CommittedVersion {
582 key: w.key,
583 commit_ts,
584 value: w.value,
585 });
586 }
587
588 self.backend
589 .put_committed_batch(commits)
590 .map_err(BatchError::Backend)?;
591 Ok(())
592 }
593
594 pub fn apply_guarded_batch(
599 &mut self,
600 commit_ts: Timestamp,
601 guards: Vec<ReadGuard>,
602 writes: Vec<PhysicalWrite>,
603 ) -> Result<(), BatchError> {
604 if writes.is_empty() {
605 return Err(BatchError::EmptyBatch);
606 }
607 if guards.is_empty() {
608 return Err(BatchError::NoReadGuards);
609 }
610
611 let mut write_keys = std::collections::HashSet::new();
612 for w in &writes {
613 if !write_keys.insert(w.key.clone()) {
614 return Err(BatchError::DuplicateKeyInBatch { key: w.key.clone() });
615 }
616 }
617
618 for guard in &guards {
620 let (guard_key, guard_read_ts) = match guard {
621 ReadGuard::ExpectedVersion { key, read_ts, .. } => (key, read_ts),
622 ReadGuard::ExpectedValue { key, read_ts, .. } => (key, read_ts),
623 };
624
625 if commit_ts <= *guard_read_ts {
626 return Err(BatchError::InvalidCommitTimestamp {
627 read_ts: *guard_read_ts,
628 commit_ts,
629 });
630 }
631
632 if let Some(intent) = self
634 .backend
635 .get_intent(guard_key)
636 .map_err(BatchError::Backend)?
637 {
638 return Err(BatchError::KeyLocked {
639 key: guard_key.clone(),
640 txn_id: intent.txn_id,
641 });
642 }
643
644 if let Some(latest_ts) = self
645 .backend
646 .get_latest_commit_ts(guard_key)
647 .map_err(BatchError::Backend)?
648 && latest_ts > *guard_read_ts
649 {
650 return Err(BatchError::GuardFailedNewerVersion {
651 key: guard_key.clone(),
652 read_ts: *guard_read_ts,
653 actual_commit_ts: latest_ts,
654 });
655 }
656
657 let visible_version = self
658 .backend
659 .get_visible_committed(guard_key, *guard_read_ts)
660 .map_err(BatchError::Backend)?;
661
662 match guard {
663 ReadGuard::ExpectedVersion {
664 expected_commit_ts, ..
665 } => {
666 let actual_commit_ts = visible_version.as_ref().map(|v| v.commit_ts);
667 if actual_commit_ts != *expected_commit_ts {
668 return Err(BatchError::GuardFailedVersionMismatch {
669 key: guard_key.clone(),
670 expected: *expected_commit_ts,
671 actual: actual_commit_ts,
672 });
673 }
674 }
675 ReadGuard::ExpectedValue { expected_value, .. } => {
676 let actual_value = visible_version.as_ref().and_then(|v| v.value.as_ref());
677 if actual_value != expected_value.as_ref() {
678 return Err(BatchError::GuardFailedValueMismatch {
679 key: guard_key.clone(),
680 });
681 }
682 }
683 }
684 }
685
686 for w in &writes {
688 if let Some(intent) = self
689 .backend
690 .get_intent(&w.key)
691 .map_err(BatchError::Backend)?
692 {
693 return Err(BatchError::KeyLocked {
694 key: w.key.clone(),
695 txn_id: intent.txn_id,
696 });
697 }
698
699 if let Some(latest_ts) = self
700 .backend
701 .get_latest_commit_ts(&w.key)
702 .map_err(BatchError::Backend)?
703 && commit_ts <= latest_ts
704 {
705 return Err(BatchError::CommitTsTooOld {
706 key: w.key.clone(),
707 commit_ts,
708 latest_commit_ts: latest_ts,
709 });
710 }
711 }
712
713 let mut commits = Vec::with_capacity(writes.len());
715 for w in writes {
716 commits.push(CommittedVersion {
717 key: w.key,
718 commit_ts,
719 value: w.value,
720 });
721 }
722
723 self.backend
724 .put_committed_batch(commits)
725 .map_err(BatchError::Backend)?;
726 Ok(())
727 }
728
729 pub fn plan_key_gc(
742 &self,
743 key: &[u8],
744 safe_point_ts: Timestamp,
745 options: KeyGcOptions,
746 ) -> Result<KeyGcPlan, GcError> {
747 if options.max_versions_examined == 0 {
748 return Err(GcError::InvalidKeyGcBudget);
749 }
750
751 let Some(keeper) = self
752 .backend
753 .get_visible_committed(key, safe_point_ts)
754 .map_err(GcError::Backend)?
755 else {
756 return Ok(KeyGcPlan {
757 key: key.to_vec(),
758 versions_to_remove: Vec::new(),
759 collapse_tombstone: None,
760 versions_examined: 0,
761 complete: true,
762 });
763 };
764
765 let collapse_final_tombstone = if options.collapse_final_tombstones
766 && keeper.value.is_none()
767 && self
768 .backend
769 .get_intent(key)
770 .map_err(GcError::Backend)?
771 .is_none()
772 {
773 self.backend
774 .get_latest_commit_ts(key)
775 .map_err(GcError::Backend)?
776 == Some(keeper.commit_ts)
777 } else {
778 false
779 };
780
781 let mut versions_to_remove = self
782 .backend
783 .get_committed_timestamps_before(key, keeper.commit_ts, options.max_versions_examined)
784 .map_err(GcError::Backend)?;
785
786 let complete = versions_to_remove.len() < options.max_versions_examined;
787 versions_to_remove.truncate(options.max_versions_examined);
788 let versions_examined = versions_to_remove.len();
789
790 Ok(KeyGcPlan {
791 key: key.to_vec(),
792 versions_to_remove,
793 collapse_tombstone: collapse_final_tombstone
794 .then_some(keeper.commit_ts)
795 .filter(|_| complete),
796 versions_examined,
797 complete,
798 })
799 }
800
801 pub fn gc_incremental(
805 &mut self,
806 safe_point_ts: Timestamp,
807 cursor: Option<IncrementalGcCursor>,
808 options: GcOptions,
809 ) -> Result<IncrementalGcResult, GcError> {
810 if options.budget.max_keys == 0 || options.budget.max_versions == 0 {
811 return Err(GcError::InvalidGcBudget);
812 }
813
814 let start_key = cursor.and_then(|c| c.next_key);
815
816 let keys = self
817 .backend
818 .keys_from(start_key.as_deref(), options.budget.max_keys + 1)
819 .map_err(GcError::Backend)?;
820
821 let mut keys_scanned = 0;
822 let mut versions_scanned = 0;
823 let mut versions_removed = 0;
824 let mut intents_preserved = 0;
825
826 let mut next_cursor_key = None;
827 let mut done = false;
828 let mut exhausted_versions = false;
829
830 let num_keys_to_process = std::cmp::min(keys.len(), options.budget.max_keys);
831
832 for key in keys.iter().take(num_keys_to_process) {
833 keys_scanned += 1;
834
835 let has_intent = self
836 .backend
837 .get_intent(key)
838 .map_err(GcError::Backend)?
839 .is_some();
840 if has_intent {
841 intents_preserved += 1;
842 }
843
844 let keeper = self
845 .backend
846 .get_visible_committed(key, safe_point_ts)
847 .map_err(GcError::Backend)?;
848
849 if let Some(keeper_ver) = keeper {
850 versions_scanned += 1; let limit = options.budget.max_versions - versions_removed;
853 if limit == 0 {
854 let check_more = self
856 .backend
857 .get_committed_timestamps_before(key, keeper_ver.commit_ts, 1)
858 .map_err(GcError::Backend)?;
859
860 if !check_more.is_empty() {
861 next_cursor_key = Some(key.clone());
862 exhausted_versions = true;
863 break;
864 }
865
866 if options.collapse_final_tombstones
869 && keeper_ver.value.is_none()
870 && !has_intent
871 && let Some(latest_ts) = self
872 .backend
873 .get_latest_commit_ts(key)
874 .map_err(GcError::Backend)?
875 && latest_ts == keeper_ver.commit_ts
876 {
877 next_cursor_key = Some(key.clone());
878 exhausted_versions = true;
879 break;
880 }
881 continue;
882 }
883
884 let mut is_final_tombstone = false;
886 if options.collapse_final_tombstones
887 && keeper_ver.value.is_none()
888 && !has_intent
889 && let Some(latest_ts) = self
890 .backend
891 .get_latest_commit_ts(key)
892 .map_err(GcError::Backend)?
893 && latest_ts == keeper_ver.commit_ts
894 {
895 is_final_tombstone = true;
896 }
897
898 if is_final_tombstone {
899 let mut older_versions = self
902 .backend
903 .get_committed_timestamps_before(key, keeper_ver.commit_ts, limit + 1)
904 .map_err(GcError::Backend)?;
905
906 versions_scanned += older_versions.len();
907
908 let has_more = older_versions.len() > limit;
909 if has_more {
910 older_versions.pop();
913 versions_scanned -= 1;
914
915 for ts in older_versions {
918 self.backend
919 .remove_committed_version(key, ts)
920 .map_err(GcError::Backend)?;
921 versions_removed += 1;
922 }
923
924 next_cursor_key = Some(key.clone());
925 exhausted_versions = true;
926 break;
927 } else {
928 let older_len = older_versions.len();
931 if older_len < limit {
932 self.backend
934 .collapse_tombstone(key, keeper_ver.commit_ts, older_versions)
935 .map_err(GcError::Backend)?;
936
937 versions_removed += older_len + 1;
938 } else {
939 for ts in older_versions {
943 self.backend
944 .remove_committed_version(key, ts)
945 .map_err(GcError::Backend)?;
946 versions_removed += 1;
947 }
948
949 next_cursor_key = Some(key.clone());
950 exhausted_versions = true;
951 break;
952 }
953 }
954 } else {
955 let mut to_remove = self
957 .backend
958 .get_committed_timestamps_before(key, keeper_ver.commit_ts, limit + 1)
959 .map_err(GcError::Backend)?;
960
961 versions_scanned += to_remove.len();
962
963 let has_more = to_remove.len() > limit;
964 if has_more {
965 to_remove.pop();
966 versions_scanned -= 1;
967 }
968
969 for ts in to_remove {
970 self.backend
971 .remove_committed_version(key, ts)
972 .map_err(GcError::Backend)?;
973 versions_removed += 1;
974 }
975
976 if has_more {
977 next_cursor_key = Some(key.clone());
978 exhausted_versions = true;
979 break;
980 }
981 }
982 }
983 }
984
985 if !exhausted_versions {
986 if keys.len() > options.budget.max_keys {
987 next_cursor_key = Some(keys[options.budget.max_keys].clone());
988 } else {
989 done = true;
990 }
991 }
992
993 Ok(IncrementalGcResult {
994 cursor: IncrementalGcCursor {
995 next_key: next_cursor_key,
996 },
997 done,
998 keys_scanned,
999 versions_scanned,
1000 versions_removed,
1001 intents_preserved,
1002 })
1003 }
1004
1005 #[deprecated(
1019 note = "unbounded: loops gc_incremental to completion and materializes the whole \
1020 keyspace via all_keys(); production must use budgeted gc_incremental"
1021 )]
1022 pub fn gc(&mut self, safe_point_ts: Timestamp, options: GcOptions) -> Result<GcStats, GcError> {
1023 let mut total_versions_removed = 0;
1024
1025 let mut cursor = None;
1026
1027 loop {
1028 let res = self.gc_incremental(safe_point_ts, cursor.take(), options)?;
1029 total_versions_removed += res.versions_removed;
1030
1031 if res.done {
1032 break;
1033 }
1034 cursor = Some(res.cursor);
1035 }
1036
1037 let mut total_intents_preserved = 0;
1038 for key in self.backend.all_keys().map_err(GcError::Backend)? {
1039 if self
1040 .backend
1041 .get_intent(&key)
1042 .map_err(GcError::Backend)?
1043 .is_some()
1044 {
1045 total_intents_preserved += 1;
1046 }
1047 }
1048
1049 Ok(GcStats {
1050 versions_removed: total_versions_removed,
1051 intents_preserved: total_intents_preserved,
1052 })
1053 }
1054}