1pub mod field_row;
6mod gc;
7pub mod handle;
8pub mod ingest;
9#[cfg(feature = "std")]
10pub(crate) mod links;
11pub(crate) mod overwritten;
12pub(crate) mod released;
13
14#[doc(hidden)]
15pub use gc::{FragmentationEntry, FragmentationMap};
16
17use crate::path::PathBuf;
18use crate::tree::inner::{FlushGuard, VersionsWriteGuard};
19use crate::{
20 Config, Memtable, ScanSinceEvent, SeqNo, TableId, TreeId, UserKey, UserValue,
21 abstract_tree::{AbstractTree, RangeItem},
22 coding::Decode,
23 iter_guard::{IterGuard, IterGuardImpl},
24 table::Table,
25 tree::inner::MemtableId,
26 value::InternalValue,
27 version::Version,
28 vlog::{Accessor, BlobFile, BlobFileWriter},
29};
30use alloc::sync::Arc;
31#[cfg(not(feature = "std"))]
32use alloc::{boxed::Box, string::ToString, vec::Vec};
33use core::ops::RangeBounds;
34use handle::BlobIndirection;
35
36pub struct Guard {
38 tree: crate::BlobTree,
39 version: Version,
40 fill: crate::config::BlobCacheFill,
43 prefetched: Vec<(crate::vlog::ValueHandle, UserValue)>,
46 kv: crate::Result<InternalValue>,
47 prefetch: Option<Arc<ScanPrefetch>>,
50 _held: Option<HeldReadAhead>,
53}
54
55struct HeldReadAhead {
58 state: Arc<ScanPrefetch>,
59 bytes: usize,
60}
61
62impl Drop for HeldReadAhead {
63 fn drop(&mut self) {
64 self.state
65 .held
66 .fetch_sub(self.bytes, core::sync::atomic::Ordering::Relaxed);
67 }
68}
69
70impl IterGuard for Guard {
71 fn into_inner_if(
72 self,
73 pred: impl Fn(&UserKey) -> bool,
74 ) -> crate::Result<(UserKey, Option<UserValue>)> {
75 let kv = self.kv?;
76
77 if pred(&kv.key.user_key) {
86 resolve_value_handle(
87 self.tree.id(),
88 &self.tree.index.config.cache,
89 self.fill,
90 &self.prefetched,
91 &self.version,
92 #[cfg(feature = "metrics")]
93 self.tree.metrics(),
94 kv,
95 )
96 .map(|(k, v)| (k, Some(v)))
97 } else {
98 Ok((kv.key.user_key, None))
99 }
100 }
101
102 fn key(self) -> crate::Result<UserKey> {
103 self.kv.map(|kv| kv.key.user_key)
104 }
105
106 fn size(self) -> crate::Result<u32> {
107 let kv = self.kv?;
108
109 if kv.key.value_type.is_indirection() {
110 let mut cursor = crate::io::Cursor::new(kv.value);
111 Ok(BlobIndirection::decode_from(&mut cursor)?.size)
112 } else if kv.key.value_type.is_cell_row() {
113 field_row::logical_len(&kv.value)
114 } else {
115 #[expect(clippy::cast_possible_truncation, reason = "values are u32 max length")]
116 Ok(kv.value.len() as u32)
117 }
118 }
119
120 fn into_inner(self) -> crate::Result<(UserKey, UserValue)> {
121 if let Some(p) = &self.prefetch {
122 p.note_resolved();
123 }
124 resolve_value_handle(
125 self.tree.id(),
126 &self.tree.index.config.cache,
127 self.fill,
128 &self.prefetched,
129 &self.version,
130 #[cfg(feature = "metrics")]
131 self.tree.metrics(),
132 self.kv?,
133 )
134 }
135}
136
137fn prefetched_value(
140 prefetched: &[(crate::vlog::ValueHandle, UserValue)],
141 vhandle: &crate::vlog::ValueHandle,
142) -> Option<UserValue> {
143 prefetched
144 .iter()
145 .find(|(held, _)| {
146 held.blob_file_id == vhandle.blob_file_id
147 && held.offset == vhandle.offset
148 && held.on_disk_size == vhandle.on_disk_size
149 })
150 .map(|(_, value)| value.clone())
151}
152
153pub(crate) fn resolve_value_handle(
154 tree_id: TreeId,
155 cache: &crate::Cache,
156 fill: crate::config::BlobCacheFill,
157 prefetched: &[(crate::vlog::ValueHandle, UserValue)],
158 version: &Version,
159 #[cfg(feature = "metrics")] metrics: &crate::metrics::Metrics,
160 item: InternalValue,
161) -> RangeItem {
162 if item.key.value_type.is_cell_row() {
163 let accessor = Accessor::new(
164 &version.blob_files,
165 fill,
166 #[cfg(feature = "metrics")]
167 Some(metrics),
168 );
169 let value = field_row::resolve_row(&item.value, |vptr| {
170 if let Some(value) = prefetched_value(prefetched, &vptr.vhandle) {
171 return Ok(value);
172 }
173 accessor
176 .get(tree_id, &item.key.user_key, &vptr.vhandle, cache)?
177 .ok_or(crate::Error::InvalidHeader(
178 "field row: a reference names a blob file the version does not hold",
179 ))
180 })?;
181 return Ok((item.key.user_key, UserValue::from(value)));
182 }
183 if item.key.value_type.is_indirection() {
184 let mut cursor = crate::io::Cursor::new(item.value);
185 let vptr = BlobIndirection::decode_from(&mut cursor)?;
186 if let Some(value) = prefetched_value(prefetched, &vptr.vhandle) {
187 return Ok((item.key.user_key, value));
188 }
189
190 let accessor = Accessor::new(
194 &version.blob_files,
195 fill,
196 #[cfg(feature = "metrics")]
197 Some(metrics),
198 );
199
200 let value = accessor
203 .get(tree_id, &item.key.user_key, &vptr.vhandle, cache)?
204 .ok_or(crate::Error::InvalidHeader(
205 "blob tree: an indirection names a blob file the version does not hold",
206 ))?;
207 Ok((item.key.user_key, value))
208 } else {
209 let k = item.key.user_key;
210 let v = item.value;
211 Ok((k, v))
212 }
213}
214
215pub(crate) enum ValuePlan<'v> {
217 Ready(UserValue),
219 Read(ValueRead<'v>),
221 Alone,
223}
224
225pub(crate) struct ValueRead<'v> {
228 pub(crate) blob_file: &'v BlobFile,
229 pub(crate) vhandle: crate::vlog::ValueHandle,
230 pub(crate) len: usize,
231}
232
233#[derive(Clone)]
236pub(crate) struct BlobSource {
237 pub(crate) tree_id: TreeId,
238 pub(crate) cache: Arc<crate::Cache>,
239 pub(crate) fill: crate::config::BlobCacheFill,
242 #[cfg(feature = "metrics")]
243 pub(crate) metrics: Arc<crate::metrics::Metrics>,
244}
245
246impl BlobSource {
247 pub(crate) fn value(&self, version: &Version, item: InternalValue) -> crate::Result<UserValue> {
250 resolve_value_handle(
251 self.tree_id,
252 &self.cache,
253 self.fill,
254 &[],
255 version,
256 #[cfg(feature = "metrics")]
257 &self.metrics,
258 item,
259 )
260 .map(|(_, value)| value)
261 }
262
263 pub(crate) fn plan_value<'v>(
269 &self,
270 version: &'v Version,
271 item: &InternalValue,
272 ) -> ValuePlan<'v> {
273 if !item.key.value_type.is_indirection() {
274 return ValuePlan::Alone;
275 }
276 let mut cursor = crate::io::Cursor::new(&item.value[..]);
277 let Ok(indirection) = BlobIndirection::decode_from(&mut cursor) else {
278 return ValuePlan::Alone;
279 };
280 let vhandle = indirection.vhandle;
281 if let Some(value) = self
282 .cache
283 .get_blob(self.tree_id, &vhandle, &item.key.user_key)
284 {
285 return ValuePlan::Ready(value);
286 }
287 let (Some(blob_file), Ok(len)) = (
288 version.blob_files.get(vhandle.blob_file_id),
289 crate::vlog::blob_file::reader::record_len(item.key.user_key.len(), &vhandle),
290 ) else {
291 return ValuePlan::Alone;
292 };
293 ValuePlan::Read(ValueRead {
294 blob_file,
295 vhandle,
296 len,
297 })
298 }
299
300 pub(crate) fn open_blob_file(
303 &self,
304 blob_file: &BlobFile,
305 ) -> crate::Result<Arc<dyn crate::fs::FsFile>> {
306 let id = crate::GlobalTableId::from((self.tree_id, blob_file.id()));
307 Ok(blob_file
308 .file_accessor()
309 .get_or_open_blob_file(&id, &blob_file.0.path)?
310 .0)
311 }
312
313 pub(crate) fn finish_value(
322 &self,
323 version: &Version,
324 key: &[u8],
325 read: &ValueRead<'_>,
326 file: &dyn crate::fs::FsFile,
327 bytes: &crate::Slice,
328 ) -> crate::Result<UserValue> {
329 let accessor = Accessor::new(
330 &version.blob_files,
331 self.fill,
332 #[cfg(feature = "metrics")]
333 Some(&self.metrics),
334 );
335 let reader = crate::vlog::blob_file::reader::Reader::new(read.blob_file, file);
336 let mut decoded = 0;
337 let parsed = reader.parse_record(key, &read.vhandle, bytes, &mut decoded);
338 accessor.count_decoded(decoded);
339 let value = parsed?;
340 if self.fill.fills() {
341 let detached = self
342 .cache
343 .insert_blob(self.tree_id, &read.vhandle, key, value.clone());
344 accessor.count_gather(detached);
345 }
346 Ok(value)
347 }
348
349 pub(crate) fn count_issued(&self, version: &Version, read: &ValueRead<'_>) {
351 Accessor::new(
352 &version.blob_files,
353 self.fill,
354 #[cfg(feature = "metrics")]
355 Some(&self.metrics),
356 )
357 .count_request(read.len);
358 }
359
360 pub(crate) fn object(
369 &self,
370 version: &Version,
371 key: &[u8],
372 indirection: &BlobIndirection,
373 ) -> crate::Result<UserValue> {
374 Accessor::new(
375 &version.blob_files,
376 self.fill,
377 #[cfg(feature = "metrics")]
378 Some(&self.metrics),
379 )
380 .get(self.tree_id, key, &indirection.vhandle, &self.cache)?
381 .ok_or(crate::Error::InvalidHeader(
382 "field row: a reference names a blob file the version does not hold",
383 ))
384 }
385
386 #[cfg(feature = "columnar")]
389 pub(crate) fn object_prefetched(
390 &self,
391 version: &Version,
392 key: &[u8],
393 indirection: &BlobIndirection,
394 local: &mut crate::vlog::Prefetched,
395 ) -> crate::Result<UserValue> {
396 match local.take(key, &indirection.vhandle) {
397 Some(object) => Ok(object),
398 None => self.object(version, key, indirection),
399 }
400 }
401
402 #[cfg(feature = "columnar")]
409 pub(crate) fn prefetch(
410 &self,
411 version: &Version,
412 items: &mut Vec<(&[u8], crate::vlog::ValueHandle, usize)>,
413 local: &mut crate::vlog::Prefetched,
414 ) {
415 let into = if self.fill.fills() {
416 crate::vlog::PrefetchInto::Cache
417 } else {
418 crate::vlog::PrefetchInto::Local(local)
419 };
420 Accessor::new(
421 &version.blob_files,
422 self.fill,
423 #[cfg(feature = "metrics")]
424 Some(&self.metrics),
425 )
426 .prefetch(
427 self.tree_id,
428 items,
429 &self.cache,
430 PREFETCH_MAX_GAP,
431 PREFETCH_MAX_READ,
432 into,
433 );
434 }
435}
436
437#[derive(Clone)]
447pub struct BlobTree {
448 #[doc(hidden)]
450 pub index: crate::Tree,
451
452 pub(crate) blobs_folder: Arc<PathBuf>,
453}
454
455impl BlobTree {
456 #[must_use]
458 pub fn downgrade(&self) -> crate::WeakTree {
459 crate::WeakTree::of(&self.index, Some(Arc::clone(&self.blobs_folder)))
460 }
461
462 pub fn close(self) -> crate::TreeClose {
466 crate::close::close(self.index)
467 }
468
469 fn full_compaction_blob_need(&self, version: &crate::version::Version) -> crate::Result<u64> {
474 let Some(blob_opts) = &self.index.config.kv_separation_opts else {
475 return Ok(0);
476 };
477 let all_tables: crate::HashSet<TableId> = version.iter_tables().map(Table::id).collect();
478 crate::compaction::worker::pick_blob_files_to_rewrite(&all_tables, version, blob_opts)?
479 .iter()
480 .try_fold(0u64, |acc, bf| bf.physical_size().map(|size| acc + size))
481 }
482
483 pub(crate) fn open(
487 config: Config,
488 #[cfg(feature = "std")] lock: crate::config::DirectoryLock,
489 names: crate::tree::NewFolderNames,
490 ) -> crate::Result<(Self, Option<crate::tree::Creation>)> {
491 use crate::file::BLOBS_FOLDER;
492
493 let (index, creation) = crate::Tree::open(
494 config,
495 #[cfg(feature = "std")]
496 lock,
497 names,
498 )?;
499
500 let blobs_folder = index.config.path.join(BLOBS_FOLDER);
512 let folder = (|| -> crate::Result<()> {
513 let made = !(*index.config.fs).exists(&blobs_folder)?;
514 (*index.config.fs).create_dir_all(&blobs_folder)?;
515 if made {
516 crate::file::fsync_directory(
517 &index.config.path,
518 &*index.config.fs,
519 index.config.sync_mode,
520 )?;
521 }
522 Ok(())
523 })();
524 if let Err(e) = folder {
527 if let Some(creation) = creation {
528 creation.take_back(index);
529 }
530 return Err(e);
531 }
532
533 let blob_file_id_to_continue_with = index
534 .current_version()
535 .blob_files
536 .list_ids()
537 .max()
538 .map(|x| x + 1)
539 .unwrap_or_default();
540
541 index
542 .0
543 .blob_file_id_counter
544 .set(blob_file_id_to_continue_with);
545
546 Ok((
547 Self {
548 index,
549 blobs_folder: Arc::new(blobs_folder),
550 },
551 creation,
552 ))
553 }
554
555 fn resolve_key(
559 &self,
560 super_version: &crate::version::SuperVersion,
561 source: &BlobSource,
562 key: &[u8],
563 seqno: SeqNo,
564 ) -> crate::Result<Option<UserValue>> {
565 let Some(item) = crate::Tree::get_internal_entry_from_version(
566 super_version,
567 key,
568 seqno,
569 self.index.config.comparator.as_ref(),
570 )?
571 else {
572 return Ok(None);
573 };
574 if item.key.value_type.is_merge_operand()
578 && let Some(merge_operator) = &self.index.config.merge_operator
579 {
580 return crate::Tree::resolve_point_merge(
581 super_version,
582 key,
583 seqno,
584 merge_operator,
585 Some(source),
586 );
587 }
588
589 source.value(&super_version.version, item).map(Some)
590 }
591
592 pub fn scan_since_seqno(
611 &self,
612 target_seqno: SeqNo,
613 ) -> crate::Result<impl Iterator<Item = ScanSinceEvent> + use<>> {
614 let fill = self.blob_cache_fill();
615 self.index
616 .scan_since_seqno_with(target_seqno, true, |version, entry| {
617 let seqno = entry.key.seqno;
618 let (key, value) = resolve_value_handle(
619 self.id(),
620 &self.index.config.cache,
621 fill,
622 &[],
623 version,
624 #[cfg(feature = "metrics")]
625 self.metrics(),
626 entry,
627 )?;
628 Ok(ScanSinceEvent::Insert { key, value, seqno })
629 })
630 }
631
632 pub fn start_multi_get<K: Into<UserKey>>(
641 &self,
642 keys: impl IntoIterator<Item = K>,
643 seqno: SeqNo,
644 ) -> crate::Result<crate::resumable::Step> {
645 let snapshot = self.index.snapshot_for_read(seqno)?;
646 let seqno = crate::Tree::read_ceiling(&snapshot, seqno);
647 let keys: Vec<UserKey> = keys.into_iter().map(Into::into).collect();
648 snapshot
651 .version
652 .read_mix()
653 .note_points(u64::try_from(keys.len()).unwrap_or(u64::MAX));
654 let source = self.blob_source();
657 Ok(crate::resumable::ResumableMultiGet::start(
658 crate::tree::read_job::ReadCtx {
659 super_version: crate::version::SnapshotRef::Owned((*snapshot).clone()),
660 keys,
661 seqno,
662 comparator: Arc::clone(&self.index.config.comparator),
663 merge_operator: self.index.config.merge_operator.clone(),
664 merge_base: Some(source.clone()),
665 values: crate::tree::read_job::Values::Blob(source),
666 metadata_budget: self.index.config.multi_get_metadata_budget,
667 },
668 ))
669 }
670
671 fn blob_cache_fill(&self) -> crate::config::BlobCacheFill {
674 self.index.runtime_config.load().blob_cache_fill
675 }
676
677 fn blob_source(&self) -> BlobSource {
679 BlobSource {
680 tree_id: self.id(),
681 cache: Arc::clone(&self.index.config.cache),
682 fill: self.blob_cache_fill(),
683 #[cfg(feature = "metrics")]
684 metrics: Arc::clone(self.metrics()),
685 }
686 }
687
688 pub fn scan_since_seqno_in_range<K: AsRef<[u8]>, R: core::ops::RangeBounds<K>>(
702 &self,
703 target_seqno: SeqNo,
704 range: R,
705 ) -> crate::Result<impl Iterator<Item = ScanSinceEvent> + use<K, R>> {
706 let bounds = crate::tree::range_to_user_bounds(&range);
707 let fill = self.blob_cache_fill();
708 self.index.scan_since_seqno_scoped(
709 target_seqno,
710 true,
711 |version, entry| {
712 let seqno = entry.key.seqno;
713 let (key, value) = resolve_value_handle(
714 self.id(),
715 &self.index.config.cache,
716 fill,
717 &[],
718 version,
719 #[cfg(feature = "metrics")]
720 self.metrics(),
721 entry,
722 )?;
723 Ok(ScanSinceEvent::Insert { key, value, seqno })
724 },
725 Some(&bounds),
726 )
727 }
728
729 pub fn insert_cells<K: Into<UserKey>>(
785 &self,
786 key: K,
787 fields: &[field_row::Field<'_>],
788 seqno: SeqNo,
789 ) -> crate::Result<(u64, u64)> {
790 let key = key.into();
791 let mut row = Vec::with_capacity(fields.len());
792 for field in fields {
793 let cell = match &field.cell {
794 field_row::Cell::Value(bytes) => field_row::RowCell::Value(bytes),
795 field_row::Cell::Ref(reference) => {
796 if reference.tree != self.id() {
797 return Err(crate::Error::BlobRef("reference read from another tree"));
798 }
799 if !crate::comparator::same_user_key(reference.key, &key) {
800 return Err(crate::Error::BlobRef("reference read from another key"));
801 }
802 field_row::RowCell::Ref {
806 indirection: reference.indirection,
807 owner: false,
808 }
809 }
810 };
811 row.push(field_row::RowField {
812 column: field.column,
813 tag: field.tag,
814 cell,
815 });
816 }
817 field_row::order_fields(&mut row)?;
818 let refs = fields.iter().filter_map(|field| match &field.cell {
819 field_row::Cell::Ref(reference) => Some(reference),
820 field_row::Cell::Value(_) => None,
821 });
822 for (i, a) in refs.clone().enumerate() {
823 if refs.clone().skip(i + 1).any(|b| a == b) {
824 return Err(crate::Error::BlobRef("one object in two fields"));
825 }
826 }
827 let value = field_row::encode_row(&row)?;
828 let sourced: Vec<(BlobIndirection, u64)> = refs
829 .map(|reference| (reference.indirection, reference.source))
830 .collect();
831 #[cfg(feature = "metrics")]
834 let payload = key.len()
835 + fields
836 .iter()
837 .map(|field| match &field.cell {
838 field_row::Cell::Value(bytes) => bytes.len(),
839 field_row::Cell::Ref(_) => 0,
840 })
841 .sum::<usize>();
842 let written = self.index.append_cell_row(
843 InternalValue::from_components(key, value, seqno, crate::ValueType::CellRow),
844 &sourced,
845 )?;
846 #[cfg(feature = "metrics")]
849 self.index.0.metrics.accepted_value(payload);
850 Ok(written)
851 }
852
853 pub fn get_cells<K: AsRef<[u8]>>(
889 &self,
890 key: K,
891 seqno: SeqNo,
892 ) -> crate::Result<Option<field_row::RowCells>> {
893 let read = self.cell_read(seqno)?;
894 read.version.version.read_mix().note_points(1);
895 let Some(item) = crate::Tree::get_internal_entry_from_version(
896 &read.version,
897 key.as_ref(),
898 seqno,
899 self.index.config.comparator.as_ref(),
900 )?
901 else {
902 return Ok(None);
903 };
904 if !item.key.value_type.is_cell_row() {
905 return Err(crate::Error::BlobRef("the key's version is not a cell row"));
906 }
907 Ok(Some(field_row::RowCells {
908 key: item.key.user_key,
909 row: item.value,
910 read: Arc::new(read),
911 }))
912 }
913
914 pub fn range_cells<K: AsRef<[u8]>, R: RangeBounds<K>>(
957 &self,
958 range: R,
959 seqno: SeqNo,
960 ) -> crate::Result<
961 Box<dyn DoubleEndedIterator<Item = crate::Result<field_row::RowCells>> + Send + 'static>,
962 > {
963 let read = Arc::new(self.cell_read(seqno)?);
964 let rows = crate::Tree::create_internal_range_with_prefix_hash(
965 read.version.clone(),
966 &range,
967 seqno,
968 None,
969 self.index.config.merge_operator.clone(),
970 self.index.config.comparator.clone(),
971 None,
972 Some(self.blob_source()),
973 );
974 Ok(Box::new(rows.filter_map(move |item| match item {
975 Ok(item) if item.key.value_type.is_cell_row() => Some(Ok(field_row::RowCells {
976 key: item.key.user_key,
977 row: item.value,
978 read: Arc::clone(&read),
979 })),
980 Ok(_) => None,
981 Err(e) => Some(Err(e)),
982 })))
983 }
984
985 fn cell_read(&self, seqno: SeqNo) -> crate::Result<field_row::CellRead> {
990 let history = self.index.version_history.read();
991 let version = history.latest_version_ref().clone();
992 version.check_serves(seqno)?;
993 let token = history.released().register(version.version.id());
994 drop(history);
995 Ok(field_row::CellRead {
996 tree: self.id(),
997 version,
998 source: self.blob_source(),
999 token,
1000 })
1001 }
1002
1003 #[cfg(feature = "columnar")]
1054 pub fn columnar_scan<R: core::ops::RangeBounds<UserKey>>(
1055 &self,
1056 projection: impl Into<crate::Projection>,
1057 predicate: Option<&crate::table::columnar_predicate::ColumnRangePredicate>,
1058 seqno: SeqNo,
1059 range: R,
1060 ) -> crate::Result<crate::tree::columnar_scan::ColumnarScan> {
1061 self.index
1062 .columnar_scan(projection, predicate, seqno, range)
1063 }
1064
1065 pub fn update_runtime_config<F>(&self, mutator: F) -> crate::Result<()>
1080 where
1081 F: FnOnce(&mut crate::runtime_config::RuntimeConfig),
1082 {
1083 self.index.update_runtime_config(mutator)
1084 }
1085
1086 #[must_use]
1089 pub fn runtime_config(&self) -> Arc<crate::runtime_config::RuntimeConfig> {
1090 self.index.runtime_config()
1091 }
1092
1093 #[cfg(zstd_any)]
1102 pub fn register_zstd_dictionary(
1103 &self,
1104 dict: Arc<crate::compression::ZstdDictionary>,
1105 ) -> crate::Result<()> {
1106 self.index.register_zstd_dictionary(dict)
1107 }
1108}
1109
1110impl crate::abstract_tree::sealed::Sealed for BlobTree {}
1111
1112const PREFETCH_MAX_GAP: u64 = 4 * 1_024;
1117
1118const PREFETCH_MAX_READ: usize = 4 * 1_024 * 1_024;
1122
1123impl BlobTree {
1124 fn scan_prefetch_state(&self, fill: crate::config::BlobCacheFill) -> Arc<ScanPrefetch> {
1127 let window = self
1128 .index
1129 .config
1130 .kv_separation_opts
1131 .as_ref()
1132 .map_or(0, |o| o.scan_prefetch as usize);
1133
1134 Arc::new(ScanPrefetch {
1135 resolving: core::sync::atomic::AtomicBool::new(false),
1136 held: core::sync::atomic::AtomicUsize::new(0),
1137 fill,
1138 window,
1139 max_gap: PREFETCH_MAX_GAP,
1140 max_read: PREFETCH_MAX_READ,
1141 })
1142 }
1143}
1144
1145fn take_prefetched(
1148 local: &mut crate::vlog::Prefetched,
1149 kv: &InternalValue,
1150) -> Vec<(crate::vlog::ValueHandle, UserValue)> {
1151 let key = &kv.key.user_key;
1152 if kv.key.value_type.is_cell_row() {
1153 let Ok(refs) = field_row::row_refs(&kv.value) else {
1154 return Vec::new();
1155 };
1156 return refs
1157 .into_iter()
1158 .filter_map(|(vptr, _)| {
1159 local
1160 .take(key, &vptr.vhandle)
1161 .map(|value| (vptr.vhandle, value))
1162 })
1163 .collect();
1164 }
1165 if !kv.key.value_type.is_indirection() {
1166 return Vec::new();
1167 }
1168 let mut cursor = crate::io::Cursor::new(&kv.value[..]);
1169 let Ok(vptr) = BlobIndirection::decode_from(&mut cursor) else {
1170 return Vec::new();
1171 };
1172 local
1173 .take(key, &vptr.vhandle)
1174 .map(|value| alloc::vec![(vptr.vhandle, value)])
1175 .unwrap_or_default()
1176}
1177
1178fn blob_guard(
1181 tree: &crate::BlobTree,
1182 version: &Version,
1183 fill: crate::config::BlobCacheFill,
1184 item: crate::Result<InternalValue>,
1185) -> IterGuardImpl {
1186 IterGuardImpl::Blob(Guard {
1187 tree: tree.clone(),
1188 version: version.clone(),
1189 fill,
1190 prefetched: Vec::new(),
1191 kv: item,
1192 prefetch: None,
1193 _held: None,
1194 })
1195}
1196
1197pub(crate) struct ScanPrefetch {
1208 resolving: core::sync::atomic::AtomicBool,
1209 held: core::sync::atomic::AtomicUsize,
1213 fill: crate::config::BlobCacheFill,
1216 window: usize,
1218 max_gap: u64,
1220 max_read: usize,
1222}
1223
1224impl ScanPrefetch {
1225 pub(crate) fn note_resolved(&self) {
1227 self.resolving
1228 .store(true, core::sync::atomic::Ordering::Relaxed);
1229 }
1230
1231 fn armed(&self) -> bool {
1235 self.window > 0 && self.resolving.load(core::sync::atomic::Ordering::Relaxed)
1236 }
1237}
1238
1239struct PrefetchScan<I> {
1247 inner: I,
1248 buf: alloc::collections::VecDeque<crate::Result<InternalValue>>,
1250 inner_back_done: bool,
1253 armed: bool,
1257 state: Arc<ScanPrefetch>,
1258 tree: crate::BlobTree,
1259 version: Version,
1260 local: crate::vlog::Prefetched,
1263}
1264
1265impl<I: Iterator<Item = crate::Result<InternalValue>>> PrefetchScan<I> {
1266 fn fill(&mut self) {
1268 self.local.clear();
1271 self.buf.reserve(self.state.window);
1275 for _ in 0..self.state.window {
1276 match self.inner.next() {
1277 Some(item) => self.buf.push_back(item),
1278 None => break,
1279 }
1280 }
1281
1282 let mut handles: Vec<(&[u8], crate::vlog::ValueHandle, usize)> =
1292 Vec::with_capacity(self.buf.len());
1293 for item in &self.buf {
1294 let Ok(kv) = item else { continue };
1295 if kv.key.value_type.is_cell_row() {
1296 if let Ok(refs) = field_row::row_refs(&kv.value) {
1299 for (vptr, _) in refs {
1300 handles.push((&kv.key.user_key, vptr.vhandle, 0));
1301 }
1302 }
1303 continue;
1304 }
1305 if !kv.key.value_type.is_indirection() {
1306 continue;
1307 }
1308 let mut cursor = crate::io::Cursor::new(&kv.value[..]);
1309 let Ok(vptr) = BlobIndirection::decode_from(&mut cursor) else {
1310 continue;
1311 };
1312 handles.push((&kv.key.user_key, vptr.vhandle, 0));
1313 }
1314
1315 if handles.len() < 2 {
1316 return;
1317 }
1318
1319 let accessor = Accessor::new(
1320 &self.version.blob_files,
1321 self.state.fill,
1322 #[cfg(feature = "metrics")]
1323 Some(self.tree.metrics()),
1324 );
1325
1326 let into = if self.state.fill.fills() {
1333 crate::vlog::PrefetchInto::Cache
1334 } else if self.state.held.load(core::sync::atomic::Ordering::Relaxed) >= self.state.max_read
1335 {
1336 return;
1337 } else {
1338 crate::vlog::PrefetchInto::Local(&mut self.local)
1339 };
1340 accessor.prefetch(
1341 self.tree.id(),
1342 &mut handles,
1343 &self.tree.index.config.cache,
1344 self.state.max_gap,
1345 self.state.max_read,
1346 into,
1347 );
1348 }
1349
1350 fn guard(&mut self, item: crate::Result<InternalValue>) -> IterGuardImpl {
1351 let prefetched = match &item {
1352 Ok(kv) if !self.local.is_empty() => take_prefetched(&mut self.local, kv),
1353 _ => Vec::new(),
1354 };
1355 let held = (!prefetched.is_empty()).then(|| {
1356 let bytes = prefetched.iter().map(|(_, value)| value.len()).sum();
1357 self.state
1358 .held
1359 .fetch_add(bytes, core::sync::atomic::Ordering::Relaxed);
1360 HeldReadAhead {
1361 state: Arc::clone(&self.state),
1362 bytes,
1363 }
1364 });
1365 IterGuardImpl::Blob(Guard {
1366 tree: self.tree.clone(),
1367 version: self.version.clone(),
1368 fill: self.state.fill,
1369 prefetched,
1370 _held: held,
1371 kv: item,
1372 prefetch: if self.armed || self.state.window == 0 {
1379 None
1380 } else {
1381 Some(self.state.clone())
1382 },
1383 })
1384 }
1385}
1386
1387impl<I: Iterator<Item = crate::Result<InternalValue>>> Iterator for PrefetchScan<I> {
1388 type Item = IterGuardImpl;
1389
1390 fn next(&mut self) -> Option<Self::Item> {
1391 if self.buf.is_empty() {
1392 if !self.armed {
1393 self.armed = self.state.armed();
1394 }
1395 if self.armed {
1396 self.fill();
1397 }
1398 }
1399
1400 let item = match self.buf.pop_front() {
1401 Some(item) => item,
1402 None => self.inner.next()?,
1403 };
1404 Some(self.guard(item))
1405 }
1406}
1407
1408impl<I: DoubleEndedIterator<Item = crate::Result<InternalValue>>> DoubleEndedIterator
1409 for PrefetchScan<I>
1410{
1411 fn next_back(&mut self) -> Option<Self::Item> {
1412 if !self.armed {
1418 self.armed = self.state.armed();
1419 }
1420 if !self.inner_back_done {
1426 if let Some(item) = self.inner.next_back() {
1427 return Some(self.guard(item));
1428 }
1429 self.inner_back_done = true;
1430 }
1431
1432 let item = self.buf.pop_back()?;
1433 Some(self.guard(item))
1434 }
1435}
1436
1437struct BlobSeekable {
1440 inner: crate::range::SeekableTreeIter,
1441 tree: crate::BlobTree,
1442 version: Version,
1443 fill: crate::config::BlobCacheFill,
1445}
1446
1447impl Iterator for BlobSeekable {
1448 type Item = IterGuardImpl;
1449
1450 fn next(&mut self) -> Option<Self::Item> {
1451 self.inner
1452 .next()
1453 .map(|item| blob_guard(&self.tree, &self.version, self.fill, item))
1454 }
1455}
1456
1457impl DoubleEndedIterator for BlobSeekable {
1458 fn next_back(&mut self) -> Option<Self::Item> {
1459 self.inner
1460 .next_back()
1461 .map(|item| blob_guard(&self.tree, &self.version, self.fill, item))
1462 }
1463}
1464
1465impl crate::iter_guard::SeekableGuardIter for BlobSeekable {
1466 fn seek_to(&mut self, key: &[u8]) {
1467 self.inner.seek_to(key);
1468 }
1469
1470 fn seek_to_for_prev(&mut self, key: &[u8]) {
1471 self.inner.seek_to_for_prev(key);
1472 }
1473
1474 fn peek_key(&mut self) -> Option<crate::Result<crate::UserKey>> {
1475 self.inner.peek_key()
1478 }
1479}
1480
1481impl AbstractTree for BlobTree {
1482 #[cfg(feature = "std")]
1483 fn create_checkpoint(
1484 &self,
1485 target_path: &crate::path::Path,
1486 ) -> crate::Result<crate::CheckpointInfo> {
1487 crate::checkpoint::run_checkpoint(
1488 self,
1489 &crate::checkpoint::CheckpointParams {
1490 target_root: target_path,
1491 target_fs: &self.index.config.fs,
1492 src_root: &self.index.config.path,
1493 src_fs: &self.index.config.fs,
1494 deletion_pause: &self.index.deletion_pause,
1495 visible_seqno: &self.index.config.visible_seqno,
1496 include_blobs: true,
1497 runtime_config: self.index.0.runtime_config.load_full(),
1498 encryption: self.index.0.config.encryption.clone(),
1499 source: &self.index.0,
1500 },
1501 )
1502 }
1503
1504 #[cfg(feature = "std")]
1505 fn pin(&self) -> crate::PinnedVersion {
1506 crate::PinnedVersion::take(&self.index)
1507 }
1508
1509 #[cfg(feature = "std")]
1510 fn acknowledge_repair_report(&self, id: u128) -> crate::Result<bool> {
1511 self.index.acknowledge_repair_report(id)
1512 }
1513
1514 fn print_trace(&self, key: &[u8]) -> crate::Result<()> {
1515 self.index.print_trace(key)
1516 }
1517
1518 fn table_file_cache_size(&self) -> usize {
1519 self.index.table_file_cache_size()
1520 }
1521
1522 fn get_version_history_lock(&self) -> VersionsWriteGuard<'_> {
1523 self.index.get_version_history_lock()
1524 }
1525
1526 fn persist_flushes(&self) -> crate::Result<()> {
1527 self.index.persist_flushes()
1528 }
1529
1530 fn next_table_id(&self) -> TableId {
1531 self.index.next_table_id()
1532 }
1533
1534 fn id(&self) -> crate::TreeId {
1535 self.index.id()
1536 }
1537
1538 fn get_internal_entry(&self, key: &[u8], seqno: SeqNo) -> crate::Result<Option<InternalValue>> {
1539 self.index.get_internal_entry(key, seqno)
1540 }
1541
1542 fn current_version(&self) -> Version {
1543 self.index.current_version()
1544 }
1545
1546 #[cfg(feature = "std")]
1547 fn refresh_table_checksum(
1548 &self,
1549 table_id: crate::TableId,
1550 checksum: crate::checksum::Checksum,
1551 expected_restriction: Option<&crate::UserKey>,
1552 ) -> crate::Result<crate::abstract_tree::ChecksumRefreshOutcome> {
1553 self.index
1556 .refresh_table_checksum(table_id, checksum, expected_restriction)
1557 }
1558
1559 fn sync_mode(&self) -> crate::fs::SyncMode {
1560 self.index.sync_mode()
1562 }
1563
1564 fn wait_for_table_file_refills(&self) {
1565 self.index.wait_for_table_file_refills();
1566 }
1567
1568 fn prefix_extractor(&self) -> Option<alloc::sync::Arc<dyn crate::prefix::PrefixExtractor>> {
1569 self.index.prefix_extractor()
1571 }
1572
1573 fn storage_stats(&self) -> crate::Result<crate::StorageStats> {
1574 let version = self.current_version();
1581 let mut stats = crate::storage_stats::compute_storage_stats(
1582 &version,
1583 self.index.is_compacting(),
1584 false,
1585 )?;
1586 let (capacity, available, compaction_possible) =
1591 self.index.admission_capacity(stats.used_bytes);
1592 stats.capacity_bytes = capacity;
1593 stats.available_bytes = available;
1594 stats.compaction_possible = compaction_possible;
1595 let blob_need = self.full_compaction_blob_need(&version)?;
1602 stats.full_compaction_bytes += blob_need;
1603 if self.index.storage_admission_enabled()
1607 && capacity.is_some()
1608 && stats.status == crate::StorageStatus::Healthy
1609 {
1610 let sst_need = crate::storage_stats::full_compaction_demand_bytes(&version)?;
1618 let sst_dest_level = self.index.config.level_count.saturating_sub(1);
1621 let quota_headroom = self.index.quota_headroom(stats.used_bytes);
1622 let full_fits = crate::compaction::worker::merge_would_fit(
1623 &self.index.compaction_capacity,
1624 self.index.id,
1625 &self.index.config,
1626 quota_headroom,
1627 (sst_need, blob_need),
1628 sst_dest_level,
1629 );
1630 stats.status = if full_fits {
1631 crate::StorageStatus::FullCompactionAvailable
1632 } else {
1633 crate::StorageStatus::TightCompactionAvailable
1634 };
1635 }
1636 if self.index.is_read_only() {
1640 stats.status = crate::StorageStatus::ReadOnlyOutOfSpace;
1641 }
1642 Ok(stats)
1643 }
1644
1645 fn write_admission(&self) -> crate::Result<()> {
1646 self.index.write_admission()
1652 }
1653
1654 fn write_backpressure(
1655 &self,
1656 strategy: &dyn crate::compaction::CompactionStrategy,
1657 ) -> crate::Backpressure {
1658 self.index.write_backpressure(strategy)
1663 }
1664
1665 #[cfg(feature = "metrics")]
1666 fn cache_stats(&self) -> crate::CacheStats {
1667 self.index.cache_stats()
1668 }
1669
1670 #[cfg(feature = "metrics")]
1671 fn metrics(&self) -> &Arc<crate::Metrics> {
1672 self.index.metrics()
1673 }
1674
1675 fn prefix<K: AsRef<[u8]>>(
1676 &self,
1677 prefix: K,
1678 seqno: SeqNo,
1679 index: Option<(Arc<Memtable>, SeqNo)>,
1680 ) -> Box<dyn DoubleEndedIterator<Item = IterGuardImpl> + Send + 'static> {
1681 use crate::prefix::compute_prefix_hash;
1682 use crate::range::prefix_to_range;
1683
1684 let prefix_bytes = prefix.as_ref();
1685
1686 let prefix_hash =
1687 compute_prefix_hash(self.index.config.prefix_extractor.as_ref(), prefix_bytes);
1688
1689 let super_version = match self.index.get_version_for_snapshot(seqno) {
1694 Ok(super_version) => super_version,
1695 Err(e) => return Box::new(core::iter::once(crate::tree::error_guard(e))),
1696 };
1697 let tree = self.clone();
1698 let source = self.blob_source();
1701 let state = self.scan_prefetch_state(source.fill);
1702
1703 let range = prefix_to_range(prefix_bytes);
1704
1705 Box::new(PrefetchScan {
1706 inner: crate::Tree::create_internal_range_with_prefix_hash(
1707 super_version.clone(),
1708 &range,
1709 seqno,
1710 index,
1711 self.index.config.merge_operator.clone(),
1712 self.index.config.comparator.clone(),
1713 prefix_hash,
1714 Some(source),
1715 ),
1716 buf: alloc::collections::VecDeque::new(),
1717 inner_back_done: false,
1718 armed: false,
1719 state,
1720 version: super_version.version,
1721 tree,
1722 local: crate::vlog::Prefetched::default(),
1723 })
1724 }
1725
1726 fn range<K: AsRef<[u8]>, R: RangeBounds<K>>(
1727 &self,
1728 range: R,
1729 seqno: SeqNo,
1730 index: Option<(Arc<Memtable>, SeqNo)>,
1731 ) -> Box<dyn DoubleEndedIterator<Item = IterGuardImpl> + Send + 'static> {
1732 let super_version = match self.index.get_version_for_snapshot(seqno) {
1736 Ok(super_version) => super_version,
1737 Err(e) => return Box::new(core::iter::once(crate::tree::error_guard(e))),
1738 };
1739 let tree = self.clone();
1740 let source = self.blob_source();
1742 let state = self.scan_prefetch_state(source.fill);
1743
1744 Box::new(PrefetchScan {
1745 inner: crate::Tree::create_internal_range_with_prefix_hash(
1746 super_version.clone(),
1747 &range,
1748 seqno,
1749 index,
1750 self.index.config.merge_operator.clone(),
1751 self.index.config.comparator.clone(),
1752 None,
1753 Some(source),
1754 ),
1755 buf: alloc::collections::VecDeque::new(),
1756 inner_back_done: false,
1757 armed: false,
1758 state,
1759 version: super_version.version,
1760 tree,
1761 local: crate::vlog::Prefetched::default(),
1762 })
1763 }
1764
1765 fn range_seekable<K: AsRef<[u8]>, R: RangeBounds<K>>(
1766 &self,
1767 range: R,
1768 seqno: SeqNo,
1769 index: Option<(Arc<Memtable>, SeqNo)>,
1770 ) -> Box<dyn crate::iter_guard::SeekableGuardIter + 'static> {
1771 let (lo, hi) = crate::tree::range_to_user_bounds(&range);
1772 let source = self.blob_source();
1774 let fill = source.fill;
1775 let inner =
1776 match self
1777 .index
1778 .create_seekable_range_bounds_with(lo, hi, seqno, index, Some(source))
1779 {
1780 Ok(inner) => inner,
1781 Err(e) => return Box::new(crate::iter_guard::FailedSeekable::new(e)),
1782 };
1783 let version = inner.version();
1784 Box::new(BlobSeekable {
1785 inner,
1786 tree: self.clone(),
1787 version,
1788 fill,
1789 })
1790 }
1791
1792 fn batch_range_scan<K: AsRef<[u8]>, R: RangeBounds<K> + 'static, I: IntoIterator<Item = R>>(
1793 &self,
1794 intervals: I,
1795 seqno: SeqNo,
1796 index: Option<(Arc<Memtable>, SeqNo)>,
1797 ) -> Box<dyn Iterator<Item = IterGuardImpl> + Send + 'static>
1798 where
1799 I::IntoIter: Send + 'static,
1800 {
1801 let source = self.blob_source();
1803 let state = self.scan_prefetch_state(source.fill);
1804 let inner = match self.index.create_seekable_range_bounds_with(
1805 core::ops::Bound::Unbounded,
1806 core::ops::Bound::Unbounded,
1807 seqno,
1808 index,
1809 Some(source),
1810 ) {
1811 Ok(inner) => inner,
1812 Err(e) => return Box::new(core::iter::once(crate::tree::error_guard(e))),
1813 };
1814 let version = inner.version();
1815 let tree = self.clone();
1816 let intervals = intervals
1817 .into_iter()
1818 .map(|r| crate::tree::range_to_user_bounds(&r));
1819 Box::new(PrefetchScan {
1820 inner: crate::range::BatchRangeScan::new(inner, intervals),
1821 buf: alloc::collections::VecDeque::new(),
1822 inner_back_done: false,
1823 armed: false,
1824 state,
1825 version,
1826 tree,
1827 local: crate::vlog::Prefetched::default(),
1828 })
1829 }
1830
1831 fn tombstone_count(&self) -> u64 {
1832 self.index.tombstone_count()
1833 }
1834
1835 fn weak_tombstone_count(&self) -> u64 {
1836 self.index.weak_tombstone_count()
1837 }
1838
1839 fn weak_tombstone_reclaimable_count(&self) -> u64 {
1840 self.index.weak_tombstone_reclaimable_count()
1841 }
1842
1843 fn clear(&self) -> crate::Result<()> {
1844 let config = self.tree_config();
1845 let mut versions = self.get_version_history_lock();
1846
1847 let prior = versions.latest_version();
1850
1851 #[cfg(zstd_any)]
1854 let still_written = self.index.write_referenced_dict_ids();
1855 #[cfg(not(zstd_any))]
1856 let still_written: Vec<crate::file::DictId> = Vec::new();
1857
1858 versions.upgrade_version(
1859 &config.path,
1860 |v| {
1861 let mut copy = v.clone();
1862 copy.active_memtable = Arc::new(Memtable::new(
1863 self.index.memtable_id_counter.next(),
1864 config.comparator.clone(),
1865 ));
1866 copy.sealed_memtables = Arc::default();
1867 copy.version = v.version.cleared(&still_written);
1868 Ok(copy)
1869 },
1870 &config.seqno,
1871 &config.visible_seqno,
1872 &*config.fs,
1873 self.index.0.runtime_config.load_full(),
1874 self.index.0.config.encryption.clone(),
1875 crate::version::RetentionEffect::DropsData,
1878 )?;
1879
1880 drop(versions);
1885
1886 for table in prior.version.iter_tables() {
1887 table.mark_as_deleted();
1888 }
1889 for blob_file in prior.version.blob_files.iter() {
1890 blob_file.mark_as_deleted();
1891 }
1892
1893 Ok(())
1894 }
1895
1896 fn major_compact(
1897 &self,
1898 target_size: u64,
1899 gc_watermark: SeqNo,
1900 ) -> crate::Result<crate::compaction::CompactionResult> {
1901 self.index.major_compact(target_size, gc_watermark)
1902 }
1903
1904 fn clear_active_memtable(&self) {
1905 self.index.clear_active_memtable();
1906 }
1907
1908 fn l0_run_count(&self) -> usize {
1909 self.index.l0_run_count()
1910 }
1911
1912 fn blob_file_count(&self) -> usize {
1913 self.current_version().blob_file_count()
1914 }
1915
1916 fn size_of<K: AsRef<[u8]>>(&self, key: K, seqno: SeqNo) -> crate::Result<Option<u32>> {
1919 let key = key.as_ref();
1920 let super_version = self.index.snapshot_for_read(seqno)?;
1921 super_version.version.read_mix().note_points(1);
1922 let Some(item) = crate::Tree::get_internal_entry_from_version(
1923 &super_version,
1924 key,
1925 seqno,
1926 self.index.config.comparator.as_ref(),
1927 )?
1928 else {
1929 return Ok(None);
1930 };
1931
1932 if item.key.value_type.is_merge_operand()
1936 && let Some(merge_operator) = &self.index.config.merge_operator
1937 {
1938 #[expect(clippy::cast_possible_truncation, reason = "values are u32 length max")]
1939 return Ok(crate::Tree::resolve_point_merge(
1940 &super_version,
1941 key,
1942 seqno,
1943 merge_operator,
1944 Some(&self.blob_source()),
1945 )?
1946 .map(|value| value.len() as u32));
1947 }
1948
1949 Ok(Some(if item.key.value_type.is_indirection() {
1950 let mut cursor = crate::io::Cursor::new(item.value);
1951 let vptr = BlobIndirection::decode_from(&mut cursor)?;
1952 vptr.size
1953 } else if item.key.value_type.is_cell_row() {
1954 field_row::logical_len(&item.value)?
1955 } else {
1956 #[expect(clippy::cast_possible_truncation, reason = "values are u32 length max")]
1957 {
1958 item.value.len() as u32
1959 }
1960 }))
1961 }
1962
1963 fn stale_blob_bytes(&self) -> u64 {
1964 self.current_version().gc_stats().stale_bytes()
1965 }
1966
1967 fn filter_size(&self) -> u64 {
1968 self.index.filter_size()
1969 }
1970
1971 fn pinned_filter_size(&self) -> usize {
1972 self.index.pinned_filter_size()
1973 }
1974
1975 fn filter_memory(&self) -> crate::FilterMemory {
1976 self.index.filter_memory()
1977 }
1978
1979 fn pinned_block_index_size(&self) -> usize {
1980 self.index.pinned_block_index_size()
1981 }
1982
1983 fn sealed_memtable_count(&self) -> usize {
1984 self.index.sealed_memtable_count()
1985 }
1986
1987 fn get_flush_lock(&self) -> FlushGuard<'_> {
1988 self.index.get_flush_lock()
1989 }
1990
1991 #[expect(clippy::too_many_lines, reason = "flush logic is inherently complex")]
1992 fn flush_to_tables_with_rt(
1993 &self,
1994 stream: impl Iterator<Item = crate::Result<InternalValue>>,
1995 range_tombstones: Vec<crate::range_tombstone::RangeTombstone>,
1996 keys: u64,
1997 hashes: u64,
1998 overwritten: Option<&overwritten::OverwrittenKeys>,
1999 ) -> crate::Result<
2000 Option<(
2001 Vec<Table>,
2002 Option<Vec<BlobFile>>,
2003 crate::runtime_config::WritePin,
2004 )>,
2005 > {
2006 use crate::{coding::Encode, file::BLOBS_FOLDER, table::multi_writer::MultiWriter};
2007
2008 let start = crate::time::Instant::now();
2009
2010 let (table_folder, level_fs) = self.index.config.tables_folder_for_level(0);
2011
2012 let data_block_size = self.index.config.data_block_size_policy.get(0);
2013
2014 let data_block_restart_interval =
2015 self.index.config.data_block_restart_interval_policy.get(0);
2016 let index_block_restart_interval =
2017 self.index.config.index_block_restart_interval_policy.get(0);
2018
2019 let rc = self.index.0.runtime_config.load_full();
2024
2025 let data_block_compression = rc.data_block_compression_policy.get(0);
2026 let index_block_compression = rc.index_block_compression_policy.get(0);
2027
2028 let data_block_hash_ratio = self.index.config.data_block_hash_ratio_policy.get(0);
2029
2030 let index_partitioning = self.index.config.index_block_partitioning_policy.get(0);
2031 let filter_partitioning = self.index.config.filter_block_partitioning_policy.get(0);
2032
2033 log::debug!(
2034 "Flushing memtable(s) and performing key-value separation, data_block_restart_interval={data_block_restart_interval}, index_block_restart_interval={index_block_restart_interval}, data_block_size={data_block_size}, data_block_compression={data_block_compression:?}, index_block_compression={index_block_compression:?}"
2035 );
2036 log::debug!("=> to table(s) in {}", table_folder.display());
2037 log::debug!("=> to blob file(s) at {}", self.blobs_folder.display());
2038
2039 let table_writer = MultiWriter::from_pool(
2040 table_folder.clone(),
2041 Arc::clone(&self.index.table_files),
2042 None,
2043 self.index.config.table_target_size,
2044 0,
2045 level_fs.clone(),
2046 crate::fs::WriteOrigin::FlushTable,
2047 )?;
2048 let recency = table_writer
2052 .first_made_now()
2053 .unwrap_or_else(|| self.index.table_id_counter.next());
2054 let mut table_writer = table_writer
2055 .set_comparator(self.index.config.comparator.clone())
2056 .use_recency(Some(recency))
2057 .use_data_block_restart_interval(data_block_restart_interval)
2058 .use_index_block_restart_interval(index_block_restart_interval)
2059 .use_data_block_compression(data_block_compression)
2060 .use_index_block_compression(index_block_compression)
2061 .use_data_block_size(data_block_size)
2062 .use_index_partition_size(self.index.config.index_block_partition_size_policy.get(0))
2063 .use_filter_partition_size(self.index.config.filter_block_partition_size_policy.get(0))
2064 .use_row_group_size(self.index.config.columnar_row_group_size_policy.get(0))
2065 .use_columnar_page_size(self.index.config.columnar_page_size_policy.get(0))
2066 .use_column_encoding(self.index.config.column_encoding_policy.get(0))
2067 .use_data_block_hash_ratio(data_block_hash_ratio);
2068
2069 let bloom_policy = {
2070 use crate::config::FilterPolicyEntry::{Bloom, None};
2071 use crate::table::filter::BloomConstructionPolicy;
2072
2073 match self.index.config.filter_policy.get(0) {
2074 Bloom(policy) => policy,
2075 None => BloomConstructionPolicy::BitsPerKey(0.0),
2076 }
2077 };
2078 table_writer = table_writer.use_bloom_policy(bloom_policy);
2079 let filter_sizing = self.index.new_data_filter_sizing(
2080 0,
2081 bloom_policy,
2082 crate::filter_budget::FilterCount { keys, hashes },
2083 rc.ecc_scheme,
2084 );
2085 table_writer = table_writer.use_filter_sizing(filter_sizing.clone());
2086
2087 if index_partitioning {
2088 table_writer = table_writer.use_adaptive_index(rc.index_partition_spill_threshold);
2091 }
2092 if filter_partitioning {
2093 table_writer = table_writer.use_partitioned_filter();
2094 }
2095
2096 table_writer =
2097 table_writer.use_prefix_extractor(self.index.config.prefix_extractor.clone());
2098 table_writer = table_writer.use_key_domains(self.index.config.key_domains.clone());
2099 table_writer = table_writer.use_encryption(self.index.config.encryption.clone());
2100 table_writer = table_writer.use_clock(self.index.config.clock.clone());
2101 table_writer = table_writer.use_page_ecc(self.index.config.page_ecc, rc.ecc_scheme);
2105 table_writer = table_writer
2106 .use_sync_mode(self.index.config.sync_mode)
2107 .use_durability(self.index.config.flush_durability)
2108 .use_writeback_bytes(self.index.config.writeback_bytes);
2109 table_writer = table_writer.use_seqno_in_index(rc.seqno_in_index);
2110 table_writer = table_writer.use_zone_map(rc.zone_map);
2111 table_writer = table_writer
2112 .use_columnar(rc.columnar)
2113 .use_cell_rows(rc.columnar);
2114 table_writer = table_writer.use_disable_cow_on_sst(rc.disable_cow_on_sst_files);
2115 table_writer = table_writer.use_kv_checksums(rc.kv_checksums, rc.kv_checksum_algo);
2116 table_writer = table_writer.use_locator(self.index.config.locator_policy.get(0));
2117
2118 #[cfg(zstd_any)]
2121 let dicts = self.index.config.current_zstd_dictionaries();
2122 #[cfg(zstd_any)]
2123 {
2124 table_writer = table_writer
2125 .use_zstd_dictionary(dicts.for_compression(data_block_compression)?)
2126 .use_zstd_two_pass_seed(rc.zstd_two_pass_seed);
2127 }
2128 let write_pin = crate::runtime_config::WritePin::new(&rc).with_filter_sizing(filter_sizing);
2129
2130 #[cfg(feature = "std")]
2136 {
2137 let transform_does_work = data_block_compression != crate::CompressionType::None
2138 || self.index.config.encryption.is_some()
2139 || self.index.config.page_ecc;
2140 if transform_does_work {
2141 table_writer =
2142 table_writer.use_parallel_compression(self.index.config.parallel_compression());
2143 }
2144 }
2145
2146 #[expect(
2147 clippy::expect_used,
2148 reason = "cannot create blob tree without defining kv separation options"
2149 )]
2150 let kv_opts = self
2151 .index
2152 .config
2153 .kv_separation_opts
2154 .as_ref()
2155 .expect("kv separation options should exist");
2156
2157 let mut blob_writer = {
2158 let w = BlobFileWriter::new_for(
2159 self.index.0.blob_file_id_counter.clone(),
2160 self.index.config.path.join(BLOBS_FOLDER),
2161 self.id(),
2162 self.index.config.descriptor_table.clone(),
2163 self.index.config.fs.clone(),
2164 crate::fs::WriteOrigin::FlushBlob,
2165 )?
2166 .use_target_size(kv_opts.file_target_size)
2167 .use_compression(rc.blob_compression)
2168 .use_sync_mode(self.index.config.sync_mode)
2169 .use_durability(self.index.config.flush_durability)
2170 .use_writeback_bytes(self.index.config.writeback_bytes)
2171 .use_lifetime_groups(kv_opts.lifetime_groups)
2172 .use_clock(self.index.config.clock.clone())
2173 .use_key_domains(
2174 self.index.config.key_domains.clone(),
2175 self.index.config.comparator.clone(),
2176 );
2177 #[cfg(zstd_any)]
2178 let w = w
2179 .use_zstd_dictionary(dicts.for_compression(rc.blob_compression)?)
2180 .use_zstd_two_pass_seed(rc.zstd_two_pass_seed)
2181 .use_zstd_dictionaries(dicts);
2182 w
2183 };
2184
2185 let separation_threshold = kv_opts.separation_threshold;
2186 let lifetime = kv_opts.lifetime_classifier();
2187 let comparator = &self.index.config.comparator;
2188
2189 table_writer.set_range_tombstones(range_tombstones);
2193
2194 for item in stream {
2195 let item = item?;
2196
2197 let overwritten = overwritten
2200 .is_some_and(|keys| keys.take_before_and_check(&item.key.user_key, comparator));
2201
2202 if item.is_tombstone() {
2203 table_writer.write(InternalValue::new(item.key, UserValue::empty()))?;
2206 continue;
2207 }
2208
2209 let value = item.value;
2210
2211 if item.key.value_type.is_cell_row() {
2212 let class = lifetime.class_of(&item.key.user_key, overwritten);
2213 let row = field_row::separate_row(
2216 &value,
2217 |column| kv_opts.cell_threshold(column),
2218 |bytes| {
2219 blob_writer.select_lifetime_class(class)?;
2220 blob_writer.write(&item.key.user_key, item.key.seqno, bytes)
2221 },
2222 )?
2223 .map_or(value, UserValue::from);
2224 table_writer.write(InternalValue::new(item.key, row.clone()))?;
2225 table_writer.register_cell_row(&row)?;
2226 continue;
2227 }
2228
2229 #[expect(clippy::cast_possible_truncation, reason = "values are u32 length max")]
2230 let value_size = value.len() as u32;
2231
2232 if item.key.value_type == crate::ValueType::Value && value_size >= separation_threshold
2235 {
2236 blob_writer
2237 .select_lifetime_class(lifetime.class_of(&item.key.user_key, overwritten))?;
2238 let vhandle = blob_writer.write(&item.key.user_key, item.key.seqno, &value)?;
2239
2240 let indirection = BlobIndirection {
2241 vhandle,
2242 size: value_size,
2243 };
2244
2245 table_writer.write({
2246 let mut vptr =
2247 InternalValue::new(item.key.clone(), indirection.encode_into_vec());
2248 vptr.key.value_type = crate::ValueType::Indirection;
2249 vptr
2250 })?;
2251
2252 table_writer.register_blob(indirection);
2253 } else {
2254 table_writer.write(InternalValue::new(item.key, value))?;
2255 }
2256 }
2257
2258 let blob_files = blob_writer.finish()?;
2259
2260 let written = table_writer.finish()?;
2261 let entries_durable = written.entries_durable;
2262
2263 log::debug!("Flushed memtable(s) in {:?}", start.elapsed());
2264
2265 let pin_filter = self.index.config.filter_block_pinning_policy.get(0);
2266 let pin_index = self.index.config.index_block_pinning_policy.get(0);
2267
2268 let tables = written
2270 .tables
2271 .into_iter()
2272 .map(|(table_id, checksum)| -> crate::Result<Table> {
2273 let mut params = crate::table::RecoverParams::new(
2274 table_folder.join(table_id.to_string()),
2275 checksum,
2276 table_id,
2277 level_fs.clone(),
2278 self.index.config.comparator.clone(),
2279 self.index.config.cache.clone(),
2280 );
2281 params.tree_id = self.index.id;
2282 params
2283 .descriptor_table
2284 .clone_from(&self.index.config.descriptor_table);
2285 params.pin_filter = pin_filter;
2286 params.pin_index = pin_index;
2287 params.encryption.clone_from(&self.index.config.encryption);
2288 params
2289 .prefix_extractor
2290 .clone_from(&self.index.config.prefix_extractor);
2291 #[cfg(zstd_any)]
2292 {
2293 params.zstd_dictionaries = self.index.config.current_zstd_dictionaries();
2294 }
2295 #[cfg(feature = "metrics")]
2296 {
2297 params.metrics = self.index.metrics.clone();
2298 }
2299 let table = Table::recover(params)?;
2300 if entries_durable {
2301 table.mark_entry_durable();
2302 }
2303 Ok(table)
2304 })
2305 .collect::<crate::Result<Vec<_>>>()?;
2306
2307 Ok(Some((tables, Some(blob_files), write_pin)))
2311 }
2312
2313 fn register_tables(
2314 &self,
2315 tables: &[Table],
2316 blob_files: Option<&[BlobFile]>,
2317 frag_map: Option<FragmentationMap>,
2318 sealed_memtables_to_delete: &[MemtableId],
2319 gc_watermark: SeqNo,
2320 collected_below_watermark: bool,
2321 ) -> crate::Result<()> {
2322 self.index.register_tables(
2323 tables,
2324 blob_files,
2325 frag_map,
2326 sealed_memtables_to_delete,
2327 gc_watermark,
2328 collected_below_watermark,
2329 )
2330 }
2331
2332 fn compact(
2333 &self,
2334 strategy: Arc<dyn crate::compaction::CompactionStrategy>,
2335 gc_watermark: SeqNo,
2336 ) -> crate::Result<crate::compaction::CompactionResult> {
2337 self.index.compact(strategy, gc_watermark)
2338 }
2339
2340 fn get_next_table_id(&self) -> TableId {
2341 self.index.get_next_table_id()
2342 }
2343
2344 fn tree_config(&self) -> &Config {
2345 &self.index.config
2346 }
2347
2348 fn get_highest_seqno(&self) -> Option<SeqNo> {
2349 self.index.get_highest_seqno()
2350 }
2351
2352 fn active_memtable(&self) -> Arc<Memtable> {
2353 self.index.active_memtable()
2354 }
2355
2356 fn rotate_memtable(&self) -> Option<Arc<Memtable>> {
2357 self.index.rotate_memtable()
2358 }
2359
2360 fn table_count(&self) -> usize {
2361 self.index.table_count()
2362 }
2363
2364 fn level_table_count(&self, idx: usize) -> Option<usize> {
2365 self.index.level_table_count(idx)
2366 }
2367
2368 fn approximate_len(&self) -> usize {
2369 self.index.approximate_len()
2370 }
2371
2372 fn is_empty(&self, seqno: SeqNo, index: Option<(Arc<Memtable>, SeqNo)>) -> crate::Result<bool> {
2375 self.index.is_empty(seqno, index)
2376 }
2377
2378 fn contains_key<K: AsRef<[u8]>>(&self, key: K, seqno: SeqNo) -> crate::Result<bool> {
2381 self.index.contains_key(key, seqno)
2382 }
2383
2384 fn contains_prefix<K: AsRef<[u8]>>(
2388 &self,
2389 prefix: K,
2390 seqno: SeqNo,
2391 index: Option<(Arc<Memtable>, SeqNo)>,
2392 ) -> crate::Result<bool> {
2393 self.index.contains_prefix(prefix, seqno, index)
2394 }
2395
2396 fn len(&self, seqno: SeqNo, index: Option<(Arc<Memtable>, SeqNo)>) -> crate::Result<usize> {
2399 self.index.len(seqno, index)
2400 }
2401
2402 fn disk_space(&self) -> u64 {
2403 let version = self.current_version();
2404 self.index.disk_space() + version.blob_files.on_disk_size()
2405 }
2406
2407 fn approximate_range_stats<K: AsRef<[u8]>, R: core::ops::RangeBounds<K>>(
2408 &self,
2409 range: R,
2410 seqno: SeqNo,
2411 ) -> crate::Result<crate::ApproximateRangeStats> {
2412 self.index.approximate_range_stats(range, seqno)
2423 }
2424
2425 fn approximate_range_cardinality<K: AsRef<[u8]>, R: core::ops::RangeBounds<K>>(
2426 &self,
2427 range: R,
2428 seqno: SeqNo,
2429 ) -> crate::Result<crate::RangeCardinality> {
2430 self.index.approximate_range_cardinality(range, seqno)
2434 }
2435
2436 fn get_highest_memtable_seqno(&self) -> Option<SeqNo> {
2437 self.index.get_highest_memtable_seqno()
2438 }
2439
2440 fn get_highest_persisted_seqno(&self) -> Option<SeqNo> {
2441 self.index.get_highest_persisted_seqno()
2442 }
2443
2444 fn oldest_retained_seqno(&self) -> SeqNo {
2445 self.index.oldest_retained_seqno()
2447 }
2448
2449 fn retention_floor(&self) -> SeqNo {
2450 self.index.retention_floor()
2451 }
2452
2453 fn lower_retention_floor(&self, floor: SeqNo) -> crate::Result<()> {
2454 self.index.lower_retention_floor(floor)
2456 }
2457
2458 fn compaction_rate_limiter(&self) -> alloc::sync::Arc<crate::rate_limiter::RateLimiter> {
2459 self.index.compaction_rate_limiter()
2461 }
2462
2463 fn write_rate_limiter(&self) -> alloc::sync::Arc<crate::rate_limiter::RateLimiter> {
2464 self.index.write_rate_limiter()
2466 }
2467
2468 fn apply_batch(&self, batch: crate::WriteBatch, seqno: SeqNo) -> crate::Result<(u64, u64)> {
2469 self.index.apply_batch(batch, seqno)
2470 }
2471
2472 fn insert<K: Into<UserKey>, V: Into<UserValue>>(
2473 &self,
2474 key: K,
2475 value: V,
2476 seqno: SeqNo,
2477 ) -> (u64, u64) {
2478 self.index.insert(key, value.into(), seqno)
2479 }
2480
2481 fn get<K: AsRef<[u8]>>(&self, key: K, seqno: SeqNo) -> crate::Result<Option<crate::UserValue>> {
2482 let super_version = self.index.snapshot_for_read(seqno)?;
2487 super_version.version.read_mix().note_points(1);
2488 self.resolve_key(&super_version, &self.blob_source(), key.as_ref(), seqno)
2489 }
2490
2491 fn multi_get<K: AsRef<[u8]>>(
2492 &self,
2493 keys: impl IntoIterator<Item = K>,
2494 seqno: SeqNo,
2495 ) -> crate::Result<Vec<Option<crate::UserValue>>> {
2496 let super_version = self.index.snapshot_for_read(seqno)?;
2500
2501 let keys: Vec<_> = keys.into_iter().collect();
2502 let n = keys.len();
2503 if n == 0 {
2504 return Ok(Vec::new());
2505 }
2506
2507 if n <= 2 {
2509 super_version
2510 .version
2511 .read_mix()
2512 .note_points(u64::try_from(n).unwrap_or(u64::MAX));
2513 let source = self.blob_source();
2514 return keys
2515 .iter()
2516 .map(|key| self.resolve_key(&super_version, &source, key.as_ref(), seqno))
2517 .collect();
2518 }
2519
2520 let source = self.blob_source();
2524 crate::Tree::read_many(&crate::tree::read_job::ReadCtx {
2525 super_version,
2526 keys,
2527 seqno,
2528 comparator: Arc::clone(&self.index.config.comparator),
2529 merge_operator: self.index.config.merge_operator.clone(),
2530 merge_base: Some(source.clone()),
2531 values: crate::tree::read_job::Values::Blob(source),
2532 metadata_budget: self.index.config.multi_get_metadata_budget,
2533 })
2534 }
2535
2536 fn merge<K: Into<UserKey>, V: Into<UserValue>>(
2537 &self,
2538 key: K,
2539 operand: V,
2540 seqno: SeqNo,
2541 ) -> (u64, u64) {
2542 self.index.merge(key, operand, seqno)
2543 }
2544
2545 fn remove<K: Into<UserKey>>(&self, key: K, seqno: SeqNo) -> (u64, u64) {
2546 self.index.remove(key, seqno)
2547 }
2548
2549 fn remove_weak<K: Into<UserKey>>(&self, key: K, seqno: SeqNo) -> (u64, u64) {
2550 self.index.remove_weak(key, seqno)
2551 }
2552
2553 fn remove_range<K: Into<UserKey>>(&self, start: K, end: K, seqno: SeqNo) -> u64 {
2554 self.index.remove_range(start, end, seqno)
2555 }
2556}
2557
2558#[cfg(test)]
2559mod tests;