1pub use crate::db_status::{DbStatus, SegmentPrefix};
24
25use crate::db_cache::CacheTarget;
26use crate::db_cache_manager;
27use std::ops::Range;
28use std::sync::Arc;
29
30use bytes::Bytes;
31use fail_parallel::{fail_point, FailPointRegistry};
32use object_store::path::Path;
33use object_store::{parse_url_opts, ObjectStore};
34
35use crate::compactor::COMPACTOR_TASK_NAME;
36use crate::db_transaction::DbTransaction;
37use crate::dispatcher::MessageHandlerExecutor;
38use crate::garbage_collector::GC_TASK_NAME;
39use crate::transaction_manager::IsolationLevel;
40use crate::CloseReason;
41use log::{debug, info, trace, warn};
42use parking_lot::RwLock;
43use std::time::Duration;
44
45use crate::batch::WriteBatch;
46use crate::batch_write::{BatchWriterMessage, WriteBatchRequest, WRITE_BATCH_TASK_NAME};
47use crate::bytes_range::{ByteRangeBounds, BytesRange};
48use crate::cached_object_store::CachedObjectStore;
49use crate::clock::MonotonicClock;
50use crate::config::{
51 FlushOptions, FlushType, MergeOptions, PutOptions, ReadOptions, ScanOptions, Settings,
52 WriteOptions,
53};
54use crate::db_common::extract_segment_prefix;
55use crate::db_iter::{DbIterator, DbRecencyIterator};
56use crate::db_snapshot::DbSnapshot;
57use crate::db_state::{collect_touched_segments, DbState, SsTableId};
58use crate::db_stats::DbStats;
59use crate::error::SlateDBError;
60use crate::iter::IterationOrder;
61use crate::manifest::{Manifest, VersionedManifest};
62use crate::mem_table::KVTableMetadata;
63use crate::memtable_flusher::{FlushResult, FlushTarget, MemtableFlusher};
64use crate::merge_operator::{instrument_merge_operator, MergeOperatorType};
65use crate::oracle::{DbOracle, Oracle};
66use crate::paths::PathResolver;
67use crate::prefix_extractor::PrefixExtractor;
68use crate::reader::{Reader, ScanContext};
69use crate::snapshot_manager::SnapshotManager;
70use crate::sst_iter::SstIteratorOptions;
71use crate::tablestore::TableStore;
72use crate::transaction_manager::TransactionManager;
73use crate::types::KeyValue;
74use crate::utils::{format_bytes_si, SafeSender, WatchableOnceCellReader};
75use crate::wal_replay::{WalReplayIterator, WalReplayOptions};
76use crate::{DbCacheManagerOps, DbMetadataOps, DbReadOps, DbWriteOps};
77use slatedb_common::clock::SystemClock;
78use slatedb_common::metrics::MetricsRecorderHelper;
79use slatedb_common::DbRand;
80use slatedb_txn_obj::DirtyObject;
81
82use crate::db_status::{ClosedResultWriter, DbStatusManager};
83use crate::wal::{WalEvent, WalObserver, WalStatus};
84pub use builder::DbBuilder;
85pub use builder::DbReaderBuilder;
86
87pub(crate) mod builder;
88
89pub(crate) struct DbInner {
90 pub(crate) state: Arc<RwLock<DbState>>,
91 pub(crate) settings: Settings,
92 pub(crate) table_store: Arc<TableStore>,
93 pub(crate) memtable_flusher: Arc<MemtableFlusher>,
94 pub(crate) write_notifier: SafeSender<BatchWriterMessage>,
95 pub(crate) db_stats: DbStats,
96 #[allow(dead_code)]
100 pub(crate) recorder: MetricsRecorderHelper,
101 #[allow(dead_code)]
102 pub(crate) fp_registry: Arc<FailPointRegistry>,
103 pub(crate) mono_clock: Arc<MonotonicClock>,
106 pub(crate) system_clock: Arc<dyn SystemClock>,
107 pub(crate) rand: Arc<DbRand>,
108 pub(crate) oracle: Arc<DbOracle>,
109 pub(crate) flush_merge_operator: Option<MergeOperatorType>,
110 pub(crate) reader: Reader,
111 pub(crate) wal_observer: DbWalObserver,
114 pub(crate) wal_enabled: bool,
115 pub(crate) txn_manager: Arc<TransactionManager>,
117 pub(crate) snapshot_manager: Arc<SnapshotManager>,
118 pub(crate) status_manager: Arc<DbStatusManager>,
119 pub(crate) segment_extractor: Option<Arc<dyn PrefixExtractor>>,
124}
125
126impl DbInner {
127 pub(crate) async fn new(
128 settings: Settings,
129 system_clock: Arc<dyn SystemClock>,
130 rand: Arc<DbRand>,
131 table_store: Arc<TableStore>,
132 manifest: DirtyObject<Manifest>,
133 memtable_flusher: Arc<MemtableFlusher>,
134 write_notifier: SafeSender<BatchWriterMessage>,
135 wal_observer: Box<dyn WalObserver>,
136 recorder: MetricsRecorderHelper,
137 fp_registry: Arc<FailPointRegistry>,
138 merge_operator: Option<crate::merge_operator::MergeOperatorType>,
139 status_manager: Arc<DbStatusManager>,
140 segment_extractor: Option<Arc<dyn PrefixExtractor>>,
141 ) -> Result<Self, SlateDBError> {
142 let last_l0_seq = manifest.value.core.last_l0_seq;
144 let oracle = Arc::new(DbOracle::new(
145 last_l0_seq,
146 last_l0_seq,
147 last_l0_seq,
148 status_manager.clone(),
149 ));
150
151 let mono_clock = Arc::new(MonotonicClock::new(
152 system_clock.clone(),
153 manifest.value.core.last_l0_clock_tick,
154 ));
155
156 let db_state = DbState::new(manifest);
158 let state = Arc::new(RwLock::new(db_state));
159
160 let db_stats = DbStats::new(&recorder);
161 let wal_enabled = DbInner::wal_enabled_in_options(&settings);
162 let flush_merge_operator = merge_operator.clone().map(|merge_operator| {
163 instrument_merge_operator(
164 merge_operator,
165 db_stats.merge_operator_flush_operands.clone(),
166 )
167 });
168
169 let reader = Reader::new(
170 table_store.clone(),
171 db_stats.clone(),
172 mono_clock.clone(),
173 oracle.clone(),
174 merge_operator.clone(),
175 );
176
177 let txn_manager = Arc::new(TransactionManager::new(oracle.clone(), rand.clone()));
178 let snapshot_manager = Arc::new(SnapshotManager::new(oracle.clone(), rand.clone()));
179 let wal_observer = DbWalObserver::new(
180 wal_observer,
181 oracle.clone(),
182 state.clone(),
183 status_manager.clone(),
184 );
185
186 let db_inner = Self {
187 state,
188 settings,
189 memtable_flusher,
190 oracle,
191 wal_enabled,
192 table_store,
193 wal_observer,
194 write_notifier,
195 db_stats,
196 mono_clock,
197 system_clock,
198 rand,
199 flush_merge_operator,
200 recorder,
201 fp_registry,
202 reader,
203 txn_manager,
204 snapshot_manager,
205 status_manager,
206 segment_extractor,
207 };
208 Ok(db_inner)
209 }
210
211 pub(crate) async fn get_with_options<K: AsRef<[u8]>>(
213 &self,
214 key: K,
215 options: &ReadOptions,
216 ) -> Result<Option<Bytes>, SlateDBError> {
217 self.get_key_value_with_options(key, options)
218 .await
219 .map(|kv_opt| kv_opt.map(|kv| kv.value))
220 }
221
222 pub(crate) async fn get_key_value_with_options<K: AsRef<[u8]>>(
224 &self,
225 key: K,
226 options: &ReadOptions,
227 ) -> Result<Option<KeyValue>, SlateDBError> {
228 self.check_closed()?;
229 let db_state = self.state.read().view();
230 self.reader
231 .get_key_value_with_options(key, options, &db_state, None, None)
232 .await
233 }
234
235 pub(crate) async fn scan_with_options(
239 &self,
240 range: BytesRange,
241 options: &ScanOptions,
242 prefix: Option<Bytes>,
243 ) -> Result<DbIterator, SlateDBError> {
244 self.check_closed()?;
245 let db_state = self.state.read().view();
246 self.reader
247 .scan_with_options(
248 range,
249 options,
250 ScanContext {
251 db_state: &db_state,
252 write_batch_iter: None,
253 max_seq: None,
254 prefix,
255 },
256 )
257 .await
258 }
259
260 pub(crate) async fn scan_prefix_by_recency_with_options(
261 &self,
262 prefix: Bytes,
263 options: &ScanOptions,
264 ) -> Result<DbRecencyIterator, SlateDBError> {
265 self.check_closed()?;
266 let db_state = self.state.read().view();
267 self.reader
268 .scan_prefix_by_recency_with_options(prefix, options, &db_state)
269 .await
270 }
271
272 #[allow(unused_variables)]
273 pub(crate) fn wal_enabled_in_options(settings: &Settings) -> bool {
274 #[cfg(feature = "wal_disable")]
275 return settings.wal_enabled;
276 #[cfg(not(feature = "wal_disable"))]
277 return true;
278 }
279
280 pub(crate) async fn write_with_options(
281 &self,
282 batch: WriteBatch,
283 options: &WriteOptions,
284 txn: Option<DbTransaction>,
285 ) -> Result<WriteHandle, SlateDBError> {
286 self.db_stats.write_batch_count.increment(1);
287 self.db_stats.write_ops.increment(batch.op_count() as u64);
288 self.check_closed()?;
289 if batch.ops.is_empty() {
290 return Err(SlateDBError::EmptyBatch);
291 }
292
293 let (tx, rx) = tokio::sync::oneshot::channel();
294 let batch_msg = BatchWriterMessage::WriteBatch(WriteBatchRequest {
295 batch,
296 options: options.clone(),
297 done: tx,
298 txn,
299 });
300
301 self.maybe_apply_backpressure().await?;
302 self.write_notifier.send(batch_msg)?;
303
304 let write_handle = rx.await??;
307
308 if options.await_durable {
309 let seq = write_handle.seq;
310 let mut status_subscription = self.status_manager.subscribe();
311 let status = status_subscription
312 .wait_for(|s| s.durable_seq >= seq || s.close_reason.is_some())
313 .await
314 .map_err(|_| SlateDBError::Closed)?;
315 if status.durable_seq < seq {
316 self.check_closed()?;
317 warn!(
318 "durable seq {} not advanced past write seq {} and db not closed",
319 status.durable_seq, seq
320 );
321 return Err(SlateDBError::InvalidDBState);
322 }
323 }
324
325 Ok(write_handle)
326 }
327
328 #[inline]
329 pub(crate) async fn maybe_apply_backpressure(&self) -> Result<(), SlateDBError> {
330 loop {
331 self.check_closed()?;
332 let wal_status = self.wal_observer.status()?;
333 let (active_memtable_size_bytes, imm_memtable_size_bytes) = {
334 let guard = self.state.read();
335 let estimate = |metadata: KVTableMetadata| {
336 self.table_store.estimate_encoded_size_compacted(
337 metadata.entry_num,
338 metadata.entries_size_in_bytes,
339 )
340 };
341 let active_memtable_size_bytes = estimate(guard.memtable().table().metadata());
342 let imm_memtable_size_bytes = guard
343 .state()
344 .imm_memtable
345 .iter()
346 .map(|imm| estimate(imm.table().metadata()))
347 .fold(0usize, |total, size| total.saturating_add(size));
348 (active_memtable_size_bytes, imm_memtable_size_bytes)
349 };
350 let total_mem_size_bytes = active_memtable_size_bytes
351 .saturating_add(imm_memtable_size_bytes)
352 .saturating_add(wal_status.estimated_bytes);
353 self.db_stats
354 .total_mem_size_bytes
355 .set(total_mem_size_bytes as i64);
356
357 trace!(
358 "checking backpressure [total_mem_size_bytes={}, active_memtable_size_bytes={}, imm_memtable_size_bytes={}, wal_size_bytes={}, max_unflushed_bytes={}]",
359 format_bytes_si(total_mem_size_bytes as u64),
360 format_bytes_si(active_memtable_size_bytes as u64),
361 format_bytes_si(imm_memtable_size_bytes as u64),
362 format_bytes_si(wal_status.estimated_bytes as u64),
363 format_bytes_si(self.settings.max_unflushed_bytes as u64),
364 );
365
366 if total_mem_size_bytes >= self.settings.max_unflushed_bytes {
367 self.db_stats.backpressure_count.increment(1);
368 warn!(
369 "unflushed WAL and memtable size exceeds max_unflushed_bytes. applying backpressure. [total_mem_size_bytes={}, active_memtable_size_bytes={}, imm_memtable_size_bytes={}, wal_size_bytes={}, max_unflushed_bytes={}]",
370 format_bytes_si(total_mem_size_bytes as u64),
371 format_bytes_si(active_memtable_size_bytes as u64),
372 format_bytes_si(imm_memtable_size_bytes as u64),
373 format_bytes_si(wal_status.estimated_bytes as u64),
374 format_bytes_si(self.settings.max_unflushed_bytes as u64),
375 );
376
377 let maybe_oldest_unflushed_memtable = {
378 let guard = self.state.read();
379 guard.state().imm_memtable.back().cloned()
380 };
381
382 if maybe_oldest_unflushed_memtable.is_none() && wal_status.estimated_bytes == 0 {
387 continue;
388 }
389
390 let await_memtable_uploaded = async {
391 if let Some(oldest_unflushed_memtable) = maybe_oldest_unflushed_memtable {
392 oldest_unflushed_memtable.await_uploaded().await
393 } else {
394 std::future::pending().await
395 }
396 };
397
398 let await_flush_wal = self
399 .wal_observer
400 .wait_until_wal_flushed(wal_status.last_flushed_wal_id);
401
402 let timeout_fut = self.system_clock.sleep(Duration::from_secs(30));
403 let await_closed = async {
404 let mut watcher = self.status_manager.result_reader();
405 match watcher.await_value().await {
406 Ok(()) => Err(SlateDBError::Closed),
407 Err(e) => Err(e),
408 }
409 };
410
411 tokio::select! {
412 biased;
413
414 result = await_closed => result?,
415 result = await_memtable_uploaded => result?,
416 result = await_flush_wal => result?,
417 _ = timeout_fut => {
418 warn!("backpressure timeout: waited 30s, no memtable/WAL flushed yet");
419 }
420 };
421 } else {
422 break;
423 }
424 }
425 Ok(())
426 }
427
428 async fn flush_imm_memtables(&self, target: FlushTarget) -> Result<FlushResult, SlateDBError> {
429 self.memtable_flusher().flush(target).await
430 }
431
432 pub(crate) async fn flush_memtables(
433 &self,
434 target: FlushTarget,
435 ) -> Result<FlushResult, SlateDBError> {
436 self.request_batch_writer_flush(true).await?;
439 self.flush_imm_memtables(target).await
440 }
441
442 pub(crate) fn memtable_flusher(&self) -> &MemtableFlusher {
443 &self.memtable_flusher
444 }
445
446 pub(crate) async fn flush(
461 &self,
462 options: FlushOptions,
463 check_status: bool,
464 ) -> Result<(), SlateDBError> {
465 self.db_stats.flush_requests.increment(1);
466 if check_status {
467 self.check_closed()?;
468 }
469 match options.flush_type {
470 FlushType::Wal => {
471 if !self.wal_enabled {
472 return Err(SlateDBError::WalDisabled);
473 }
474 self.request_batch_writer_flush(false).await
475 }
476 FlushType::MemTable => self.flush_memtables(FlushTarget::All).await.map(|_| ()),
477 }
478 }
479
480 async fn replay_wal(&self, wal_id_range: Range<u64>) -> Result<(), SlateDBError> {
481 let mut current_memtable_wal_id = self
482 .state
483 .read()
484 .state()
485 .manifest
486 .value
487 .core
488 .replay_after_wal_id;
489 let writer_epoch = self.state.read().state().manifest.value.writer_epoch;
490 fail_point!(
491 Arc::clone(&self.fp_registry),
492 "replay-wal-pause",
493 writer_epoch == 1,
494 |_| -> Result<(), SlateDBError> { Ok(()) }
495 );
496
497 let sst_iter_options = SstIteratorOptions {
498 max_fetch_tasks: 1,
499 blocks_to_fetch: 256,
500 cache_blocks: false,
501 cache_metadata: false,
502 eager_spawn: true,
503 order: IterationOrder::Ascending,
504 prefix: None,
505 filter_context: None,
506 };
507
508 let replay_options = WalReplayOptions {
509 sst_batch_size: 4,
510 max_memtable_bytes: self.settings.l0_sst_size_bytes,
511 sst_iter_options,
512 min_seq: None,
513 };
514
515 let db_state = self.state.read().state().core().clone();
516 let mut replay_iter = WalReplayIterator::range(
517 wal_id_range,
518 &db_state,
519 replay_options,
520 Arc::clone(&self.table_store),
521 )
522 .await?;
523
524 loop {
525 let replayed_table = match replay_iter.next().await {
526 Ok(Some(replayed_table)) => replayed_table,
527 Ok(None) => break,
528 Err(err) if err.has_object_store_not_found() => {
533 self.memtable_flusher.refresh_manifest().await?;
534 if self.state.read().state().manifest.value.writer_epoch > writer_epoch {
535 return Err(SlateDBError::Fenced);
536 }
537 return Err(err);
538 }
539 Err(err) => return Err(err),
540 };
541
542 if let Some(extractor) = self.segment_extractor.as_ref() {
550 let mut touched_segments: std::collections::BTreeSet<Bytes> =
551 std::collections::BTreeSet::new();
552 let mut iter = replayed_table.table.table().iter();
553 while let Some(entry) = iter.next_sync() {
554 touched_segments
555 .insert(extract_segment_prefix(extractor.as_ref(), &entry.key)?);
556 }
557 replayed_table
558 .table
559 .record_touched_segments(touched_segments);
560 }
561 assert!(self.oracle.last_remote_persisted_seq() <= replayed_table.last_seq);
570 self.oracle.advance_durable_seq(replayed_table.last_seq);
571 self.maybe_freeze_memtable(current_memtable_wal_id);
572 self.maybe_apply_backpressure().await?;
573 let replayed_table_last_wal_id = replayed_table.last_wal_id;
574 self.replay_memtable(current_memtable_wal_id, replayed_table)?;
575 current_memtable_wal_id = replayed_table_last_wal_id;
576 }
577
578 let guard = self.state.read();
579 self.status_manager
580 .report_memtable_segments(collect_touched_segments(&guard.view()));
581
582 Ok(())
583 }
584
585 async fn preload_cache(
586 &self,
587 cached_obj_store: &CachedObjectStore,
588 path_resolver: &PathResolver,
589 ) -> Result<(), SlateDBError> {
590 let state = self.state.read().state();
591 let cache_opts = &self.settings.object_store_cache_options;
592 crate::utils::preload_cache_from_manifest(
593 &state.manifest.value.core,
594 cached_obj_store,
595 path_resolver,
596 cache_opts.preload_disk_cache_on_startup,
597 cache_opts.max_cache_size_bytes.unwrap_or(usize::MAX),
598 )
599 .await
600 }
601
602 pub(crate) fn status(&self) -> DbStatus {
604 self.status_manager.status()
605 }
606
607 pub(crate) fn check_closed(&self) -> Result<(), SlateDBError> {
616 if let Some(result) = self.status_manager.result_reader().read() {
617 return match result {
618 Ok(()) => Err(SlateDBError::Closed),
619 Err(e) => Err(e),
620 };
621 }
622 Ok(())
623 }
624
625 pub(crate) fn manifest(&self) -> VersionedManifest {
626 self.state.read().state().manifest.clone().into()
627 }
628}
629
630#[derive(Clone)]
631pub struct Db {
632 pub(crate) inner: Arc<DbInner>,
633 task_executor: Arc<MessageHandlerExecutor>,
634}
635
636impl Db {
637 pub async fn open<P: Into<Path>>(
664 path: P,
665 object_store: Arc<dyn ObjectStore>,
666 ) -> Result<Self, crate::Error> {
667 Self::builder(path, object_store).build().await
669 }
670
671 pub fn builder<P: Into<Path>>(path: P, object_store: Arc<dyn ObjectStore>) -> DbBuilder<P> {
697 DbBuilder::new(path, object_store)
698 }
699
700 pub async fn close(&self) -> Result<(), crate::Error> {
721 let should_flush = match self.status().close_reason {
722 Some(CloseReason::Clean) => return Err(SlateDBError::Closed.into()),
724 Some(_) => false,
729 None => true,
731 };
732
733 self.inner.status_manager.write_result(Ok(()));
735
736 let result = if should_flush {
737 self.inner
740 .flush(
741 FlushOptions {
742 flush_type: FlushType::MemTable,
743 },
744 false,
745 )
746 .await
747 .map_err(Into::into)
748 .inspect_err(|e| warn!("failed to flush db during close [error={:?}]", e))
749 } else {
750 Ok(())
751 };
752
753 MemtableFlusher::shutdown(&self.task_executor).await;
754
755 if let Err(e) = self.task_executor.shutdown_task(COMPACTOR_TASK_NAME).await {
756 warn!("failed to shutdown compactor task [error={:?}]", e);
757 }
758
759 if let Err(e) = self
760 .task_executor
761 .shutdown_task(crate::compaction_worker::COMPACTION_WORKER_TASK_NAME)
762 .await
763 {
764 warn!("failed to shutdown compaction worker task [error={:?}]", e);
765 }
766
767 if let Err(e) = self.task_executor.shutdown_task(GC_TASK_NAME).await {
768 warn!("failed to shutdown garbage collector task [error={:?}]", e);
769 }
770
771 if let Err(e) = self
772 .task_executor
773 .shutdown_task(WRITE_BATCH_TASK_NAME)
774 .await
775 {
776 warn!("failed to shutdown writer task [error={:?}]", e);
777 }
778
779 if let Err(e) = self.inner.table_store.close_cache().await {
780 warn!("failed to close block cache [error={:?}]", e);
781 }
782
783 info!("db closed");
784 result
785 }
786
787 pub async fn snapshot(&self) -> Result<Arc<DbSnapshot>, crate::Error> {
825 self.inner.check_closed()?;
826 let snapshot = DbSnapshot::new(self.inner.clone(), None);
827 Ok(snapshot)
828 }
829
830 pub async fn get<K: AsRef<[u8]> + Send>(&self, key: K) -> Result<Option<Bytes>, crate::Error> {
865 self.get_with_options(key, &ReadOptions::default()).await
866 }
867
868 pub async fn get_with_options<K: AsRef<[u8]> + Send>(
905 &self,
906 key: K,
907 options: &ReadOptions,
908 ) -> Result<Option<Bytes>, crate::Error> {
909 self.inner
910 .get_with_options(key, options)
911 .await
912 .map_err(Into::into)
913 }
914
915 pub async fn get_key_value<K: AsRef<[u8]> + Send>(
932 &self,
933 key: K,
934 ) -> Result<Option<KeyValue>, crate::Error> {
935 self.get_key_value_with_options(key, &ReadOptions::default())
936 .await
937 }
938
939 pub async fn get_key_value_with_options<K: AsRef<[u8]> + Send>(
958 &self,
959 key: K,
960 options: &ReadOptions,
961 ) -> Result<Option<KeyValue>, crate::Error> {
962 let kv = self
963 .inner
964 .get_key_value_with_options(key, options)
965 .await
966 .map_err(crate::Error::from)?;
967 Ok(kv)
968 }
969
970 pub async fn scan<T>(&self, range: T) -> Result<DbIterator, crate::Error>
1003 where
1004 T: ByteRangeBounds + Send,
1005 {
1006 self.scan_with_options(range, &ScanOptions::default()).await
1007 }
1008
1009 pub async fn scan_with_options<T>(
1042 &self,
1043 range: T,
1044 options: &ScanOptions,
1045 ) -> Result<DbIterator, crate::Error>
1046 where
1047 T: ByteRangeBounds + Send,
1048 {
1049 let start = range.start_bound().map(Bytes::copy_from_slice);
1050 let end = range.end_bound().map(Bytes::copy_from_slice);
1051 let range = (start, end);
1052 self.inner
1053 .scan_with_options(BytesRange::from(range), options, None)
1054 .await
1055 .map_err(Into::into)
1056 }
1057
1058 pub async fn scan_prefix<P, T>(
1109 &self,
1110 prefix: P,
1111 subrange: T,
1112 ) -> Result<DbIterator, crate::Error>
1113 where
1114 P: AsRef<[u8]> + Send,
1115 T: ByteRangeBounds + Send,
1116 {
1117 self.scan_prefix_with_options(prefix, subrange, &ScanOptions::default())
1118 .await
1119 }
1120
1121 pub async fn scan_prefix_with_options<P, T>(
1166 &self,
1167 prefix: P,
1168 subrange: T,
1169 options: &ScanOptions,
1170 ) -> Result<DbIterator, crate::Error>
1171 where
1172 P: AsRef<[u8]> + Send,
1173 T: ByteRangeBounds + Send,
1174 {
1175 let prefix = Bytes::copy_from_slice(prefix.as_ref());
1176 let range = BytesRange::from_prefix_and_subrange(prefix.as_ref(), subrange);
1177 self.inner
1178 .scan_with_options(range, options, Some(prefix))
1179 .await
1180 .map_err(Into::into)
1181 }
1182
1183 pub async fn scan_prefix_by_recency<P>(
1285 &self,
1286 prefix: P,
1287 ) -> Result<DbRecencyIterator, crate::Error>
1288 where
1289 P: AsRef<[u8]> + Send,
1290 {
1291 self.scan_prefix_by_recency_with_options(prefix, &ScanOptions::default())
1292 .await
1293 }
1294
1295 pub async fn scan_prefix_by_recency_with_options<P>(
1348 &self,
1349 prefix: P,
1350 options: &ScanOptions,
1351 ) -> Result<DbRecencyIterator, crate::Error>
1352 where
1353 P: AsRef<[u8]> + Send,
1354 {
1355 let prefix = Bytes::copy_from_slice(prefix.as_ref());
1356 self.inner
1357 .scan_prefix_by_recency_with_options(prefix, options)
1358 .await
1359 .map_err(Into::into)
1360 }
1361
1362 pub async fn put<K, V>(&self, key: K, value: V) -> Result<WriteHandle, crate::Error>
1387 where
1388 K: AsRef<[u8]>,
1389 V: AsRef<[u8]>,
1390 {
1391 let mut batch = WriteBatch::new();
1392 batch.put(key, value);
1393 self.write(batch).await
1394 }
1395
1396 pub async fn put_with_options<K, V>(
1423 &self,
1424 key: K,
1425 value: V,
1426 put_opts: &PutOptions,
1427 write_opts: &WriteOptions,
1428 ) -> Result<WriteHandle, crate::Error>
1429 where
1430 K: AsRef<[u8]>,
1431 V: AsRef<[u8]>,
1432 {
1433 let mut batch = WriteBatch::new();
1434 batch.put_with_options(key, value, put_opts);
1435 self.write_with_options(batch, write_opts).await
1436 }
1437
1438 pub async fn put_bytes(&self, key: Bytes, value: Bytes) -> Result<WriteHandle, crate::Error> {
1444 self.put_bytes_with_options(key, value, &PutOptions::default(), &WriteOptions::default())
1445 .await
1446 }
1447
1448 pub async fn put_bytes_with_options(
1452 &self,
1453 key: Bytes,
1454 value: Bytes,
1455 put_opts: &PutOptions,
1456 write_opts: &WriteOptions,
1457 ) -> Result<WriteHandle, crate::Error> {
1458 let mut batch = WriteBatch::new();
1459 batch.put_bytes_with_options(key, value, put_opts);
1460 self.write_with_options(batch, write_opts).await
1461 }
1462
1463 pub async fn delete<K: AsRef<[u8]>>(&self, key: K) -> Result<WriteHandle, crate::Error> {
1487 let mut batch = WriteBatch::new();
1488 batch.delete(key.as_ref());
1489 self.write(batch).await
1490 }
1491
1492 pub async fn delete_with_options<K: AsRef<[u8]>>(
1517 &self,
1518 key: K,
1519 options: &WriteOptions,
1520 ) -> Result<WriteHandle, crate::Error> {
1521 let mut batch = WriteBatch::new();
1522 batch.delete(key);
1523 self.write_with_options(batch, options).await
1524 }
1525
1526 pub async fn merge<K, V>(&self, key: K, value: V) -> Result<WriteHandle, crate::Error>
1569 where
1570 K: AsRef<[u8]>,
1571 V: AsRef<[u8]>,
1572 {
1573 self.merge_with_options(
1574 key,
1575 value,
1576 &MergeOptions::default(),
1577 &WriteOptions::default(),
1578 )
1579 .await
1580 }
1581
1582 pub async fn merge_with_options<K, V>(
1632 &self,
1633 key: K,
1634 value: V,
1635 merge_opts: &MergeOptions,
1636 write_opts: &WriteOptions,
1637 ) -> Result<WriteHandle, crate::Error>
1638 where
1639 K: AsRef<[u8]>,
1640 V: AsRef<[u8]>,
1641 {
1642 if self.inner.flush_merge_operator.is_none() {
1643 return Err(SlateDBError::MergeOperatorMissing.into());
1644 }
1645
1646 let mut batch = WriteBatch::new();
1647 batch.merge_with_options(key, value, merge_opts);
1648 self.write_with_options(batch, write_opts).await
1649 }
1650
1651 pub async fn write(&self, batch: WriteBatch) -> Result<WriteHandle, crate::Error> {
1683 self.write_with_options(batch, &WriteOptions::default())
1684 .await
1685 }
1686
1687 pub async fn write_with_options(
1720 &self,
1721 batch: WriteBatch,
1722 options: &WriteOptions,
1723 ) -> Result<WriteHandle, crate::Error> {
1724 self.inner
1725 .write_with_options(batch, options, None)
1726 .await
1727 .map_err(Into::into)
1728 }
1729
1730 pub async fn flush(&self) -> Result<(), crate::Error> {
1758 let flush_type = if self.inner.wal_enabled {
1759 FlushType::Wal
1760 } else {
1761 FlushType::MemTable
1762 };
1763 self.inner
1764 .flush(FlushOptions { flush_type }, true)
1765 .await
1766 .map_err(Into::into)
1767 }
1768
1769 pub async fn flush_with_options(&self, options: FlushOptions) -> Result<(), crate::Error> {
1805 self.inner.flush(options, true).await.map_err(Into::into)
1806 }
1807
1808 pub async fn refresh_manifest(&self) -> Result<(), crate::Error> {
1824 self.inner.check_closed()?;
1825 self.inner
1826 .memtable_flusher
1827 .refresh_manifest()
1828 .await
1829 .map_err(Into::into)
1830 }
1831
1832 pub async fn begin(
1856 &self,
1857 isolation_level: IsolationLevel,
1858 ) -> Result<DbTransaction, crate::Error> {
1859 self.inner.check_closed()?;
1860 let txn = DbTransaction::new(
1861 self.inner.clone(),
1862 self.inner.txn_manager.clone(),
1863 isolation_level,
1864 );
1865 Ok(txn)
1866 }
1867
1868 pub fn resolve_object_store(url: &str) -> Result<Arc<dyn ObjectStore>, crate::Error> {
1884 let url = url
1885 .try_into()
1886 .map_err(|e| SlateDBError::InvalidObjectStoreURL(url.to_string(), e))?;
1887 let env_vars = std::env::vars().map(|(key, value)| (key.to_ascii_lowercase(), value));
1889 let (object_store, path) = parse_url_opts(&url, env_vars).map_err(SlateDBError::from)?;
1890 if !path.as_ref().is_empty() {
1891 return Err(SlateDBError::InvalidObjectStorePath(path.to_string()))?;
1892 }
1893 Ok(Arc::from(object_store))
1894 }
1895}
1896
1897#[async_trait::async_trait]
1898impl DbReadOps for Db {
1899 async fn get_with_options<K: AsRef<[u8]> + Send>(
1900 &self,
1901 key: K,
1902 options: &ReadOptions,
1903 ) -> Result<Option<Bytes>, crate::Error> {
1904 Db::get_with_options(self, key, options).await
1905 }
1906
1907 async fn get_key_value_with_options<K: AsRef<[u8]> + Send>(
1908 &self,
1909 key: K,
1910 options: &ReadOptions,
1911 ) -> Result<Option<KeyValue>, crate::Error> {
1912 Db::get_key_value_with_options(self, key, options).await
1913 }
1914
1915 async fn scan_with_options<T>(
1916 &self,
1917 range: T,
1918 options: &ScanOptions,
1919 ) -> Result<DbIterator, crate::Error>
1920 where
1921 T: ByteRangeBounds + Send,
1922 {
1923 Db::scan_with_options(self, range, options).await
1924 }
1925
1926 async fn scan_prefix_with_options<P, T>(
1927 &self,
1928 prefix: P,
1929 subrange: T,
1930 options: &ScanOptions,
1931 ) -> Result<DbIterator, crate::Error>
1932 where
1933 P: AsRef<[u8]> + Send,
1934 T: ByteRangeBounds + Send,
1935 {
1936 Db::scan_prefix_with_options(self, prefix, subrange, options).await
1937 }
1938}
1939
1940impl DbMetadataOps for Db {
1941 fn manifest(&self) -> VersionedManifest {
1942 self.inner.manifest()
1943 }
1944
1945 fn subscribe(&self) -> tokio::sync::watch::Receiver<DbStatus> {
1946 self.inner.status_manager.subscribe()
1947 }
1948
1949 fn status(&self) -> DbStatus {
1950 self.inner.status()
1951 }
1952}
1953
1954#[async_trait::async_trait]
1955impl DbWriteOps for Db {
1956 type Transaction = DbTransaction;
1957
1958 async fn put_with_options<K, V>(
1959 &self,
1960 key: K,
1961 value: V,
1962 put_opts: &PutOptions,
1963 write_opts: &WriteOptions,
1964 ) -> Result<WriteHandle, crate::Error>
1965 where
1966 K: AsRef<[u8]> + Send,
1967 V: AsRef<[u8]> + Send,
1968 {
1969 Db::put_with_options(self, key, value, put_opts, write_opts).await
1970 }
1971
1972 async fn delete_with_options<K: AsRef<[u8]> + Send>(
1973 &self,
1974 key: K,
1975 options: &WriteOptions,
1976 ) -> Result<WriteHandle, crate::Error> {
1977 Db::delete_with_options(self, key, options).await
1978 }
1979
1980 async fn merge_with_options<K, V>(
1981 &self,
1982 key: K,
1983 value: V,
1984 merge_opts: &MergeOptions,
1985 write_opts: &WriteOptions,
1986 ) -> Result<WriteHandle, crate::Error>
1987 where
1988 K: AsRef<[u8]> + Send,
1989 V: AsRef<[u8]> + Send,
1990 {
1991 Db::merge_with_options(self, key, value, merge_opts, write_opts).await
1992 }
1993
1994 async fn write_with_options(
1995 &self,
1996 batch: WriteBatch,
1997 options: &WriteOptions,
1998 ) -> Result<WriteHandle, crate::Error> {
1999 Db::write_with_options(self, batch, options).await
2000 }
2001
2002 async fn flush(&self) -> Result<(), crate::Error> {
2003 Db::flush(self).await
2004 }
2005
2006 async fn flush_with_options(&self, options: FlushOptions) -> Result<(), crate::Error> {
2007 Db::flush_with_options(self, options).await
2008 }
2009
2010 async fn begin(&self, isolation_level: IsolationLevel) -> Result<DbTransaction, crate::Error> {
2011 Db::begin(self, isolation_level).await
2012 }
2013}
2014
2015impl Db {
2016 pub fn manifest(&self) -> VersionedManifest {
2018 <Self as DbMetadataOps>::manifest(self)
2019 }
2020
2021 pub fn subscribe(&self) -> tokio::sync::watch::Receiver<DbStatus> {
2023 <Self as DbMetadataOps>::subscribe(self)
2024 }
2025
2026 pub fn status(&self) -> DbStatus {
2028 <Self as DbMetadataOps>::status(self)
2029 }
2030}
2031
2032#[async_trait::async_trait]
2033impl DbCacheManagerOps for Db {
2034 async fn warm_sst(
2035 &self,
2036 sst_id: SsTableId,
2037 targets: &[CacheTarget],
2038 ) -> Result<(), crate::Error> {
2039 self.inner.check_closed()?;
2040 let manifest = self.manifest();
2041 db_cache_manager::warm_sst_impl(&self.inner.table_store, &manifest, sst_id, targets).await
2042 }
2043
2044 async fn evict_cached_sst(&self, sst_id: SsTableId) -> Result<(), crate::Error> {
2045 self.inner.check_closed()?;
2046 db_cache_manager::evict_cached_sst_impl(&self.inner.table_store, sst_id).await
2047 }
2048}
2049
2050#[derive(Debug, Clone)]
2053pub struct WriteHandle {
2054 pub(crate) seq: u64,
2055 pub(crate) create_ts: i64,
2056}
2057
2058impl WriteHandle {
2059 pub fn new(seq: u64, create_ts: i64) -> Self {
2060 Self { seq, create_ts }
2061 }
2062
2063 pub fn seqnum(&self) -> u64 {
2065 self.seq
2066 }
2067
2068 pub fn create_ts(&self) -> i64 {
2070 self.create_ts
2071 }
2072}
2073
2074#[derive(Clone)]
2078pub(crate) struct DbWalObserver {
2079 status_rx: tokio::sync::watch::Receiver<Result<WalStatus, WalStatus>>,
2080 closed_reader: WatchableOnceCellReader<Result<(), SlateDBError>>,
2081 wrapped: Arc<dyn WalObserver>,
2082}
2083
2084impl DbWalObserver {
2085 fn new(
2086 wrapped: Box<dyn WalObserver>,
2087 oracle: Arc<DbOracle>,
2088 db_state: Arc<RwLock<DbState>>,
2089 closed_writer: Arc<dyn ClosedResultWriter>,
2090 ) -> Self {
2091 let (status_tx, status_rx) = tokio::sync::watch::channel(wrapped.status());
2092 let closed_reader = closed_writer.result_reader();
2093 wrapped
2094 .subscribe(Arc::new(move |event| {
2095 let status: Result<WalStatus, WalStatus> = match event {
2096 WalEvent::WalFlushed(status) => {
2097 if let Some(seq) = status.last_flushed_seq {
2098 oracle.advance_durable_seq(seq);
2099 }
2100 let mut guard = db_state.write();
2101 guard.set_next_wal_id(status.last_flushed_wal_id + 1);
2102 drop(guard);
2103 Ok(status)
2104 }
2105 WalEvent::WalClosed(status) => {
2106 closed_writer.write_result(Err(status.clone().into()));
2107 Err(status)
2108 }
2109 };
2110 let _ = status_tx.send(status);
2111 }))
2112 .expect("failed to subscribe to wal");
2113 Self {
2114 status_rx,
2115 closed_reader,
2116 wrapped: wrapped.into(),
2117 }
2118 }
2119
2120 pub(crate) fn status(&self) -> Result<WalStatus, WalStatus> {
2121 self.wrapped.status()
2122 }
2123
2124 async fn wait_on_condition(
2125 &self,
2126 mut predicate: impl FnMut(&WalStatus) -> bool,
2127 ) -> Result<(), SlateDBError> {
2128 let mut status_rx = self.status_rx.clone();
2129 let result = status_rx
2130 .wait_for(|s| match s {
2131 Err(_) => true,
2132 Ok(s) => predicate(s),
2133 })
2134 .await;
2135 let Ok(result) = result else {
2136 drop(result);
2137 debug!("wal listener tx dropped - wait on db close");
2138 return self.closed_reader.clone().await_value().await;
2139 };
2140 let result = result.clone();
2141 result?;
2142 Ok(())
2143 }
2144
2145 async fn wait_until_wal_flushed(&self, last_flushed_wal_id: u64) -> Result<(), SlateDBError> {
2147 self.wait_on_condition(|status| status.last_flushed_wal_id > last_flushed_wal_id)
2148 .await
2149 }
2150}
2151
2152#[cfg(test)]
2153mod tests {
2154 use super::*;
2155 use crate::block_cache_policy::BlockCachePolicy;
2156 use crate::config::DurabilityLevel::{Memory, Remote};
2157 use crate::config::MetricLevel;
2158 use crate::config::{
2159 CheckpointOptions, CompactionWorkerOptions, CompactorOptions,
2160 GarbageCollectorDirectoryOptions, GarbageCollectorOptions, ObjectStoreCacheOptions,
2161 PutOptions, ScanOptions, Settings, SstBlockSize, Ttl, WriteOptions,
2162 };
2163 use crate::db::builder::GarbageCollectorBuilder;
2164 use crate::db_stats::IMMUTABLE_MEMTABLE_FLUSHES;
2165 use crate::format::sst::SsTableFormat;
2166 use crate::instrumented_object_store::stats::{
2167 REQUEST_COUNT as OBJECT_STORE_REQUEST_COUNT,
2168 REQUEST_DURATION_SECONDS as OBJECT_STORE_REQUEST_DURATION_SECONDS,
2169 };
2170 use crate::iter::RowEntryIterator;
2171 use crate::manifest::store::{ManifestStore, StoredManifest};
2172 use crate::manifest::{ManifestCore, VersionedManifest};
2173 use crate::merge_operator::{
2174 MERGE_OPERATOR_COMPACT_PATH, MERGE_OPERATOR_FLUSH_PATH, MERGE_OPERATOR_READ_PATH,
2175 };
2176 use crate::object_stores::ObjectStores;
2177 use crate::proptest_util::arbitrary;
2178 use crate::proptest_util::sample;
2179 use crate::seq_tracker::FindOption;
2180 use crate::sst_iter::{SstIterator, SstIteratorOptions};
2181 use crate::tablestore::TableStoreKind;
2182 use crate::test_utils::{
2183 assert_iterator, lookup_merge_operator_operands, GatedObjectStore,
2184 OnDemandCompactionSchedulerSupplier, StringConcatMergeOperator,
2185 };
2186 use crate::types::RowEntry;
2187 use crate::wal::WalError;
2188 use crate::wal_reader::WalReader;
2189 use crate::{proptest_util, test_utils, CloseReason, CompactorBuilder, KeyValue};
2190 use async_trait::async_trait;
2191 use chrono::{TimeZone, Utc};
2192 use fail_parallel::FailPointRegistry;
2193 use futures::{future, future::join_all, FutureExt, StreamExt};
2194 use object_store::memory::InMemory;
2195 use object_store::ObjectStore;
2196 use proptest::test_runner::{TestRng, TestRunner};
2197 use slatedb_common::clock::DefaultSystemClock;
2198 use slatedb_common::clock::MockSystemClock;
2199 use slatedb_common::metrics::{
2200 lookup_metric, lookup_metric_with_labels, DefaultMetricsRecorder, MetricValue,
2201 };
2202 use std::collections::BTreeMap;
2203 use std::collections::Bound::Included;
2204 use std::sync::atomic::{AtomicBool, Ordering};
2205 use std::time::Duration;
2206 use tokio::runtime::Runtime;
2207 use tracing::info;
2208
2209 fn object_store_labels(
2210 component: &'static str,
2211 store_type: &'static str,
2212 op: &'static str,
2213 api: &'static str,
2214 ) -> [(&'static str, &'static str); 4] {
2215 [
2216 ("component", component),
2217 ("store_type", store_type),
2218 ("op", op),
2219 ("api", api),
2220 ]
2221 }
2222
2223 fn lookup_object_store_histogram_count(
2224 recorder: &DefaultMetricsRecorder,
2225 labels: &[(&str, &str)],
2226 ) -> Option<u64> {
2227 recorder
2228 .snapshot()
2229 .by_name_and_labels(OBJECT_STORE_REQUEST_DURATION_SECONDS, labels)
2230 .map(|metric| match &metric.value {
2231 MetricValue::Histogram { count, .. } => *count,
2232 other => panic!("expected histogram metric, got {other:?}"),
2233 })
2234 }
2235
2236 fn lookup_object_store_op_request_count(
2237 recorder: &DefaultMetricsRecorder,
2238 component: &'static str,
2239 store_type: &'static str,
2240 op: &'static str,
2241 ) -> i64 {
2242 let apis = match op {
2243 "get" => &["get", "get_range", "get_ranges", "head"][..],
2244 "put" => &[
2245 "put",
2246 "multipart_init",
2247 "multipart_part",
2248 "multipart_complete",
2249 ][..],
2250 "delete" => &["delete"][..],
2251 _ => panic!("unexpected op {op}"),
2252 };
2253
2254 apis.iter()
2255 .map(|api| {
2256 lookup_metric_with_labels(
2257 recorder,
2258 OBJECT_STORE_REQUEST_COUNT,
2259 &object_store_labels(component, store_type, op, api),
2260 )
2261 .unwrap_or(0)
2262 })
2263 .sum()
2264 }
2265
2266 fn lookup_object_store_op_histogram_count(
2267 recorder: &DefaultMetricsRecorder,
2268 component: &'static str,
2269 store_type: &'static str,
2270 op: &'static str,
2271 ) -> u64 {
2272 let apis = match op {
2273 "get" => &["get", "get_range", "get_ranges", "head"][..],
2274 "put" => &[
2275 "put",
2276 "multipart_init",
2277 "multipart_part",
2278 "multipart_complete",
2279 ][..],
2280 "delete" => &["delete"][..],
2281 _ => panic!("unexpected op {op}"),
2282 };
2283
2284 apis.iter()
2285 .map(|api| {
2286 lookup_object_store_histogram_count(
2287 recorder,
2288 &object_store_labels(component, store_type, op, api),
2289 )
2290 .unwrap_or(0)
2291 })
2292 .sum()
2293 }
2294
2295 #[tokio::test]
2296 async fn test_put_get_delete() {
2297 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
2298 let kv_store = Db::builder("/tmp/test_kv_store", object_store)
2299 .with_settings(test_db_options(0, 1024, None))
2300 .build()
2301 .await
2302 .unwrap();
2303
2304 let key = b"test_key";
2305 let value = b"test_value";
2306 kv_store.put(key, value).await.unwrap();
2307 kv_store.flush().await.unwrap();
2308
2309 assert_eq!(
2310 kv_store.get(key).await.unwrap(),
2311 Some(Bytes::from_static(value))
2312 );
2313 kv_store.delete(key).await.unwrap();
2314 assert_eq!(None, kv_store.get(key).await.unwrap());
2315 kv_store.close().await.unwrap();
2316 }
2317
2318 #[tokio::test]
2319 async fn test_manifest_returns_current_versioned_manifest() {
2320 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
2321 let db = Db::builder("/tmp/test_manifest_accessor", object_store)
2322 .with_settings(test_db_options(0, 1024, None))
2323 .build()
2324 .await
2325 .unwrap();
2326
2327 db.put(b"test_key", b"test_value").await.unwrap();
2328
2329 let manifest = db.manifest();
2330 let expected: VersionedManifest = db.inner.state.read().state().manifest.clone().into();
2331 assert_eq!(manifest, expected);
2332
2333 db.close().await.unwrap();
2334 }
2335
2336 #[tokio::test]
2337 async fn test_coarse_size_estimation_via_manifest() {
2338 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
2339 let path = "/tmp/test_coarse_size_estimation";
2340 let should_compact = Arc::new(AtomicBool::new(false));
2341 let should_compact_clone = should_compact.clone();
2342 let compaction_scheduler = Arc::new(OnDemandCompactionSchedulerSupplier::new(Arc::new(
2343 move |_state| should_compact_clone.swap(false, Ordering::SeqCst),
2344 )));
2345 let db = Db::builder(path, object_store.clone())
2346 .with_settings(test_db_options(0, 1024, None))
2347 .with_compactor_builder(
2348 CompactorBuilder::new(path, object_store.clone())
2349 .with_scheduler_supplier(compaction_scheduler)
2350 .with_options(fast_compactor_options()),
2351 )
2352 .build()
2353 .await
2354 .unwrap();
2355 let db = Arc::new(db);
2356
2357 for i in 0..100u32 {
2359 let key = format!("k{:04}", i);
2360 db.put(key.as_bytes(), &[0u8; 64]).await.unwrap();
2361 }
2362 db.flush().await.unwrap();
2363
2364 let manifest = db.manifest();
2366 assert!(!manifest.manifest.core.tree.l0.is_empty());
2367 for view in &manifest.manifest.core.tree.l0 {
2368 assert!(view.estimate_size() > 0);
2369 }
2370
2371 should_compact.store(true, Ordering::SeqCst);
2373 let db_poll = db.clone();
2374 tokio::time::timeout(Duration::from_secs(10), async move {
2375 loop {
2376 {
2377 let state = db_poll.inner.state.read();
2378 if !state.state().core().tree.compacted.is_empty() {
2379 return;
2380 }
2381 }
2382 tokio::time::sleep(Duration::from_millis(10)).await;
2383 }
2384 })
2385 .await
2386 .unwrap();
2387
2388 let manifest = db.manifest();
2389 assert!(!manifest.manifest.core.tree.compacted.is_empty());
2390
2391 for sr in &manifest.manifest.core.tree.compacted {
2392 let covering = sr
2394 .tables_covering_range(Bytes::from_static(b"k0000")..Bytes::from_static(b"k0100"));
2395 assert!(!covering.is_empty());
2396 for view in &covering {
2397 assert!(view.estimate_size() > 0);
2398 }
2399
2400 let outside = sr
2402 .tables_covering_range(Bytes::from_static(b"a0000")..Bytes::from_static(b"a9999"));
2403 assert!(outside.is_empty());
2404 }
2405
2406 db.close().await.unwrap();
2407 }
2408
2409 #[tokio::test]
2410 async fn test_scan_prefix_returns_matching_keys() {
2411 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
2412 let kv_store = Db::builder("/tmp/test_scan_prefix", object_store)
2413 .with_settings(test_db_options(0, 1024, None))
2414 .build()
2415 .await
2416 .unwrap();
2417
2418 kv_store.put(b"ab", b"v0").await.unwrap();
2419 kv_store.put(b"aba", b"v1").await.unwrap();
2420 kv_store.put(b"abb", b"v2").await.unwrap();
2421 kv_store.put(b"ac", b"v3").await.unwrap();
2422
2423 let mut iter = kv_store.scan_prefix(b"ab", ..).await.unwrap();
2424 assert_eq!(iter.next().await.unwrap().unwrap().key.as_ref(), b"ab");
2425 assert_eq!(iter.next().await.unwrap().unwrap().key.as_ref(), b"aba");
2426 assert_eq!(iter.next().await.unwrap().unwrap().key.as_ref(), b"abb");
2427 assert_eq!(iter.next().await.unwrap(), None);
2428
2429 kv_store.close().await.unwrap();
2430 }
2431
2432 #[tokio::test]
2433 async fn test_scan_and_prefix_range_forms_are_accepted() {
2434 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
2435 let kv_store = Db::builder("/tmp/test_scan_range_forms", object_store)
2436 .with_settings(test_db_options(0, 1024, None))
2437 .build()
2438 .await
2439 .unwrap();
2440
2441 kv_store.put(b"a", b"v0").await.unwrap();
2442 kv_store.put(b"aa", b"v1").await.unwrap();
2443 kv_store.put(b"ab", b"v2").await.unwrap();
2444 kv_store.put(b"b", b"v3").await.unwrap();
2445
2446 let mut all = kv_store.scan(..).await.unwrap();
2447 assert_eq!(all.next().await.unwrap().unwrap().key.as_ref(), b"a");
2448 assert_eq!(all.next().await.unwrap().unwrap().key.as_ref(), b"aa");
2449 assert_eq!(all.next().await.unwrap().unwrap().key.as_ref(), b"ab");
2450 assert_eq!(all.next().await.unwrap().unwrap().key.as_ref(), b"b");
2451 assert_eq!(all.next().await.unwrap(), None);
2452
2453 let mut range = kv_store.scan(b"a".to_vec()..=b"ab".to_vec()).await.unwrap();
2454 assert_eq!(range.next().await.unwrap().unwrap().key.as_ref(), b"a");
2455 assert_eq!(range.next().await.unwrap().unwrap().key.as_ref(), b"aa");
2456 assert_eq!(range.next().await.unwrap().unwrap().key.as_ref(), b"ab");
2457 assert_eq!(range.next().await.unwrap(), None);
2458
2459 let mut prefix = kv_store.scan_prefix(b"a", b"".to_vec()..).await.unwrap();
2460 assert_eq!(prefix.next().await.unwrap().unwrap().key.as_ref(), b"a");
2461 assert_eq!(prefix.next().await.unwrap().unwrap().key.as_ref(), b"aa");
2462 assert_eq!(prefix.next().await.unwrap().unwrap().key.as_ref(), b"ab");
2463 assert_eq!(prefix.next().await.unwrap(), None);
2464
2465 let mut bounded_prefix = kv_store
2466 .scan_prefix(b"a", b"a".to_vec()..=b"b".to_vec())
2467 .await
2468 .unwrap();
2469 assert_eq!(
2470 bounded_prefix.next().await.unwrap().unwrap().key.as_ref(),
2471 b"aa"
2472 );
2473 assert_eq!(
2474 bounded_prefix.next().await.unwrap().unwrap().key.as_ref(),
2475 b"ab"
2476 );
2477 assert_eq!(bounded_prefix.next().await.unwrap(), None);
2478
2479 kv_store.close().await.unwrap();
2480 }
2481
2482 #[tokio::test]
2483 async fn test_scan_descending_returns_records_in_reverse_order() {
2484 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
2485 let kv_store = Db::builder("/tmp/test_scan_descending", object_store)
2486 .with_settings(test_db_options(0, 1024, None))
2487 .build()
2488 .await
2489 .unwrap();
2490
2491 kv_store.put(b"a", b"v0").await.unwrap();
2492 kv_store.put(b"b", b"v1").await.unwrap();
2493 kv_store.flush().await.unwrap();
2494 kv_store.put(b"c", b"v2").await.unwrap();
2495 kv_store.put(b"d", b"v3").await.unwrap();
2496
2497 let scan_options = ScanOptions::default().with_order(IterationOrder::Descending);
2498 let mut iter = kv_store.scan_with_options(.., &scan_options).await.unwrap();
2499 assert_eq!(iter.next().await.unwrap().unwrap().key.as_ref(), b"d");
2500 assert_eq!(iter.next().await.unwrap().unwrap().key.as_ref(), b"c");
2501 assert_eq!(iter.next().await.unwrap().unwrap().key.as_ref(), b"b");
2502 assert_eq!(iter.next().await.unwrap().unwrap().key.as_ref(), b"a");
2503 assert_eq!(iter.next().await.unwrap(), None);
2504
2505 kv_store.close().await.unwrap();
2506 }
2507
2508 #[tokio::test]
2509 async fn test_scan_descending_bounded_range() {
2510 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
2511 let kv_store = Db::builder("/tmp/test_scan_descending_bounded", object_store)
2512 .with_settings(test_db_options(0, 1024, None))
2513 .build()
2514 .await
2515 .unwrap();
2516
2517 kv_store.put(b"a", b"v0").await.unwrap();
2518 kv_store.put(b"b", b"v1").await.unwrap();
2519 kv_store.put(b"c", b"v2").await.unwrap();
2520 kv_store.put(b"d", b"v3").await.unwrap();
2521 kv_store.put(b"e", b"v4").await.unwrap();
2522
2523 let scan_options = ScanOptions::default().with_order(IterationOrder::Descending);
2524 let mut iter = kv_store
2525 .scan_with_options(b"b".to_vec()..b"d".to_vec(), &scan_options)
2526 .await
2527 .unwrap();
2528 assert_eq!(iter.next().await.unwrap().unwrap().key.as_ref(), b"c");
2529 assert_eq!(iter.next().await.unwrap().unwrap().key.as_ref(), b"b");
2530 assert_eq!(iter.next().await.unwrap(), None);
2531
2532 kv_store.close().await.unwrap();
2533 }
2534
2535 #[tokio::test]
2536 async fn test_scan_descending_skips_deleted_keys() {
2537 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
2538 let kv_store = Db::builder("/tmp/test_scan_descending_deletes", object_store)
2539 .with_settings(test_db_options(0, 1024, None))
2540 .build()
2541 .await
2542 .unwrap();
2543
2544 kv_store.put(b"a", b"v0").await.unwrap();
2545 kv_store.put(b"b", b"v1").await.unwrap();
2546 kv_store.put(b"c", b"v2").await.unwrap();
2547 kv_store.put(b"d", b"v3").await.unwrap();
2548 kv_store.delete(b"b").await.unwrap();
2549 kv_store.delete(b"d").await.unwrap();
2550
2551 let scan_options = ScanOptions::default().with_order(IterationOrder::Descending);
2552 let mut iter = kv_store.scan_with_options(.., &scan_options).await.unwrap();
2553 assert_eq!(iter.next().await.unwrap().unwrap().key.as_ref(), b"c");
2554 assert_eq!(iter.next().await.unwrap().unwrap().key.as_ref(), b"a");
2555 assert_eq!(iter.next().await.unwrap(), None);
2556
2557 kv_store.close().await.unwrap();
2558 }
2559
2560 #[tokio::test]
2561 async fn test_scan_prefix_descending() {
2562 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
2563 let kv_store = Db::builder("/tmp/test_scan_prefix_descending", object_store)
2564 .with_settings(test_db_options(0, 1024, None))
2565 .build()
2566 .await
2567 .unwrap();
2568
2569 kv_store.put(b"prefix/a", b"v0").await.unwrap();
2570 kv_store.put(b"prefix/b", b"v1").await.unwrap();
2571 kv_store.put(b"prefix/c", b"v2").await.unwrap();
2572 kv_store.put(b"other/a", b"v3").await.unwrap();
2573
2574 let scan_options = ScanOptions::default().with_order(IterationOrder::Descending);
2575 let mut iter = kv_store
2576 .scan_prefix_with_options(b"prefix/", .., &scan_options)
2577 .await
2578 .unwrap();
2579 assert_eq!(
2580 iter.next().await.unwrap().unwrap().key.as_ref(),
2581 b"prefix/c"
2582 );
2583 assert_eq!(
2584 iter.next().await.unwrap().unwrap().key.as_ref(),
2585 b"prefix/b"
2586 );
2587 assert_eq!(
2588 iter.next().await.unwrap().unwrap().key.as_ref(),
2589 b"prefix/a"
2590 );
2591 assert_eq!(iter.next().await.unwrap(), None);
2592
2593 kv_store.close().await.unwrap();
2594 }
2595
2596 #[tokio::test]
2597 async fn test_scan_prefix_with_options_handles_unbounded_end() {
2598 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
2599 let kv_store = Db::builder("/tmp/test_scan_prefix_unbounded", object_store)
2600 .with_settings(test_db_options(0, 1024, None))
2601 .build()
2602 .await
2603 .unwrap();
2604
2605 kv_store.put(&[0xff, 0xff], b"v0").await.unwrap();
2606 kv_store.put(&[0xff, 0xff, 0x00], b"v1").await.unwrap();
2607 kv_store.put(&[0xff, 0xff, 0x10], b"v2").await.unwrap();
2608 kv_store.put(&[0xff, 0xff, 0xff], b"v4").await.unwrap();
2609 kv_store.put(&[0xff, 0xfe], b"v3").await.unwrap();
2610
2611 let scan_options = ScanOptions {
2612 cache_blocks: false,
2613 ..ScanOptions::default()
2614 };
2615 let mut iter = kv_store
2616 .scan_prefix_with_options(&[0xff, 0xff], .., &scan_options)
2617 .await
2618 .unwrap();
2619 assert_eq!(
2620 iter.next().await.unwrap().unwrap().key.as_ref(),
2621 &[0xff, 0xff]
2622 );
2623 assert_eq!(
2624 iter.next().await.unwrap().unwrap().key.as_ref(),
2625 &[0xff, 0xff, 0x00]
2626 );
2627 assert_eq!(
2628 iter.next().await.unwrap().unwrap().key.as_ref(),
2629 &[0xff, 0xff, 0x10]
2630 );
2631 assert_eq!(
2632 iter.next().await.unwrap().unwrap().key.as_ref(),
2633 &[0xff, 0xff, 0xff]
2634 );
2635 assert_eq!(iter.next().await.unwrap(), None);
2636
2637 kv_store.close().await.unwrap();
2638 }
2639
2640 fn assert_value(entry: &crate::types::RowEntry, expected: &[u8]) {
2641 match &entry.value {
2642 crate::types::ValueDeletable::Value(v) => assert_eq!(v.as_ref(), expected),
2643 other => panic!("expected Value({expected:?}), got {other:?}"),
2644 }
2645 }
2646
2647 #[tokio::test]
2648 async fn test_scan_prefix_by_recency_returns_matching_keys_from_memtable() {
2649 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
2650 let db = Db::builder("/tmp/test_recency_memtable", object_store)
2651 .with_settings(test_db_options(0, 64 * 1024, None))
2652 .build()
2653 .await
2654 .unwrap();
2655
2656 db.put(b"px:a", b"v0").await.unwrap();
2657 db.put(b"px:b", b"v1").await.unwrap();
2658 db.put(b"px:c", b"v2").await.unwrap();
2659 db.put(b"qq:x", b"vx").await.unwrap();
2660
2661 let mut iter = db.scan_prefix_by_recency(b"px:").await.unwrap();
2662 let e1 = iter.next_entry().await.unwrap().unwrap();
2663 let e2 = iter.next_entry().await.unwrap().unwrap();
2664 let e3 = iter.next_entry().await.unwrap().unwrap();
2665 assert_eq!(e1.key.as_ref(), b"px:a");
2666 assert_eq!(e2.key.as_ref(), b"px:b");
2667 assert_eq!(e3.key.as_ref(), b"px:c");
2668 assert!(iter.next_entry().await.unwrap().is_none());
2669
2670 db.close().await.unwrap();
2671 }
2672
2673 #[tokio::test]
2674 async fn test_scan_prefix_by_recency_no_match_returns_none() {
2675 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
2676 let db = Db::builder("/tmp/test_recency_no_match", object_store)
2677 .with_settings(test_db_options(0, 64 * 1024, None))
2678 .build()
2679 .await
2680 .unwrap();
2681
2682 db.put(b"aa", b"v0").await.unwrap();
2683 db.put(b"ab", b"v1").await.unwrap();
2684
2685 let mut iter = db.scan_prefix_by_recency(b"zz").await.unwrap();
2686 assert!(iter.next_entry().await.unwrap().is_none());
2687
2688 db.close().await.unwrap();
2689 }
2690
2691 #[tokio::test]
2692 async fn test_scan_prefix_by_recency_emits_both_versions_across_sources() {
2693 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
2696 let db = Db::builder("/tmp/test_recency_no_dedup", object_store)
2697 .with_settings(test_db_options(0, 64 * 1024, None))
2698 .build()
2699 .await
2700 .unwrap();
2701
2702 db.put(b"px:a", b"old").await.unwrap();
2703 db.flush_with_options(FlushOptions {
2704 flush_type: FlushType::MemTable,
2705 })
2706 .await
2707 .unwrap();
2708
2709 db.put(b"px:a", b"new").await.unwrap();
2710
2711 let mut iter = db.scan_prefix_by_recency(b"px:").await.unwrap();
2712 let e1 = iter.next_entry().await.unwrap().unwrap();
2713 assert_eq!(e1.key.as_ref(), b"px:a");
2714 assert_value(&e1, b"new");
2715 let e2 = iter.next_entry().await.unwrap().unwrap();
2716 assert_eq!(e2.key.as_ref(), b"px:a");
2717 assert_value(&e2, b"old");
2718 assert!(e1.seq > e2.seq);
2719 assert!(iter.next_entry().await.unwrap().is_none());
2720
2721 db.close().await.unwrap();
2722 }
2723
2724 #[tokio::test]
2725 async fn test_scan_prefix_by_recency_emits_tombstones() {
2726 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
2729 let db = Db::builder("/tmp/test_recency_tombstones", object_store)
2730 .with_settings(test_db_options(0, 64 * 1024, None))
2731 .build()
2732 .await
2733 .unwrap();
2734
2735 db.put(b"px:a", b"va").await.unwrap();
2736 db.put(b"px:b", b"vb").await.unwrap();
2737 db.flush_with_options(FlushOptions {
2738 flush_type: FlushType::MemTable,
2739 })
2740 .await
2741 .unwrap();
2742
2743 db.delete(b"px:a").await.unwrap();
2744
2745 let mut iter = db.scan_prefix_by_recency(b"px:").await.unwrap();
2746 let e1 = iter.next_entry().await.unwrap().unwrap();
2748 assert_eq!(e1.key.as_ref(), b"px:a");
2749 assert!(e1.value.is_tombstone());
2750 let e2 = iter.next_entry().await.unwrap().unwrap();
2752 assert_eq!(e2.key.as_ref(), b"px:a");
2753 assert_value(&e2, b"va");
2754 let e3 = iter.next_entry().await.unwrap().unwrap();
2755 assert_eq!(e3.key.as_ref(), b"px:b");
2756 assert_value(&e3, b"vb");
2757 assert!(e1.seq > e2.seq);
2760 assert!(iter.next_entry().await.unwrap().is_none());
2761
2762 db.close().await.unwrap();
2763 }
2764
2765 #[tokio::test]
2766 async fn test_scan_prefix_by_recency_walks_memtable_then_l0() {
2767 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
2768 let db = Db::builder("/tmp/test_recency_multi_source", object_store)
2769 .with_settings(test_db_options(0, 64 * 1024, None))
2770 .build()
2771 .await
2772 .unwrap();
2773
2774 db.put(b"px:b", b"old_b").await.unwrap();
2776 db.put(b"px:c", b"vc").await.unwrap();
2777 db.flush_with_options(FlushOptions {
2778 flush_type: FlushType::MemTable,
2779 })
2780 .await
2781 .unwrap();
2782
2783 db.put(b"px:a", b"va").await.unwrap();
2785 db.put(b"px:b", b"new_b").await.unwrap();
2786
2787 let mut iter = db.scan_prefix_by_recency(b"px:").await.unwrap();
2788 let e1 = iter.next_entry().await.unwrap().unwrap();
2790 assert_eq!(e1.key.as_ref(), b"px:a");
2791 assert_value(&e1, b"va");
2792 let e2 = iter.next_entry().await.unwrap().unwrap();
2793 assert_eq!(e2.key.as_ref(), b"px:b");
2794 assert_value(&e2, b"new_b");
2795 let e3 = iter.next_entry().await.unwrap().unwrap();
2797 assert_eq!(e3.key.as_ref(), b"px:b");
2798 assert_value(&e3, b"old_b");
2799 let e4 = iter.next_entry().await.unwrap().unwrap();
2800 assert_eq!(e4.key.as_ref(), b"px:c");
2801 assert_value(&e4, b"vc");
2802 assert!(iter.next_entry().await.unwrap().is_none());
2803
2804 db.close().await.unwrap();
2805 }
2806
2807 #[tokio::test]
2808 async fn test_scan_prefix_by_recency_active_memtable_avoids_main_object_store_gets() {
2809 let metrics_recorder = Arc::new(DefaultMetricsRecorder::new());
2813 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
2814 let db = Db::builder("/tmp/test_recency_no_main_gets", object_store)
2815 .with_settings(test_db_options(0, 64 * 1024, None))
2816 .with_metrics_recorder(metrics_recorder.clone())
2817 .build()
2818 .await
2819 .unwrap();
2820
2821 db.put(b"qq:a", b"vqa").await.unwrap();
2824 db.put(b"qq:b", b"vqb").await.unwrap();
2825 db.flush_with_options(FlushOptions {
2826 flush_type: FlushType::MemTable,
2827 })
2828 .await
2829 .unwrap();
2830
2831 db.put(b"px:a", b"va").await.unwrap();
2833 db.put(b"px:b", b"vb").await.unwrap();
2834
2835 let gets_before =
2836 lookup_object_store_op_request_count(&metrics_recorder, "db", "main", "get");
2837
2838 let mut iter = db.scan_prefix_by_recency(b"px:").await.unwrap();
2839 let e1 = iter.next_entry().await.unwrap().unwrap();
2840 let e2 = iter.next_entry().await.unwrap().unwrap();
2841 assert_eq!(e1.key.as_ref(), b"px:a");
2842 assert_eq!(e2.key.as_ref(), b"px:b");
2843 let gets_after =
2846 lookup_object_store_op_request_count(&metrics_recorder, "db", "main", "get");
2847 assert_eq!(
2848 gets_before, gets_after,
2849 "scan_prefix_by_recency should not touch the main object store \
2850 when the prefix is fully covered by the active memtable"
2851 );
2852
2853 db.close().await.unwrap();
2854 }
2855
2856 #[tokio::test]
2857 async fn test_scan_prefix_by_recency_durability_remote_filters_unflushed() {
2858 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
2862 let db = Db::builder("/tmp/test_recency_remote_durability", object_store)
2863 .with_settings(test_db_options(0, 64 * 1024, None))
2864 .build()
2865 .await
2866 .unwrap();
2867
2868 db.put(b"px:a", b"va_durable").await.unwrap();
2869 db.flush_with_options(FlushOptions {
2870 flush_type: FlushType::MemTable,
2871 })
2872 .await
2873 .unwrap();
2874
2875 db.put_with_options(
2878 b"px:b",
2879 b"vb_dirty",
2880 &PutOptions::default(),
2881 &WriteOptions {
2882 await_durable: false,
2883 seqnum: 0,
2884 },
2885 )
2886 .await
2887 .unwrap();
2888
2889 let opts = ScanOptions {
2890 durability_filter: Remote,
2891 ..ScanOptions::default()
2892 };
2893 let mut iter = db
2894 .scan_prefix_by_recency_with_options(b"px:", &opts)
2895 .await
2896 .unwrap();
2897 let e = iter.next_entry().await.unwrap().unwrap();
2898 assert_eq!(e.key.as_ref(), b"px:a");
2899 assert_value(&e, b"va_durable");
2900 assert!(iter.next_entry().await.unwrap().is_none());
2901
2902 db.close().await.unwrap();
2903 }
2904
2905 #[tokio::test]
2906 async fn test_scan_prefix_by_recency_walks_memtable_then_compacted_run() {
2907 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
2912 let path = "/tmp/test_recency_compacted";
2913 let should_compact = Arc::new(AtomicBool::new(false));
2914 let should_compact_clone = should_compact.clone();
2915 let scheduler = Arc::new(OnDemandCompactionSchedulerSupplier::new(Arc::new(
2916 move |_state| should_compact_clone.swap(false, Ordering::SeqCst),
2917 )));
2918 let db = Db::builder(path, object_store.clone())
2919 .with_settings(test_db_options(0, 64 * 1024, None))
2920 .with_compactor_builder(
2921 CompactorBuilder::new(path, object_store.clone())
2922 .with_scheduler_supplier(scheduler)
2923 .with_options(fast_compactor_options()),
2924 )
2925 .build()
2926 .await
2927 .unwrap();
2928 let db = Arc::new(db);
2929
2930 db.put(b"px:a", b"v_oldest").await.unwrap();
2932 db.flush_with_options(FlushOptions {
2933 flush_type: FlushType::MemTable,
2934 })
2935 .await
2936 .unwrap();
2937
2938 should_compact.store(true, Ordering::SeqCst);
2940 let db_poll = db.clone();
2941 tokio::time::timeout(Duration::from_secs(10), async move {
2942 loop {
2943 {
2944 let state = db_poll.inner.state.read();
2945 if !state.state().core().tree.compacted.is_empty() {
2946 return;
2947 }
2948 }
2949 tokio::time::sleep(Duration::from_millis(10)).await;
2950 }
2951 })
2952 .await
2953 .unwrap();
2954
2955 db.put(b"px:a", b"v_l0").await.unwrap();
2957 db.flush_with_options(FlushOptions {
2958 flush_type: FlushType::MemTable,
2959 })
2960 .await
2961 .unwrap();
2962
2963 db.put(b"px:a", b"v_memtable").await.unwrap();
2965
2966 let mut iter = db.scan_prefix_by_recency(b"px:").await.unwrap();
2967 let e1 = iter.next_entry().await.unwrap().unwrap();
2968 let e2 = iter.next_entry().await.unwrap().unwrap();
2969 let e3 = iter.next_entry().await.unwrap().unwrap();
2970 assert_eq!(e1.key.as_ref(), b"px:a");
2971 assert_value(&e1, b"v_memtable");
2972 assert_eq!(e2.key.as_ref(), b"px:a");
2973 assert_value(&e2, b"v_l0");
2974 assert_eq!(e3.key.as_ref(), b"px:a");
2975 assert_value(&e3, b"v_oldest");
2976 assert!(e1.seq > e2.seq);
2977 assert!(e2.seq > e3.seq);
2978 assert!(iter.next_entry().await.unwrap().is_none());
2979
2980 Arc::into_inner(db)
2981 .expect("db Arc should be uniquely held")
2982 .close()
2983 .await
2984 .unwrap();
2985 }
2986
2987 #[tokio::test]
2988 async fn test_scan_prefix_by_recency_errors_on_multi_segment_prefix() {
2989 use crate::manifest::{LsmTreeState, Segment};
2990 use std::collections::VecDeque;
2991 use std::sync::Arc;
2992
2993 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
2994 let db = Db::builder("/tmp/test_recency_multi_segment", object_store)
2995 .with_settings(test_db_options(0, 64 * 1024, None))
2996 .build()
2997 .await
2998 .unwrap();
2999
3000 db.inner.state.write().modify(|m| {
3005 let core = &mut m.state.manifest.value.core;
3006 core.segment_extractor_name = Some("hour".into());
3007 core.segments = vec![
3008 Segment {
3009 prefix: Bytes::from_static(b"hour=12/"),
3010 tree: Arc::new(LsmTreeState {
3011 last_compacted_l0_sst_view_id: None,
3012 last_compacted_l0_sst_id: None,
3013 l0: VecDeque::new(),
3014 compacted: vec![],
3015 }),
3016 },
3017 Segment {
3018 prefix: Bytes::from_static(b"hour=13/"),
3019 tree: Arc::new(LsmTreeState {
3020 last_compacted_l0_sst_view_id: None,
3021 last_compacted_l0_sst_id: None,
3022 l0: VecDeque::new(),
3023 compacted: vec![],
3024 }),
3025 },
3026 ];
3027 });
3028
3029 match db.scan_prefix_by_recency(b"hour=").await {
3030 Err(e) => assert_eq!(e.kind(), crate::ErrorKind::Invalid),
3031 Ok(_) => panic!("expected multi-segment error"),
3032 }
3033
3034 let mut iter = db.scan_prefix_by_recency(b"hour=12/").await.unwrap();
3036 assert!(iter.next_entry().await.unwrap().is_none());
3037
3038 db.close().await.unwrap();
3039 }
3040
3041 #[test]
3042 fn test_get_after_put() {
3043 let mut runner = new_proptest_runner(None);
3044 let runtime = Runtime::new().unwrap();
3045
3046 let table = sample::table(runner.rng(), 1000, 10);
3047 let db_options = test_db_options(0, 1024, None);
3048 let db = runtime.block_on(build_database_from_table(&table, db_options, true));
3049
3050 runner
3051 .run(
3052 &(arbitrary::bytes(100), arbitrary::bytes(100)),
3053 |(key, value)| {
3054 runtime.block_on(async {
3055 if !key.is_empty() {
3056 db.put_with_options(
3057 &key,
3058 &value,
3059 &PutOptions::default(),
3060 &WriteOptions {
3061 await_durable: false,
3062 ..Default::default()
3063 },
3064 )
3065 .await
3066 .unwrap();
3067 assert_eq!(
3068 Some(value),
3069 db.get_with_options(
3070 &key,
3071 &ReadOptions {
3072 durability_filter: Memory,
3073 dirty: false,
3074 cache_blocks: true,
3075 filter_context: None,
3076 }
3077 )
3078 .await
3079 .unwrap()
3080 );
3081 }
3082 });
3083 Ok(())
3084 },
3085 )
3086 .unwrap();
3087 }
3088
3089 #[tokio::test]
3090 async fn test_no_flush_interval() {
3091 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
3092 let db_options_no_flush_interval = {
3093 let mut db_options = test_db_options(0, 1024, None);
3094 db_options.flush_interval = None;
3095 db_options
3096 };
3097 let kv_store = Db::builder("/tmp/test_kv_store", object_store)
3098 .with_settings(db_options_no_flush_interval)
3099 .build()
3100 .await
3101 .unwrap();
3102 let key = b"test_key";
3103 let value = b"test_value";
3104
3105 kv_store
3106 .put_with_options(
3107 key,
3108 value,
3109 &PutOptions::default(),
3110 &WriteOptions {
3111 await_durable: false,
3112 ..Default::default()
3113 },
3114 )
3115 .await
3116 .unwrap();
3117
3118 assert_ne!(
3120 kv_store
3121 .inner
3122 .wal_observer
3123 .status()
3124 .unwrap()
3125 .estimated_bytes,
3126 0
3127 );
3128
3129 kv_store.flush().await.unwrap();
3131 assert_eq!(
3132 kv_store
3133 .inner
3134 .wal_observer
3135 .status()
3136 .unwrap()
3137 .estimated_bytes,
3138 0
3139 );
3140 }
3141
3142 #[tokio::test]
3143 async fn test_close_triggers_flush_when_open() {
3144 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
3145 let db_options_no_flush_interval = {
3146 let mut db_options = test_db_options(0, 1024, None);
3147 db_options.flush_interval = None;
3148 db_options
3149 };
3150 let metrics_recorder = Arc::new(DefaultMetricsRecorder::new());
3151 let kv_store = Db::builder("/tmp/test_close_triggers_flush", object_store)
3152 .with_settings(db_options_no_flush_interval)
3153 .with_metrics_recorder(metrics_recorder.clone())
3154 .build()
3155 .await
3156 .unwrap();
3157
3158 kv_store
3159 .put_with_options(
3160 b"test_key",
3161 b"test_value",
3162 &PutOptions::default(),
3163 &WriteOptions {
3164 await_durable: false,
3165 ..Default::default()
3166 },
3167 )
3168 .await
3169 .unwrap();
3170
3171 assert_eq!(
3173 kv_store
3174 .inner
3175 .wal_observer
3176 .status()
3177 .unwrap()
3178 .buffered_wal_entries_count,
3179 1
3180 );
3181 assert_eq!(
3182 lookup_metric(
3183 &metrics_recorder,
3184 crate::wal_buffer::stats::WAL_BUFFER_FLUSHES
3185 )
3186 .unwrap(),
3187 0
3188 );
3189
3190 kv_store.close().await.unwrap();
3191
3192 assert_eq!(
3194 kv_store
3195 .inner
3196 .wal_observer
3197 .status()
3198 .unwrap_err()
3199 .buffered_wal_entries_count,
3200 0
3201 );
3202 assert_eq!(
3203 lookup_metric(
3204 &metrics_recorder,
3205 crate::wal_buffer::stats::WAL_BUFFER_FLUSHES
3206 )
3207 .unwrap(),
3208 1
3209 );
3210 }
3211
3212 #[tokio::test]
3213 async fn test_close_twice_returns_closed_clean() {
3214 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
3215 let db = Db::builder("/tmp/test_close_twice_returns_closed_clean", object_store)
3216 .with_settings(test_db_options(0, 1024, None))
3217 .build()
3218 .await
3219 .unwrap();
3220
3221 db.close().await.unwrap();
3222 let err = db.close().await.unwrap_err();
3223 assert_eq!(err.kind(), crate::ErrorKind::Closed(CloseReason::Clean));
3224 }
3225
3226 #[tokio::test]
3227 async fn test_close_failed_state_does_not_flush() {
3228 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
3229 let db_options_no_flush_interval = {
3230 let mut db_options = test_db_options(0, 1024, None);
3231 db_options.flush_interval = None;
3232 db_options
3233 };
3234 let metrics_recorder = Arc::new(DefaultMetricsRecorder::new());
3235 let db = Db::builder("/tmp/test_close_failed_state_no_flush", object_store)
3236 .with_settings(db_options_no_flush_interval)
3237 .with_metrics_recorder(metrics_recorder.clone())
3238 .build()
3239 .await
3240 .unwrap();
3241
3242 let put_seq = db
3243 .put_with_options(
3244 b"test_key",
3245 b"test_value",
3246 &PutOptions::default(),
3247 &WriteOptions {
3248 await_durable: false,
3249 ..Default::default()
3250 },
3251 )
3252 .await
3253 .unwrap()
3254 .seq;
3255
3256 let wal_status = db.inner.wal_observer.status().unwrap();
3258 assert_eq!(wal_status.buffered_wal_entries_count, 1);
3259 assert!(wal_status.last_flushed_seq.unwrap_or(0) < put_seq);
3260 assert_eq!(
3261 lookup_metric(
3262 &metrics_recorder,
3263 crate::wal_buffer::stats::WAL_BUFFER_FLUSHES
3264 )
3265 .unwrap(),
3266 0
3267 );
3268
3269 db.inner
3271 .status_manager
3272 .write_result(Err(crate::error::SlateDBError::Fenced));
3273
3274 db.close().await.unwrap();
3276
3277 let wal_status = db.inner.wal_observer.status().unwrap_err();
3278 assert!(matches!(wal_status.closed_reason, Some(WalError::Fenced)));
3279 assert!(wal_status.last_flushed_seq.unwrap_or(0) < put_seq);
3280 assert_eq!(
3281 lookup_metric(
3282 &metrics_recorder,
3283 crate::wal_buffer::stats::WAL_BUFFER_FLUSHES
3284 )
3285 .unwrap(),
3286 0
3287 );
3288 let status = db.status();
3289 assert_eq!(status.close_reason, Some(CloseReason::Fenced));
3290 }
3291
3292 #[tokio::test]
3293 async fn test_clean_close_flushes_pending_wal() {
3294 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
3295 let db_options_no_flush_interval = {
3296 let mut db_options = test_db_options(0, 1024, None);
3297 db_options.flush_interval = None;
3298 db_options
3299 };
3300 let metrics_recorder = Arc::new(DefaultMetricsRecorder::new());
3301 let db = Db::builder("/tmp/test_clean_close_flushes_pending_wal", object_store)
3302 .with_settings(db_options_no_flush_interval)
3303 .with_metrics_recorder(metrics_recorder.clone())
3304 .build()
3305 .await
3306 .unwrap();
3307
3308 let put_seq = db
3309 .put_with_options(
3310 b"test_key",
3311 b"test_value",
3312 &PutOptions::default(),
3313 &WriteOptions {
3314 await_durable: false,
3315 ..Default::default()
3316 },
3317 )
3318 .await
3319 .unwrap()
3320 .seq;
3321
3322 let wal_status = db.inner.wal_observer.status().unwrap();
3323 assert_eq!(wal_status.buffered_wal_entries_count, 1);
3324 assert!(wal_status.last_flushed_seq.unwrap_or(0) < put_seq);
3325 assert_eq!(
3326 lookup_metric(
3327 &metrics_recorder,
3328 crate::wal_buffer::stats::WAL_BUFFER_FLUSHES
3329 )
3330 .unwrap(),
3331 0
3332 );
3333
3334 db.close().await.unwrap();
3335
3336 let wal_status = db.inner.wal_observer.status().unwrap_err();
3337 assert!(matches!(wal_status.closed_reason, Some(WalError::Closed)));
3338 assert_eq!(wal_status.last_flushed_seq, Some(put_seq));
3339 assert_eq!(
3340 lookup_metric(
3341 &metrics_recorder,
3342 crate::wal_buffer::stats::WAL_BUFFER_FLUSHES
3343 )
3344 .unwrap(),
3345 1
3346 );
3347 let status = db.status();
3348 assert_eq!(status.close_reason, Some(CloseReason::Clean));
3349 }
3350
3351 #[tokio::test]
3352 async fn test_close_flushes_memtables_to_l0() {
3353 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
3354 let db_options = {
3355 let mut db_options = test_db_options(0, 1024, None);
3356 db_options.flush_interval = None;
3357 db_options
3358 };
3359 let metrics_recorder = Arc::new(DefaultMetricsRecorder::new());
3360 let db = Db::builder("/tmp/test_close_flushes_memtables_to_l0", object_store)
3361 .with_settings(db_options)
3362 .with_metrics_recorder(metrics_recorder.clone())
3363 .build()
3364 .await
3365 .unwrap();
3366
3367 db.put_with_options(
3368 b"test_key",
3369 b"test_value",
3370 &PutOptions::default(),
3371 &WriteOptions {
3372 await_durable: false,
3373 ..Default::default()
3374 },
3375 )
3376 .await
3377 .unwrap();
3378
3379 assert_eq!(
3381 lookup_metric(&metrics_recorder, crate::db_stats::L0_FLUSH_BYTES).unwrap_or(0),
3382 0
3383 );
3384
3385 db.close().await.unwrap();
3386
3387 assert!(
3389 lookup_metric(&metrics_recorder, crate::db_stats::L0_FLUSH_BYTES).unwrap() > 0,
3390 "expected L0 flush during close"
3391 );
3392 }
3393
3394 #[tokio::test]
3395 async fn test_memtable_write_bytes_matches_batch_payload() {
3396 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
3397 let metrics_recorder = Arc::new(DefaultMetricsRecorder::new());
3398 let db = Db::builder(
3399 "/tmp/test_memtable_write_bytes_matches_batch_payload",
3400 object_store,
3401 )
3402 .with_settings(test_db_options(0, 1024 * 1024, None))
3403 .with_metrics_recorder(metrics_recorder.clone())
3404 .build()
3405 .await
3406 .unwrap();
3407
3408 db.put(b"hello", b"world!").await.unwrap();
3409 assert_eq!(
3410 lookup_metric(&metrics_recorder, crate::db_stats::MEMTABLE_WRITE_BYTES).unwrap(),
3411 (b"hello".len() + b"world!".len()) as i64,
3412 );
3413
3414 db.put(b"k2", b"v2").await.unwrap();
3415 assert_eq!(
3416 lookup_metric(&metrics_recorder, crate::db_stats::MEMTABLE_WRITE_BYTES).unwrap(),
3417 (b"hello".len() + b"world!".len() + b"k2".len() + b"v2".len()) as i64,
3418 );
3419
3420 db.delete(b"k3").await.unwrap();
3422 assert_eq!(
3423 lookup_metric(&metrics_recorder, crate::db_stats::MEMTABLE_WRITE_BYTES).unwrap(),
3424 (b"hello".len() + b"world!".len() + b"k2".len() + b"v2".len() + b"k3".len()) as i64,
3425 );
3426
3427 db.close().await.unwrap();
3428 }
3429
3430 #[tokio::test]
3431 async fn test_memtable_write_bytes_matches_batch_payload_with_merges() {
3432 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
3433 let metrics_recorder = Arc::new(DefaultMetricsRecorder::new());
3434 let db = Db::builder(
3435 "/tmp/test_memtable_write_bytes_matches_batch_payload_with_merges",
3436 object_store,
3437 )
3438 .with_settings(test_db_options(0, 1024 * 1024, None))
3439 .with_merge_operator(Arc::new(StringConcatMergeOperator {}))
3440 .with_metrics_recorder(metrics_recorder.clone())
3441 .build()
3442 .await
3443 .unwrap();
3444
3445 db.merge(b"k1", b"a").await.unwrap();
3448 let mut expected = (b"k1".len() + b"a".len()) as i64;
3449 assert_eq!(
3450 lookup_metric(&metrics_recorder, crate::db_stats::MEMTABLE_WRITE_BYTES).unwrap(),
3451 expected,
3452 );
3453
3454 let mut batch = WriteBatch::new();
3458 batch.merge(b"k2", b"a");
3459 batch.merge(b"k2", b"b");
3460 batch.merge(b"k2", b"c");
3461 db.write(batch).await.unwrap();
3462 expected += (b"k2".len() + b"abc".len()) as i64;
3463 assert_eq!(
3464 lookup_metric(&metrics_recorder, crate::db_stats::MEMTABLE_WRITE_BYTES).unwrap(),
3465 expected,
3466 );
3467
3468 let mut batch = WriteBatch::new();
3471 batch.merge(b"k3", b"x");
3472 batch.merge(b"k4", b"yy");
3473 db.write(batch).await.unwrap();
3474 expected += (b"k3".len() + b"x".len() + b"k4".len() + b"yy".len()) as i64;
3475 assert_eq!(
3476 lookup_metric(&metrics_recorder, crate::db_stats::MEMTABLE_WRITE_BYTES).unwrap(),
3477 expected,
3478 );
3479
3480 db.close().await.unwrap();
3481 }
3482
3483 #[tokio::test]
3484 async fn test_wal_flush_bytes_after_explicit_flush() {
3485 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
3486 let metrics_recorder = Arc::new(DefaultMetricsRecorder::new());
3487 let db_options = {
3488 let mut db_options = test_db_options(0, 1024 * 1024, None);
3489 db_options.flush_interval = None;
3490 db_options
3491 };
3492 let db = Db::builder(
3493 "/tmp/test_wal_flush_bytes_after_explicit_flush",
3494 object_store,
3495 )
3496 .with_settings(db_options)
3497 .with_metrics_recorder(metrics_recorder.clone())
3498 .build()
3499 .await
3500 .unwrap();
3501
3502 db.put_with_options(
3503 b"hello",
3504 b"world",
3505 &PutOptions::default(),
3506 &WriteOptions {
3507 await_durable: false,
3508 ..Default::default()
3509 },
3510 )
3511 .await
3512 .unwrap();
3513 assert_eq!(
3514 lookup_metric(&metrics_recorder, crate::wal_buffer::stats::WAL_FLUSH_BYTES)
3515 .unwrap_or(0),
3516 0,
3517 );
3518
3519 db.flush().await.unwrap();
3520
3521 let wal_bytes =
3522 lookup_metric(&metrics_recorder, crate::wal_buffer::stats::WAL_FLUSH_BYTES).unwrap();
3523 let memtable_bytes =
3524 lookup_metric(&metrics_recorder, crate::db_stats::MEMTABLE_WRITE_BYTES).unwrap();
3525 assert!(
3528 wal_bytes >= memtable_bytes,
3529 "wal_bytes={wal_bytes} memtable_bytes={memtable_bytes}",
3530 );
3531 db.close().await.unwrap();
3532 }
3533
3534 #[tokio::test]
3535 #[cfg(feature = "wal_disable")]
3536 async fn test_get_with_durability_level_when_wal_disabled() {
3537 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
3538 let mut options = test_db_options(0, 1024 * 1024, None);
3539 options.wal_enabled = false;
3540 let db = Db::builder("/tmp/test_kv_store", object_store)
3541 .with_settings(options)
3542 .build()
3543 .await
3544 .unwrap();
3545 tokio::time::timeout(
3546 Duration::from_secs(1),
3547 db.task_executor
3548 .join_task(crate::wal_buffer::WAL_BUFFER_TASK_NAME),
3549 )
3550 .await
3551 .expect("native WAL task should not run when the WAL is disabled")
3552 .unwrap();
3553 let put_options = PutOptions::default();
3554 let write_options = WriteOptions {
3555 await_durable: false,
3556 ..Default::default()
3557 };
3558 let get_memory_options = ReadOptions::new().with_durability_filter(Memory);
3559 let get_remote_options = ReadOptions::new().with_durability_filter(Remote);
3560
3561 db.put_with_options(b"foo", b"bar", &put_options, &write_options)
3562 .await
3563 .unwrap();
3564 let val_bytes = Bytes::copy_from_slice(b"bar");
3565 assert_eq!(
3566 None,
3567 db.get_with_options(b"foo", &get_remote_options)
3568 .await
3569 .unwrap()
3570 );
3571 assert_eq!(
3572 Some(val_bytes.clone()),
3573 db.get_with_options(b"foo", &get_memory_options)
3574 .await
3575 .unwrap()
3576 );
3577 db.flush().await.unwrap();
3578 assert_eq!(
3579 Some(val_bytes.clone()),
3580 db.get_with_options(b"foo", &get_remote_options)
3581 .await
3582 .unwrap()
3583 );
3584 assert_eq!(
3585 Some(val_bytes.clone()),
3586 db.get_with_options(b"foo", &get_memory_options)
3587 .await
3588 .unwrap()
3589 );
3590 }
3591
3592 #[tokio::test]
3593 #[cfg(feature = "wal_disable")]
3594 async fn test_find_with_multiple_repeated_keys() {
3595 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
3596 let mut options = test_db_options(0, 1024 * 1024, None);
3597 options.wal_enabled = false;
3598 let db = Db::builder("/tmp/test_kv_store", object_store)
3599 .with_settings(options)
3600 .build()
3601 .await
3602 .unwrap();
3603
3604 let mut last_val: String = "foo".to_string();
3606 for x in 0..4096 {
3607 let val = format!("val{}", x);
3608 db.put_with_options(
3609 b"key",
3610 val.as_bytes(),
3611 &PutOptions {
3612 ttl: Default::default(),
3613 },
3614 &WriteOptions {
3615 await_durable: false,
3616 ..Default::default()
3617 },
3618 )
3619 .await
3620 .unwrap();
3621 last_val = val;
3622 if db
3623 .inner
3624 .state
3625 .write()
3626 .memtable()
3627 .metadata()
3628 .entries_size_in_bytes
3629 > (SsTableFormat::default().block_size * 3)
3630 {
3631 break;
3632 }
3633 }
3634 assert_eq!(
3635 Some(Bytes::copy_from_slice(last_val.as_bytes())),
3636 db.get_with_options(b"key", &ReadOptions::new().with_durability_filter(Memory))
3637 .await
3638 .unwrap()
3639 );
3640 db.flush().await.unwrap();
3641
3642 let state = db.inner.state.read().view();
3643 assert_eq!(1, state.state.manifest.value.core.tree.l0.len());
3644 let view = state.state.manifest.value.core.tree.l0.front().unwrap();
3645 let index = db
3646 .inner
3647 .table_store
3648 .read_index(&view.sst, true)
3649 .await
3650 .unwrap();
3651 assert!(!index.borrow().block_meta().is_empty());
3652 assert_eq!(
3653 Some(Bytes::copy_from_slice(last_val.as_bytes())),
3654 db.get(b"key").await.unwrap()
3655 );
3656 db.close().await.unwrap();
3657 }
3658
3659 #[tokio::test]
3660 async fn test_db_records_main_and_wal_object_store_requests_separately() {
3661 let main_object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
3663 let wal_object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
3664 let metrics_recorder = Arc::new(DefaultMetricsRecorder::new());
3665 let path = "/tmp/test_db_records_main_and_wal_object_store_requests_separately";
3666 let db = Db::builder(path, main_object_store)
3667 .with_settings(test_db_options(0, 1024, None))
3668 .with_wal_object_store(wal_object_store)
3669 .with_metrics_recorder(metrics_recorder.clone())
3670 .build()
3671 .await
3672 .unwrap();
3673
3674 let wal_before =
3675 lookup_object_store_op_request_count(&metrics_recorder, "db", "wal", "put");
3676 let main_before =
3677 lookup_object_store_op_request_count(&metrics_recorder, "db", "main", "put");
3678 let wal_hist_before =
3679 lookup_object_store_op_histogram_count(&metrics_recorder, "db", "wal", "put");
3680 let main_hist_before =
3681 lookup_object_store_op_histogram_count(&metrics_recorder, "db", "main", "put");
3682
3683 db.put(b"key", b"value").await.unwrap();
3685 db.flush().await.unwrap();
3686 db.flush_with_options(FlushOptions {
3687 flush_type: FlushType::MemTable,
3688 })
3689 .await
3690 .unwrap();
3691
3692 let wal_after = lookup_object_store_op_request_count(&metrics_recorder, "db", "wal", "put");
3693 let main_after =
3694 lookup_object_store_op_request_count(&metrics_recorder, "db", "main", "put");
3695 let wal_hist_after =
3696 lookup_object_store_op_histogram_count(&metrics_recorder, "db", "wal", "put");
3697 let main_hist_after =
3698 lookup_object_store_op_histogram_count(&metrics_recorder, "db", "main", "put");
3699
3700 assert!(wal_after > wal_before);
3702 assert!(main_after > main_before);
3703 assert!(wal_hist_after > wal_hist_before);
3704 assert!(main_hist_after > main_hist_before);
3705 db.close().await.unwrap();
3706 }
3707
3708 async fn build_database_from_table(
3709 table: &BTreeMap<Bytes, Bytes>,
3710 db_options: Settings,
3711 await_durable: bool,
3712 ) -> Db {
3713 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
3714 let db = Db::builder("/tmp/test_kv_store", object_store)
3715 .with_settings(db_options)
3716 .build()
3717 .await
3718 .unwrap();
3719
3720 test_utils::seed_database(&db, table, false).await.unwrap();
3721
3722 if await_durable {
3723 db.flush().await.unwrap();
3724 }
3725
3726 db
3727 }
3728
3729 #[tokio::test]
3730 async fn test_should_allow_iterating_behind_box_dyn() {
3731 #[async_trait]
3732 trait IteratorSupplier {
3733 async fn iterator(&self) -> Box<dyn IteratorTrait>;
3734 }
3735
3736 struct DbHolder {
3737 db: Db,
3738 }
3739
3740 #[async_trait]
3741 impl IteratorSupplier for DbHolder {
3742 async fn iterator(&self) -> Box<dyn IteratorTrait> {
3743 let range = BytesRange::new_empty();
3744 let iter = self
3745 .db
3746 .inner
3747 .scan_with_options(range, &ScanOptions::default(), None)
3748 .await
3749 .unwrap();
3750 Box::new(iter)
3751 }
3752 }
3753
3754 #[async_trait]
3755 trait IteratorTrait {
3756 async fn next(&mut self) -> Result<Option<KeyValue>, crate::Error>;
3757 }
3758
3759 #[async_trait]
3760 impl IteratorTrait for DbIterator {
3761 async fn next(&mut self) -> Result<Option<KeyValue>, crate::Error> {
3762 DbIterator::next(self).await
3763 }
3764 }
3765
3766 let db_options = test_db_options(0, 1024, None);
3767 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
3768 let db = Db::builder("/tmp/test_kv_store", object_store)
3769 .with_settings(db_options)
3770 .build()
3771 .await
3772 .unwrap();
3773 let db_holder = DbHolder { db };
3774 let mut boxed = db_holder.iterator().await;
3775 let next = boxed.next().await;
3776 assert_eq!(next.unwrap(), None);
3777 }
3778
3779 async fn assert_records_in_range(
3780 table: &BTreeMap<Bytes, Bytes>,
3781 db: &Db,
3782 scan_options: &ScanOptions,
3783 range: BytesRange,
3784 ) {
3785 let mut iter = db
3786 .inner
3787 .scan_with_options(range.clone(), scan_options, None)
3788 .await
3789 .unwrap();
3790 test_utils::assert_ranged_db_scan(table, range, IterationOrder::Ascending, &mut iter).await;
3791 }
3792
3793 #[test]
3794 fn test_scan_returns_records_in_range() {
3795 let mut runner = new_proptest_runner(None);
3796 let table = sample::table(runner.rng(), 1000, 5);
3797
3798 let runtime = Runtime::new().unwrap();
3799 let db_options = test_db_options(0, 1024, None);
3800 let db = runtime.block_on(build_database_from_table(&table, db_options, true));
3801
3802 runner
3803 .run(&arbitrary::nonempty_range(10), |range| {
3804 runtime.block_on(assert_records_in_range(
3805 &table,
3806 &db,
3807 &ScanOptions::default(),
3808 range,
3809 ));
3810 Ok(())
3811 })
3812 .unwrap();
3813 }
3814
3815 fn new_proptest_runner(rng_seed: Option<[u8; 32]>) -> TestRunner {
3816 proptest_util::runner::new(file!(), rng_seed)
3817 }
3818
3819 #[test]
3820 fn test_scan_returns_uncommitted_records_if_read_level_uncommitted() {
3821 let mut runner = new_proptest_runner(None);
3822 let table = sample::table(runner.rng(), 1000, 5);
3823
3824 let runtime = Runtime::new().unwrap();
3825 let mut db_options = test_db_options(0, 1024, None);
3826 db_options.flush_interval = Some(Duration::from_secs(5));
3827 let db = runtime.block_on(build_database_from_table(&table, db_options, false));
3828
3829 runner
3830 .run(&arbitrary::nonempty_range(10), |range| {
3831 let scan_options = ScanOptions {
3832 durability_filter: Memory,
3833 ..ScanOptions::default()
3834 };
3835 runtime.block_on(assert_records_in_range(&table, &db, &scan_options, range));
3836 Ok(())
3837 })
3838 .unwrap();
3839 }
3840
3841 #[test]
3842 fn test_seek_outside_of_range_returns_invalid_argument() {
3843 let mut runner = new_proptest_runner(None);
3844 let table = sample::table(runner.rng(), 1000, 10);
3845
3846 let runtime = Runtime::new().unwrap();
3847 let db_options = test_db_options(0, 1024, None);
3848 let db = runtime.block_on(build_database_from_table(&table, db_options, true));
3849
3850 runner
3851 .run(
3852 &(arbitrary::nonempty_bytes(10), arbitrary::rng()),
3853 |(arbitrary_key, mut rng)| {
3854 runtime.block_on(assert_out_of_bound_seek_returns_invalid_argument(
3855 &db,
3856 &mut rng,
3857 arbitrary_key,
3858 ));
3859 Ok(())
3860 },
3861 )
3862 .unwrap();
3863
3864 async fn assert_out_of_bound_seek_returns_invalid_argument(
3865 db: &Db,
3866 rng: &mut TestRng,
3867 arbitrary_key: Bytes,
3868 ) {
3869 let mut iter = db
3870 .scan_with_options(..arbitrary_key.clone(), &ScanOptions::default())
3871 .await
3872 .unwrap();
3873
3874 let lower_bounded_range = BytesRange::from(arbitrary_key.clone()..);
3875 let value = sample::bytes_in_range(rng, &lower_bounded_range);
3876 let err = iter.seek(value.clone()).await.unwrap_err();
3877 assert!(
3878 err.to_string()
3879 .contains("cannot seek to a key outside the iterator range"),
3880 "{}",
3881 err
3882 );
3883
3884 let mut iter = db
3885 .scan_with_options(arbitrary_key.clone().., &ScanOptions::default())
3886 .await
3887 .unwrap();
3888
3889 let upper_bounded_range = BytesRange::from(..arbitrary_key.clone());
3890 let value = sample::bytes_in_range(rng, &upper_bounded_range);
3891 let err = iter.seek(value.clone()).await.unwrap_err();
3892 assert!(
3893 err.to_string()
3894 .contains("cannot seek to a key outside the iterator range"),
3895 "{}",
3896 err
3897 );
3898 }
3899 }
3900
3901 #[test]
3902 fn test_seek_fast_forwards_iterator() {
3903 let mut runner = new_proptest_runner(None);
3904 let table = sample::table(runner.rng(), 1000, 10);
3905
3906 let runtime = Runtime::new().unwrap();
3907 let db_options = test_db_options(0, 1024, None);
3908 let db = runtime.block_on(build_database_from_table(&table, db_options, true));
3909
3910 runner
3911 .run(
3912 &(arbitrary::nonempty_range(5), arbitrary::rng()),
3913 |(range, mut rng)| {
3914 runtime.block_on(assert_seek_fast_forwards_iterator(
3915 &table, &db, &range, &mut rng,
3916 ));
3917 Ok(())
3918 },
3919 )
3920 .unwrap();
3921
3922 async fn assert_seek_fast_forwards_iterator(
3923 table: &BTreeMap<Bytes, Bytes>,
3924 db: &Db,
3925 scan_range: &BytesRange,
3926 rng: &mut TestRng,
3927 ) {
3928 let mut iter = db
3929 .inner
3930 .scan_with_options(scan_range.clone(), &ScanOptions::default(), None)
3931 .await
3932 .unwrap();
3933
3934 let seek_key = sample::bytes_in_range(rng, scan_range);
3935 iter.seek(seek_key.clone()).await.unwrap();
3936
3937 let seek_range = BytesRange::new(
3938 Included(seek_key),
3939 std::ops::RangeBounds::end_bound(scan_range).cloned(),
3940 );
3941 test_utils::assert_ranged_db_scan(
3942 table,
3943 seek_range,
3944 IterationOrder::Ascending,
3945 &mut iter,
3946 )
3947 .await;
3948 }
3949 }
3950
3951 #[tokio::test]
3952 async fn test_write_batch() {
3953 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
3954 let kv_store = Db::builder("/tmp/test_kv_store", object_store)
3955 .with_settings(test_db_options(0, 1024, None))
3956 .build()
3957 .await
3958 .unwrap();
3959
3960 let mut batch = WriteBatch::new();
3962 batch.put(b"key1", b"value1");
3963 batch.put(b"key2", b"value2");
3964 batch.delete(b"key1");
3965
3966 kv_store.write(batch).await.expect("write batch failed");
3968
3969 assert_eq!(kv_store.get(b"key1").await.unwrap(), None);
3971 assert_eq!(
3972 kv_store.get(b"key2").await.unwrap(),
3973 Some(Bytes::from_static(b"value2"))
3974 );
3975
3976 kv_store.close().await.unwrap();
3977 }
3978
3979 #[cfg(feature = "wal_disable")]
3980 #[tokio::test]
3981 async fn test_write_batch_without_wal() {
3982 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
3983 let mut options = test_db_options(0, 8, None);
3985
3986 options.wal_enabled = false;
3988
3989 let kv_store = Db::builder("/tmp/test_kv_store_without_wal", object_store.clone())
3990 .with_settings(options)
3991 .build()
3992 .await
3993 .unwrap();
3994
3995 let mut batch = WriteBatch::new();
3997 batch.put(b"key1", b"value1");
3998 batch.put(b"key2", b"value2");
3999 batch.delete(b"key1");
4000
4001 kv_store.write(batch).await.expect("write batch failed");
4003
4004 assert_eq!(kv_store.get(b"key1").await.unwrap(), None);
4006 assert_eq!(kv_store.get(b"key2").await.unwrap(), Some("value2".into()));
4007
4008 kv_store.close().await.unwrap();
4009 }
4010
4011 #[tokio::test]
4012 async fn test_write_batch_with_empty_key() {
4013 let mut batch = WriteBatch::new();
4014 let result = std::panic::catch_unwind(move || {
4015 batch.put(b"", b"value");
4016 });
4017 assert!(
4018 result.is_err(),
4019 "Expected panic when using empty key in put operation"
4020 );
4021
4022 let mut batch = WriteBatch::new();
4023 let result = std::panic::catch_unwind(move || {
4024 batch.delete(b"");
4025 });
4026 assert!(
4027 result.is_err(),
4028 "Expected panic when using empty key in delete operation"
4029 );
4030 }
4031
4032 #[tokio::test]
4044 async fn test_concurrent_batch_writes_consistency() {
4045 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
4046 let kv_store = Arc::new(
4047 Db::builder("/tmp/test_concurrent_kv_store", object_store)
4048 .with_settings(test_db_options(
4049 0,
4050 1024,
4051 Some(CompactorOptions {
4054 poll_interval: Duration::from_millis(100),
4055 max_concurrent_compactions: 1,
4056 manifest_update_timeout: Duration::from_secs(300),
4057 worker: Some(CompactionWorkerOptions {
4058 max_sst_size: 256,
4059 ..Default::default()
4060 }),
4061 ..Default::default()
4062 }),
4063 ))
4064 .build()
4065 .await
4066 .unwrap(),
4067 );
4068
4069 const NUM_KEYS: usize = 100;
4070 const NUM_ROUNDS: usize = 20;
4071
4072 for _ in 0..NUM_ROUNDS {
4073 let task1 = {
4075 let store = kv_store.clone();
4076 tokio::spawn(async move {
4077 let mut batch = WriteBatch::new();
4078 for key in 1..=NUM_KEYS {
4079 batch.put(key.to_be_bytes(), key.to_be_bytes());
4080 }
4081 store.write(batch).await.expect("write batch failed");
4082 })
4083 };
4084
4085 let task2 = {
4086 let store = kv_store.clone();
4087 tokio::spawn(async move {
4088 let mut batch = WriteBatch::new();
4089 for key in 1..=NUM_KEYS {
4090 let value = (key * 2).to_be_bytes();
4091 batch.put(key.to_be_bytes(), value);
4092 }
4093 store.write(batch).await.expect("write batch failed");
4094 })
4095 };
4096
4097 join_all(vec![task1, task2]).await;
4099
4100 let mut all_key = true;
4102 let mut all_key2 = true;
4103
4104 for key in 1..=NUM_KEYS {
4105 let value = kv_store.get(key.to_be_bytes()).await.unwrap();
4106 let value = value.expect("Value should exist");
4107
4108 if value.as_ref() != key.to_be_bytes() {
4109 all_key = false;
4110 }
4111 if value.as_ref() != (key * 2).to_be_bytes() {
4112 all_key2 = false;
4113 }
4114 }
4115
4116 assert!(
4118 all_key || all_key2,
4119 "Inconsistent state: not all values match either key or key * 2"
4120 );
4121 }
4122
4123 kv_store.close().await.unwrap();
4124 }
4125
4126 #[tokio::test]
4127 #[cfg(feature = "wal_disable")]
4128 async fn test_disable_wal_after_wal_enabled() {
4129 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
4130 let path = "/tmp/test_kv_store";
4131 let options = test_db_options(0, 32, None);
4133 let db = Db::builder(path, object_store.clone())
4134 .with_settings(options)
4135 .build()
4136 .await
4137 .unwrap();
4138 db.put(&[b'a'; 4], &[b'j'; 4]).await.unwrap();
4139 db.put(&[b'b'; 4], &[b'k'; 4]).await.unwrap();
4140 db.close().await.unwrap();
4141
4142 let mut options = test_db_options(0, 32, None);
4144 options.wal_enabled = false;
4145 let db = Db::builder(path, object_store.clone())
4146 .with_settings(options.clone())
4147 .build()
4148 .await
4149 .unwrap();
4150 db.delete_with_options(
4151 &[b'b'; 4],
4152 &WriteOptions {
4153 await_durable: false,
4154 ..Default::default()
4155 },
4156 )
4157 .await
4158 .unwrap();
4159 db.put(&[b'a'; 4], &[b'z'; 64]).await.unwrap();
4160 db.close().await.unwrap();
4161
4162 let db = Db::builder(path, object_store.clone())
4164 .with_settings(options.clone())
4165 .build()
4166 .await
4167 .unwrap();
4168 let val = db.get(&[b'a'; 4]).await.unwrap();
4169 assert_eq!(val.unwrap(), Bytes::copy_from_slice(&[b'z'; 64]));
4170 let val = db.get(&[b'b'; 4]).await.unwrap();
4171 assert!(val.is_none());
4172 }
4173
4174 #[cfg(feature = "wal_disable")]
4175 #[tokio::test]
4176 async fn test_wal_disabled() {
4177 use crate::{test_utils::assert_iterator, types::RowEntry};
4178
4179 let clock = Arc::new(MockSystemClock::new());
4180 let mut options = test_db_options(0, 350, None);
4181 options.wal_enabled = false;
4182 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
4183 let path = Path::from("/tmp/test_kv_store");
4184 let sst_format = SsTableFormat::default();
4185 let table_store = Arc::new(TableStore::new(
4186 ObjectStores::new(object_store.clone(), None),
4187 sst_format,
4188 path.clone(),
4189 None,
4190 TableStoreKind::Main,
4191 BlockCachePolicy::default(),
4192 ));
4193 let db = Db::builder(path.clone(), object_store.clone())
4194 .with_settings(options)
4195 .with_system_clock(clock.clone())
4196 .build()
4197 .await
4198 .unwrap();
4199 let manifest_store = Arc::new(ManifestStore::new(&path, object_store.clone()));
4200 let mut stored_manifest =
4201 StoredManifest::load(manifest_store.clone(), Arc::new(DefaultSystemClock::new()))
4202 .await
4203 .unwrap();
4204 let write_options = WriteOptions {
4205 await_durable: false,
4206 ..Default::default()
4207 };
4208
4209 db.put_with_options(
4210 &[b'a'; 32],
4211 &[b'j'; 32],
4212 &PutOptions::default(),
4213 &write_options,
4214 )
4215 .await
4216 .unwrap();
4217 db.delete_with_options(&[b'b'; 31], &write_options)
4218 .await
4219 .unwrap();
4220
4221 let write_options = WriteOptions {
4225 await_durable: true,
4226 ..Default::default()
4227 };
4228 clock.set(10);
4229 db.put_with_options(
4230 &[b'c'; 32],
4231 &[b'l'; 32],
4232 &PutOptions::default(),
4233 &write_options,
4234 )
4235 .await
4236 .unwrap();
4237
4238 let state = wait_for_manifest_condition(
4239 &mut stored_manifest,
4240 |s| !s.tree.l0.is_empty(),
4241 Duration::from_secs(30),
4242 )
4243 .await;
4244 assert_eq!(state.tree.l0.len(), 1);
4245
4246 let l0 = state.tree.l0.front().unwrap();
4247 let mut iter = SstIterator::new_borrowed_initialized(
4248 ..,
4249 l0,
4250 table_store.clone(),
4251 SstIteratorOptions::default(),
4252 )
4253 .await
4254 .unwrap()
4255 .expect("Expected Some(iter) but got None");
4256 assert_iterator(
4257 &mut iter,
4258 vec![
4259 RowEntry::new_value(&[b'a'; 32], &[b'j'; 32], 1).with_create_ts(0),
4260 RowEntry::new_tombstone(&[b'b'; 31], 2).with_create_ts(0),
4261 RowEntry::new_value(&[b'c'; 32], &[b'l'; 32], 3).with_create_ts(10),
4262 ],
4263 )
4264 .await;
4265 }
4266
4267 #[tokio::test]
4268 async fn test_put_flushes_memtable() {
4269 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
4270 let path = "/tmp/test_kv_store";
4271 let metrics_recorder = Arc::new(DefaultMetricsRecorder::new());
4272 let kv_store = Db::builder(path, object_store.clone())
4273 .with_settings(test_db_options(0, 320, None))
4274 .with_metrics_recorder(metrics_recorder.clone())
4275 .build()
4276 .await
4277 .unwrap();
4278 let manifest_store = Arc::new(ManifestStore::new(&Path::from(path), object_store.clone()));
4279 let mut stored_manifest =
4280 StoredManifest::load(manifest_store.clone(), Arc::new(DefaultSystemClock::new()))
4281 .await
4282 .unwrap();
4283 let sst_format = SsTableFormat {
4284 min_filter_keys: 10,
4285 ..SsTableFormat::default()
4286 };
4287 let table_store = Arc::new(TableStore::new(
4288 ObjectStores::new(object_store.clone(), None),
4289 sst_format,
4290 path,
4291 None,
4292 TableStoreKind::Main,
4293 BlockCachePolicy::default(),
4294 ));
4295
4296 let mut last_wal_id = 0;
4298 for i in 0..3 {
4299 let key = [b'a' + i; 16];
4300 let value = [b'b' + i; 50];
4301 kv_store.put(&key, &value).await.unwrap();
4302 let key = [b'j' + i; 16];
4303 let value = [b'k' + i; 50];
4304 kv_store.put(&key, &value).await.unwrap();
4305 let db_state = wait_for_manifest_condition(
4306 &mut stored_manifest,
4307 |s| s.replay_after_wal_id > last_wal_id,
4308 Duration::from_secs(30),
4309 )
4310 .await;
4311
4312 assert_eq!(db_state.replay_after_wal_id, (i as u64) * 2 + 2);
4314 last_wal_id = db_state.replay_after_wal_id
4315 }
4316
4317 let manifest = stored_manifest.refresh().await.unwrap();
4318 let l0 = &manifest.core.tree.l0;
4319 assert_eq!(l0.len(), 3);
4320 let sst_iter_options = SstIteratorOptions::default();
4321
4322 for i in 0u8..3u8 {
4323 let sst1 = l0.get(2 - i as usize).unwrap();
4324 let mut iter = SstIterator::new_borrowed_initialized(
4325 ..,
4326 sst1,
4327 table_store.clone(),
4328 sst_iter_options.clone(),
4329 )
4330 .await
4331 .unwrap()
4332 .expect("Expected Some(iter) but got None");
4333 let kv: KeyValue = iter.next().await.unwrap().unwrap().into();
4334 assert_eq!(kv.key.as_ref(), [b'a' + i; 16]);
4335 assert_eq!(kv.value.as_ref(), [b'b' + i; 50]);
4336 let kv: KeyValue = iter.next().await.unwrap().unwrap().into();
4337 assert_eq!(kv.key.as_ref(), [b'j' + i; 16]);
4338 assert_eq!(kv.value.as_ref(), [b'k' + i; 50]);
4339 let kv = iter.next().await.unwrap().map(KeyValue::from);
4340 assert!(kv.is_none());
4341 }
4342 assert!(lookup_metric(&metrics_recorder, IMMUTABLE_MEMTABLE_FLUSHES).is_some_and(|v| v > 0));
4343 }
4344
4345 #[tokio::test]
4346 async fn test_put_flushes_memtable_after_max_wal_flushes() {
4347 const MAX_WAL_FLUSHES_BEFORE_L0_FLUSH: u64 = 4096;
4348
4349 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
4350 let path = "/tmp/test_flush_memtable_max_wal_flushes";
4351
4352 let mut settings = test_db_options(0, 64 * 1024 * 1024, None);
4353 settings.flush_interval = None; settings.max_wal_flushes_before_l0_flush = MAX_WAL_FLUSHES_BEFORE_L0_FLUSH;
4355
4356 let kv_store = Db::builder(path, object_store.clone())
4357 .with_settings(settings)
4358 .build()
4359 .await
4360 .unwrap();
4361
4362 let manifest_store = Arc::new(ManifestStore::new(&Path::from(path), object_store.clone()));
4363 let mut stored_manifest =
4364 StoredManifest::load(manifest_store.clone(), Arc::new(DefaultSystemClock::new()))
4365 .await
4366 .unwrap();
4367
4368 let write_options: WriteOptions = WriteOptions {
4369 await_durable: false,
4370 ..Default::default()
4371 };
4372 let put_options = PutOptions::default();
4373
4374 for i in 0..(MAX_WAL_FLUSHES_BEFORE_L0_FLUSH - 1) {
4375 let key = format!("key{:08}", i);
4376 kv_store
4377 .put_with_options(key.as_bytes(), b"v", &put_options, &write_options)
4378 .await
4379 .unwrap();
4380 kv_store.flush().await.unwrap();
4381 }
4382
4383 let wal_id = kv_store
4385 .inner
4386 .wal_observer
4387 .status()
4388 .unwrap()
4389 .last_flushed_wal_id;
4390 assert_eq!(wal_id, MAX_WAL_FLUSHES_BEFORE_L0_FLUSH); {
4394 let guard = kv_store.inner.state.read();
4395 assert!(guard.state().imm_memtable.is_empty());
4396 assert_eq!(guard.state().core().tree.l0.len(), 0);
4397 }
4398
4399 let key = format!("key{:08}", MAX_WAL_FLUSHES_BEFORE_L0_FLUSH - 1);
4401 kv_store
4402 .put_with_options(key.as_bytes(), b"v", &put_options, &write_options)
4403 .await
4404 .unwrap();
4405 kv_store.flush().await.unwrap();
4408
4409 let db_state = wait_for_manifest_condition(
4413 &mut stored_manifest,
4414 |s| s.replay_after_wal_id == MAX_WAL_FLUSHES_BEFORE_L0_FLUSH,
4415 Duration::from_secs(30),
4416 )
4417 .await;
4418 assert_eq!(db_state.tree.l0.len(), 1);
4419
4420 for i in 0..(MAX_WAL_FLUSHES_BEFORE_L0_FLUSH - 1) {
4423 let key = format!("key{:08}", i);
4424 kv_store
4425 .put_with_options(key.as_bytes(), b"v", &put_options, &write_options)
4426 .await
4427 .unwrap();
4428 kv_store.flush().await.unwrap();
4429 }
4430
4431 {
4433 let guard = kv_store.inner.state.read();
4434 assert_eq!(guard.state().core().tree.l0.len(), 1);
4435 }
4436
4437 let key = format!("key{:08}", MAX_WAL_FLUSHES_BEFORE_L0_FLUSH);
4439 kv_store
4440 .put_with_options(key.as_bytes(), b"v", &put_options, &write_options)
4441 .await
4442 .unwrap();
4443 kv_store.flush().await.unwrap();
4444
4445 let db_state = wait_for_manifest_condition(
4447 &mut stored_manifest,
4448 |s| s.replay_after_wal_id == MAX_WAL_FLUSHES_BEFORE_L0_FLUSH * 2,
4449 Duration::from_secs(30),
4450 )
4451 .await;
4452 assert_eq!(db_state.tree.l0.len(), 2); kv_store.close().await.unwrap();
4455 }
4456
4457 #[tokio::test]
4458 async fn test_flush_memtable_with_wal_enabled() {
4459 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
4460 let path = "/tmp/test_flush_with_options";
4461 let mut options = test_db_options(0, 256, None);
4462 options.flush_interval = Some(Duration::from_secs(u64::MAX));
4463 let metrics_recorder = Arc::new(DefaultMetricsRecorder::new());
4464 let kv_store = Db::builder(path, object_store.clone())
4465 .with_settings(options)
4466 .with_metrics_recorder(metrics_recorder.clone())
4467 .build()
4468 .await
4469 .unwrap();
4470
4471 let manifest_store = Arc::new(ManifestStore::new(&Path::from(path), object_store.clone()));
4472 let mut stored_manifest =
4473 StoredManifest::load(manifest_store.clone(), Arc::new(DefaultSystemClock::new()))
4474 .await
4475 .unwrap();
4476 let sst_format = SsTableFormat {
4477 min_filter_keys: 10,
4478 ..SsTableFormat::default()
4479 };
4480 let table_store = Arc::new(TableStore::new(
4481 ObjectStores::new(object_store.clone(), None),
4482 sst_format,
4483 path,
4484 None,
4485 TableStoreKind::Main,
4486 BlockCachePolicy::default(),
4487 ));
4488
4489 let key1 = b"test_key_1";
4491 let value1 = b"test_value_1";
4492 kv_store
4493 .put_with_options(
4494 key1,
4495 value1,
4496 &PutOptions::default(),
4497 &WriteOptions {
4498 await_durable: false,
4499 ..Default::default()
4500 },
4501 )
4502 .await
4503 .unwrap();
4504
4505 let key2 = b"test_key_2";
4506 let value2 = b"test_value_2";
4507 kv_store
4508 .put_with_options(
4509 key2,
4510 value2,
4511 &PutOptions::default(),
4512 &WriteOptions {
4513 await_durable: false,
4514 ..Default::default()
4515 },
4516 )
4517 .await
4518 .unwrap();
4519
4520 let initial_manifest = stored_manifest.refresh().await.unwrap();
4522 let initial_l0_count = initial_manifest.core.tree.l0.len();
4523
4524 let initial_flush_count =
4525 lookup_metric(&metrics_recorder, IMMUTABLE_MEMTABLE_FLUSHES).unwrap();
4526
4527 kv_store
4529 .flush_with_options(FlushOptions {
4530 flush_type: FlushType::MemTable,
4531 })
4532 .await
4533 .unwrap();
4534
4535 let db_state = wait_for_manifest_condition(
4537 &mut stored_manifest,
4538 |s| s.tree.l0.len() > initial_l0_count,
4539 Duration::from_secs(30),
4540 )
4541 .await;
4542
4543 assert_eq!(db_state.tree.l0.len(), initial_l0_count + 1);
4545
4546 let final_flush_count =
4548 lookup_metric(&metrics_recorder, IMMUTABLE_MEMTABLE_FLUSHES).unwrap();
4549 assert!(final_flush_count > initial_flush_count);
4550
4551 let recent_flushed_wal_id = kv_store
4554 .inner
4555 .wal_observer
4556 .status()
4557 .unwrap()
4558 .last_flushed_wal_id;
4559 assert_eq!(recent_flushed_wal_id, 2);
4560
4561 let retrieved_value1 = kv_store.get(key1).await.unwrap().unwrap();
4563 assert_eq!(retrieved_value1.as_ref(), value1);
4564
4565 let retrieved_value2 = kv_store.get(key2).await.unwrap().unwrap();
4566 assert_eq!(retrieved_value2.as_ref(), value2);
4567
4568 let latest_sst = db_state.tree.l0.back().unwrap();
4570 let sst_iter_options = SstIteratorOptions::default();
4571 let mut iter = SstIterator::new_borrowed_initialized(
4572 ..,
4573 latest_sst,
4574 table_store.clone(),
4575 sst_iter_options,
4576 )
4577 .await
4578 .unwrap()
4579 .expect("Expected Some(iter) but got None");
4580
4581 let mut found_keys = std::collections::HashSet::new();
4583 while let Some(kv) = iter.next().await.unwrap().map(KeyValue::from) {
4584 found_keys.insert(kv.key.to_vec());
4585 }
4586
4587 assert!(found_keys.contains(key1.as_slice()));
4589 assert!(found_keys.contains(key2.as_slice()));
4590 }
4591
4592 #[tokio::test]
4593 async fn test_memtable_flush_also_flushes_wal() {
4594 let main_object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
4595 let wal_object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
4596 let path = "/tmp/test_memtable_flush_also_flushes_wal";
4597 let mut settings = test_db_options(0, 1024, None);
4598 settings.flush_interval = None;
4599
4600 let kv_store = Db::builder(path, main_object_store)
4601 .with_settings(settings)
4602 .with_wal_object_store(wal_object_store.clone())
4603 .build()
4604 .await
4605 .unwrap();
4606
4607 let key = b"wal_flush_key";
4608 let value = b"wal_flush_value";
4609 kv_store
4610 .put_with_options(
4611 key,
4612 value,
4613 &PutOptions::default(),
4614 &WriteOptions {
4615 await_durable: false,
4616 ..Default::default()
4617 },
4618 )
4619 .await
4620 .unwrap();
4621
4622 assert_eq!(
4623 kv_store
4624 .inner
4625 .wal_observer
4626 .status()
4627 .unwrap()
4628 .buffered_wal_entries_count,
4629 1
4630 );
4631
4632 kv_store
4633 .flush_with_options(FlushOptions {
4634 flush_type: FlushType::MemTable,
4635 })
4636 .await
4637 .unwrap();
4638
4639 assert_eq!(
4640 kv_store
4641 .inner
4642 .wal_observer
4643 .status()
4644 .unwrap()
4645 .buffered_wal_entries_count,
4646 0
4647 );
4648
4649 let wal_reader = WalReader::new(path, wal_object_store);
4650 let wal_files = wal_reader.list(..).await.unwrap();
4651 assert_eq!(wal_files.len(), 2); let mut rows = Vec::new();
4653 let mut wal_iter = wal_files[1] .iterator()
4655 .await
4656 .expect("expected successful WAL iterator call");
4657 while let Some(entry) = wal_iter
4658 .next()
4659 .await
4660 .expect("expected successful WAL rows read")
4661 {
4662 rows.push(entry);
4663 }
4664 assert_eq!(rows.len(), 1);
4665 let row = &rows[0];
4666 assert_eq!(row.key.as_ref(), key);
4667 assert_eq!(
4668 row.value.as_bytes().expect("expected bytes").as_ref(),
4669 value
4670 );
4671 assert_eq!(row.seq, 1);
4672 }
4673
4674 async fn test_sequence_tracker_persisted_across_flush_and_reload_impl(wal_enabled: bool) {
4675 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
4676 let path = "/tmp/test_sequence_tracker_flush";
4677 let mut settings = test_db_options(0, 256, None);
4678 settings.flush_interval = None;
4679 #[cfg(feature = "wal_disable")]
4680 {
4681 settings.wal_enabled = wal_enabled;
4682 }
4683 #[cfg(not(feature = "wal_disable"))]
4684 let _ = wal_enabled;
4685 let system_clock = Arc::new(MockSystemClock::new());
4686
4687 let kv_store = Db::builder(path, object_store.clone())
4688 .with_settings(settings.clone())
4689 .with_system_clock(system_clock.clone())
4690 .build()
4691 .await
4692 .unwrap();
4693
4694 let manifest_store = Arc::new(ManifestStore::new(&Path::from(path), object_store.clone()));
4695 let mut stored_manifest =
4696 StoredManifest::load(manifest_store.clone(), Arc::new(DefaultSystemClock::new()))
4697 .await
4698 .unwrap();
4699 let write_options = WriteOptions {
4700 await_durable: false,
4701 ..Default::default()
4702 };
4703 let put_options = PutOptions::default();
4704
4705 let timestamps_ms = [0_i64, 60_000, 120_000];
4706 for (idx, ts) in timestamps_ms.iter().enumerate() {
4707 system_clock.set(*ts);
4708 let key = format!("key-{idx}").into_bytes();
4709 let value = format!("value-{idx}").into_bytes();
4710 kv_store
4711 .put_with_options(&key, &value, &put_options, &write_options)
4712 .await
4713 .unwrap();
4714 }
4715
4716 kv_store
4717 .flush_with_options(FlushOptions {
4718 flush_type: FlushType::MemTable,
4719 })
4720 .await
4721 .unwrap();
4722
4723 let target_ts = Utc.timestamp_opt(120, 0).single().unwrap();
4724 let persisted_state = wait_for_manifest_condition(
4725 &mut stored_manifest,
4726 move |core| {
4727 core.sequence_tracker
4728 .find_seq(target_ts, FindOption::RoundDown)
4729 == Some(3)
4730 },
4731 Duration::from_secs(5),
4732 )
4733 .await;
4734
4735 let tracker = persisted_state.sequence_tracker.clone();
4736 let live_tracker = kv_store
4737 .inner
4738 .state
4739 .read()
4740 .state()
4741 .core()
4742 .sequence_tracker
4743 .clone();
4744 assert_eq!(tracker, live_tracker);
4745
4746 let seq1_ts = tracker.find_ts(1, FindOption::RoundDown).unwrap();
4747 assert_eq!(seq1_ts.timestamp(), 0);
4748
4749 let seq2_ts = tracker.find_ts(2, FindOption::RoundDown).unwrap();
4750 assert_eq!(seq2_ts.timestamp(), 60);
4751
4752 let seq3_ts = tracker.find_ts(3, FindOption::RoundDown).unwrap();
4753 assert_eq!(seq3_ts.timestamp(), 120);
4754
4755 let ts_lookup = Utc.timestamp_opt(60, 0).single().unwrap();
4756 assert_eq!(tracker.find_seq(ts_lookup, FindOption::RoundDown), Some(2));
4757
4758 kv_store.close().await.unwrap();
4759
4760 let reopened = Db::builder(path, object_store.clone())
4761 .with_settings(settings)
4762 .with_system_clock(system_clock.clone())
4763 .build()
4764 .await
4765 .unwrap();
4766
4767 let reopened_tracker = reopened
4768 .inner
4769 .state
4770 .read()
4771 .state()
4772 .core()
4773 .sequence_tracker
4774 .clone();
4775 assert_eq!(tracker, reopened_tracker);
4776
4777 reopened.close().await.unwrap();
4778 }
4779
4780 #[tokio::test]
4781 async fn test_sequence_tracker_persisted_across_flush_and_reload_wal_enabled() {
4782 test_sequence_tracker_persisted_across_flush_and_reload_impl(true).await;
4783 }
4784
4785 #[tokio::test]
4786 #[cfg(feature = "wal_disable")]
4787 async fn test_sequence_tracker_persisted_across_flush_and_reload_wal_disabled() {
4788 test_sequence_tracker_persisted_across_flush_and_reload_impl(false).await;
4789 }
4790
4791 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
4792 async fn test_sequence_tracker_not_ahead_of_last_l0_seq_when_flush_races_with_writes() {
4793 let fp_registry = Arc::new(FailPointRegistry::new());
4794 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
4795 let mut settings = test_db_options(0, 2048, None);
4796 settings.flush_interval = None;
4797 settings.manifest_poll_interval = Duration::from_secs(60 * 60);
4799
4800 let system_clock = Arc::new(MockSystemClock::new());
4801 let db = Db::builder(
4802 "/tmp/test_sequence_tracker_flush_race",
4803 object_store.clone(),
4804 )
4805 .with_settings(settings)
4806 .with_system_clock(system_clock.clone())
4807 .with_fp_registry(fp_registry.clone())
4808 .build()
4809 .await
4810 .unwrap();
4811
4812 let write_options = WriteOptions {
4813 await_durable: false,
4814 ..Default::default()
4815 };
4816
4817 async fn put_with_timestamp(
4818 db: &Db,
4819 system_clock: &Arc<MockSystemClock>,
4820 idx: usize,
4821 write_options: &WriteOptions,
4822 ) {
4823 system_clock.set((idx as i64) * 60_000);
4824 let key = format!("race-key-{idx}").into_bytes();
4825 let value = format!("race-value-{idx}").into_bytes();
4826 db.put_with_options(&key, &value, &PutOptions::default(), write_options)
4827 .await
4828 .unwrap();
4829 }
4830
4831 put_with_timestamp(&db, &system_clock, 0, &write_options).await;
4833 put_with_timestamp(&db, &system_clock, 1, &write_options).await;
4834
4835 fail_parallel::cfg(
4838 fp_registry.clone(),
4839 "after-flush-imm-to-l0-before-manifest",
4840 "pause",
4841 )
4842 .unwrap();
4843
4844 let flush_handle = {
4847 let inner = Arc::clone(&db.inner);
4848 tokio::spawn(async move { inner.flush_memtables(FlushTarget::All).await })
4849 };
4850
4851 let mut wrote_l0_sst = false;
4852 for _ in 0..6000 {
4853 let ssts = db.inner.table_store.list_compacted_ssts(..).await.unwrap();
4856 if !ssts.is_empty() {
4857 wrote_l0_sst = true;
4858 break;
4859 }
4860 tokio::time::sleep(Duration::from_millis(10)).await;
4861 }
4862 assert!(
4863 wrote_l0_sst,
4864 "L0 SST was not written before manifest update pause"
4865 );
4866
4867 put_with_timestamp(&db, &system_clock, 2, &write_options).await;
4870 put_with_timestamp(&db, &system_clock, 3, &write_options).await;
4871
4872 fail_parallel::cfg(
4874 fp_registry.clone(),
4875 "after-flush-imm-to-l0-before-manifest",
4876 "off",
4877 )
4878 .unwrap();
4879
4880 flush_handle.await.unwrap().unwrap();
4881
4882 {
4883 let guard = db.inner.state.read();
4884 assert!(guard.state().imm_memtable.is_empty());
4886 }
4887
4888 let manifest_state = {
4889 let guard = db.inner.state.read();
4890 guard.state().manifest.value.core.clone()
4891 };
4892 let last_l0_seq = manifest_state.last_l0_seq;
4893 assert!(
4894 last_l0_seq >= 2,
4895 "expected flushed memtable to advance last_l0_seq"
4896 );
4897
4898 assert!(
4901 manifest_state
4902 .sequence_tracker
4903 .find_ts(last_l0_seq + 1, FindOption::RoundUp)
4904 .is_none(),
4905 "sequence tracker should not advance beyond last_l0_seq (last_l0_seq={})",
4906 last_l0_seq
4907 );
4908
4909 db.close().await.unwrap();
4910 }
4911
4912 #[tokio::test]
4913 async fn test_flush_with_options_wal() {
4914 let fp_registry = Arc::new(FailPointRegistry::new());
4915 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
4916 let path = "/tmp/test_flush_with_options_wal";
4917 let mut options = test_db_options(0, 1024, None);
4918 options.flush_interval = Some(Duration::from_secs(u64::MAX));
4920 fail_parallel::cfg(
4923 fp_registry.clone(),
4924 "write-compacted-sst-io-error",
4925 "return",
4926 )
4927 .unwrap();
4928 let kv_store = Db::builder(path, object_store.clone())
4929 .with_settings(options)
4930 .with_fp_registry(fp_registry.clone())
4931 .build()
4932 .await
4933 .unwrap();
4934
4935 let key1 = b"wal_test_key_1";
4937 let value1 = b"wal_test_value_1";
4938 kv_store
4939 .put_with_options(
4940 key1,
4941 value1,
4942 &PutOptions::default(),
4943 &WriteOptions {
4944 await_durable: false,
4945 ..Default::default()
4946 },
4947 )
4948 .await
4949 .unwrap();
4950
4951 let key2 = b"wal_test_key_2";
4952 let value2 = b"wal_test_value_2";
4953 kv_store
4954 .put_with_options(
4955 key2,
4956 value2,
4957 &PutOptions::default(),
4958 &WriteOptions {
4959 await_durable: false,
4960 ..Default::default()
4961 },
4962 )
4963 .await
4964 .unwrap();
4965
4966 let initial_wal_id = kv_store
4968 .inner
4969 .wal_observer
4970 .status()
4971 .unwrap()
4972 .last_flushed_wal_id;
4973
4974 let flush_result = kv_store
4976 .flush_with_options(FlushOptions {
4977 flush_type: FlushType::Wal,
4978 })
4979 .await;
4980
4981 assert!(flush_result.is_ok(), "WAL flush should succeed");
4983
4984 let retrieved_value1 = kv_store.get(key1).await.unwrap().unwrap();
4986 assert_eq!(retrieved_value1.as_ref(), value1);
4987
4988 let retrieved_value2 = kv_store.get(key2).await.unwrap().unwrap();
4989 assert_eq!(retrieved_value2.as_ref(), value2);
4990
4991 let final_wal_id = kv_store
4994 .inner
4995 .wal_observer
4996 .status()
4997 .unwrap()
4998 .last_flushed_wal_id;
4999 assert!(
5000 final_wal_id >= initial_wal_id,
5001 "WAL ID should not decrease after flush"
5002 );
5003
5004 assert!(
5006 kv_store.inner.status().close_reason.is_none(),
5007 "DB should not have an error state"
5008 );
5009 }
5010
5011 #[tokio::test]
5012 #[cfg(feature = "wal_disable")]
5013 async fn test_flush_with_options_wal_disabled_error() {
5014 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
5015 let path = "/tmp/test_flush_with_options_wal_disabled";
5016 let mut options = test_db_options(0, 1024, None);
5017 options.wal_enabled = false; let kv_store = Db::builder(path, object_store.clone())
5019 .with_settings(options)
5020 .build()
5021 .await
5022 .unwrap();
5023
5024 let key1 = b"test_key_1";
5026 let value1 = b"test_value_1";
5027 kv_store
5028 .put_with_options(
5029 key1,
5030 value1,
5031 &PutOptions::default(),
5032 &WriteOptions {
5033 await_durable: false,
5034 ..Default::default()
5035 },
5036 )
5037 .await
5038 .unwrap();
5039
5040 let flush_result = kv_store
5042 .flush_with_options(FlushOptions {
5043 flush_type: FlushType::Wal,
5044 })
5045 .await;
5046
5047 assert!(flush_result.is_err(), "Expected WalDisabled error");
5049 let error = flush_result.unwrap_err();
5050
5051 assert!(
5052 error
5053 .to_string()
5054 .contains("attempted a WAL operation when the WAL is disabled"),
5055 "Expected WalDisabled error message, got: {}",
5056 error
5057 );
5058
5059 let memtable_flush_result = kv_store
5061 .flush_with_options(FlushOptions {
5062 flush_type: FlushType::MemTable,
5063 })
5064 .await;
5065 assert!(
5066 memtable_flush_result.is_ok(),
5067 "Memtable flush should work even when WAL is disabled"
5068 );
5069
5070 let retrieved_value1 = kv_store.get(key1).await.unwrap().unwrap();
5072 assert_eq!(retrieved_value1.as_ref(), value1);
5073 }
5074
5075 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
5079 async fn test_apply_wal_memory_backpressure() {
5080 let fp_registry = Arc::new(FailPointRegistry::new());
5081 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
5082 let path = Path::from("/tmp/test_kv_store");
5083 let mut options = test_db_options(0, 1, None);
5084 let first_entry = RowEntry::new_value(b"key1", b"val1", 1).with_create_ts(0);
5085 let sst_format = SsTableFormat {
5086 min_filter_keys: options.min_filter_keys,
5087 ..SsTableFormat::default()
5088 };
5089 let first_memtable_bytes =
5090 sst_format.estimate_encoded_size_compacted(1, first_entry.estimated_size());
5091 options.max_unflushed_bytes = first_memtable_bytes.saturating_add(1);
5094 let metrics_recorder = Arc::new(DefaultMetricsRecorder::new());
5095 let db = Db::builder(path, object_store.clone())
5096 .with_settings(options)
5097 .with_fp_registry(fp_registry.clone())
5098 .with_metrics_recorder(metrics_recorder.clone())
5099 .build()
5100 .await
5101 .unwrap();
5102 let metrics_recorder_clone = metrics_recorder.clone();
5103 let write_opts = WriteOptions {
5104 await_durable: false,
5105 ..Default::default()
5106 };
5107
5108 fail_parallel::cfg(fp_registry.clone(), "write-wal-sst-io-error", "pause").unwrap();
5109
5110 let wait_for = async move |condition: Box<dyn Fn() -> bool>| {
5112 for _ in 0..3000 {
5113 if condition() {
5114 return;
5115 }
5116 tokio::time::sleep(Duration::from_millis(10)).await;
5117 }
5118 };
5119
5120 db.put_with_options(b"key1", b"val1", &PutOptions::default(), &write_opts)
5122 .await
5123 .unwrap();
5124
5125 let this_wal_buffer = db.inner.wal_observer.clone();
5127 wait_for(Box::new(move || {
5128 this_wal_buffer.status().unwrap().buffered_wal_entries_count > 0
5129 }))
5130 .await;
5131
5132 let wal_status = db.inner.wal_observer.status().unwrap();
5134 assert_eq!(wal_status.buffered_wal_entries_count, 1);
5135
5136 let (active_memtable_size_bytes, imm_memtable_size_bytes) = {
5137 let guard = db.inner.state.read();
5138 let estimate = |metadata: KVTableMetadata| {
5139 db.inner.table_store.estimate_encoded_size_compacted(
5140 metadata.entry_num,
5141 metadata.entries_size_in_bytes,
5142 )
5143 };
5144 let active_memtable_size_bytes = estimate(guard.memtable().table().metadata());
5145 let imm_memtable_size_bytes = guard
5146 .state()
5147 .imm_memtable
5148 .iter()
5149 .map(|imm| estimate(imm.table().metadata()))
5150 .fold(0usize, |total, size| total.saturating_add(size));
5151 (active_memtable_size_bytes, imm_memtable_size_bytes)
5152 };
5153 let memtable_size_bytes =
5154 active_memtable_size_bytes.saturating_add(imm_memtable_size_bytes);
5155 let total_mem_size_bytes = memtable_size_bytes.saturating_add(wal_status.estimated_bytes);
5156 assert!(
5157 memtable_size_bytes < db.inner.settings.max_unflushed_bytes,
5158 "test requires memtable bytes ({memtable_size_bytes}) to remain below \
5159 max_unflushed_bytes ({})",
5160 db.inner.settings.max_unflushed_bytes
5161 );
5162 assert!(
5163 total_mem_size_bytes >= db.inner.settings.max_unflushed_bytes,
5164 "test requires memtable plus WAL bytes ({total_mem_size_bytes}) to reach \
5165 max_unflushed_bytes ({})",
5166 db.inner.settings.max_unflushed_bytes
5167 );
5168
5169 let join_handle = tokio::spawn(async move {
5173 db.put_with_options(b"key2", b"val2", &PutOptions::default(), &write_opts)
5174 .await
5175 .unwrap();
5176 });
5177
5178 let this_recorder = metrics_recorder_clone.clone();
5179 wait_for(Box::new(move || {
5181 lookup_metric(&this_recorder, crate::db_stats::BACKPRESSURE_COUNT)
5182 .is_some_and(|v| v > 0)
5183 }))
5184 .await;
5185
5186 assert!(
5188 lookup_metric(&metrics_recorder_clone, crate::db_stats::BACKPRESSURE_COUNT).unwrap()
5189 >= 1
5190 );
5191
5192 fail_parallel::cfg(fp_registry.clone(), "write-wal-sst-io-error", "off").unwrap();
5194
5195 join_handle.abort();
5197 let _ = join_handle.await;
5198 }
5199
5200 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
5201 async fn test_backpressure_waiter_exits_when_db_is_fenced() {
5202 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
5205 let fp_registry = Arc::new(FailPointRegistry::new());
5206 fail_parallel::cfg(fp_registry.clone(), "write-compacted-sst-io-error", "pause").unwrap();
5207
5208 let mut options = test_db_options(0, 4 * 1024, None);
5209 options.flush_interval = None;
5210 options.max_unflushed_bytes = 8 * 1024;
5211
5212 let metrics_recorder = Arc::new(DefaultMetricsRecorder::new());
5215 let db = Db::builder(
5216 "/tmp/test_backpressure_waiter_exits_when_db_is_fenced",
5217 object_store,
5218 )
5219 .with_settings(options)
5220 .with_fp_registry(fp_registry.clone())
5221 .with_metrics_recorder(metrics_recorder.clone())
5222 .build()
5223 .await
5224 .unwrap();
5225 let write_opts = WriteOptions {
5226 await_durable: false,
5227 ..Default::default()
5228 };
5229
5230 let large_value = vec![b'x'; 16 * 1024];
5231 db.put_with_options(b"key1", &large_value, &PutOptions::default(), &write_opts)
5232 .await
5233 .unwrap();
5234 assert!(!db.inner.state.read().state().imm_memtable.is_empty());
5235
5236 let inner = db.inner.clone();
5239 let mut backpressure_task =
5240 tokio::spawn(async move { inner.maybe_apply_backpressure().await });
5241
5242 tokio::time::timeout(Duration::from_secs(60), async {
5245 loop {
5246 if lookup_metric(&metrics_recorder, crate::db_stats::BACKPRESSURE_COUNT)
5247 .is_some_and(|v| v > 0)
5248 {
5249 break;
5250 }
5251 tokio::time::sleep(Duration::from_millis(10)).await;
5252 }
5253 })
5254 .await
5255 .expect("timed out waiting for backpressure to be applied");
5256
5257 db.inner
5260 .status_manager
5261 .write_result(Err(SlateDBError::Fenced));
5262
5263 let result = tokio::time::timeout(Duration::from_secs(5), &mut backpressure_task).await;
5266 if result.is_err() {
5267 backpressure_task.abort();
5268 let _ = backpressure_task.await;
5269 }
5270
5271 fail_parallel::cfg(fp_registry.clone(), "write-compacted-sst-io-error", "off").unwrap();
5274 let _ = db.close().await;
5275
5276 let backpressure_result = result
5279 .expect("backpressure waiter did not exit after DB was fenced")
5280 .expect("backpressure task panicked");
5281 assert!(
5282 matches!(backpressure_result, Err(SlateDBError::Fenced)),
5283 "expected fenced error, got {:?}",
5284 backpressure_result
5285 );
5286 }
5287
5288 #[tokio::test]
5289 async fn test_apply_backpressure_to_memtable_flush() {
5290 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
5291 let mut options = test_db_options(0, 1, None);
5292 options.l0_max_ssts = 4;
5293 let db = Db::builder("/tmp/test_kv_store", object_store.clone())
5294 .with_settings(options)
5295 .build()
5296 .await
5297 .unwrap();
5298 db.put(b"key1", b"val1").await.unwrap();
5299 db.put(b"key2", b"val2").await.unwrap();
5300 db.put(b"key3", b"val3").await.unwrap();
5301 db.put(b"key4", b"val4").await.unwrap();
5302 db.put(b"key5", b"val5").await.unwrap();
5303
5304 db.flush().await.unwrap();
5305
5306 let db_state = db.inner.state.read().view();
5307 assert_eq!(db_state.state.imm_memtable.len(), 1);
5308 }
5309
5310 #[tokio::test]
5311 async fn test_put_empty_value() {
5312 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
5313 let kv_store = Db::builder("/tmp/test_kv_store", object_store.clone())
5314 .with_settings(test_db_options(0, 1024, None))
5315 .build()
5316 .await
5317 .unwrap();
5318 let key = b"test_key";
5319 let value = b"";
5320 kv_store.put(key, value).await.unwrap();
5321 kv_store.flush().await.unwrap();
5322
5323 assert_eq!(
5324 kv_store.get(key).await.unwrap(),
5325 Some(Bytes::from_static(value))
5326 );
5327 }
5328
5329 #[tokio::test]
5330 async fn test_flush_while_iterating() {
5331 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
5332 let kv_store = Db::builder("/tmp/test_kv_store", object_store.clone())
5333 .with_settings(test_db_options(0, 1024, None))
5334 .with_system_clock(Arc::new(MockSystemClock::new()))
5335 .build()
5336 .await
5337 .unwrap();
5338
5339 let memtable = {
5340 let lock = kv_store.inner.state.read();
5341 lock.memtable()
5342 .put(RowEntry::new_value(b"abc1111", b"value1111", 1));
5343 lock.memtable()
5344 .put(RowEntry::new_value(b"abc2222", b"value2222", 2));
5345 lock.memtable()
5346 .put(RowEntry::new_value(b"abc3333", b"value3333", 3));
5347 lock.memtable().table().clone()
5348 };
5349
5350 let mut iter = memtable.iter();
5351 let kv: KeyValue = iter.next().await.unwrap().unwrap().into();
5352 assert_eq!(kv.key, b"abc1111".as_slice());
5353
5354 kv_store.flush().await.unwrap();
5355
5356 let kv: KeyValue = iter.next().await.unwrap().unwrap().into();
5357 assert_eq!(kv.key, b"abc2222".as_slice());
5358
5359 let kv: KeyValue = iter.next().await.unwrap().unwrap().into();
5360 assert_eq!(kv.key, b"abc3333".as_slice());
5361 }
5362
5363 #[tokio::test]
5364 async fn test_basic_restore() {
5365 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
5366 let path = "/tmp/test_kv_store";
5367 let mut next_wal_id = 1;
5368 let kv_store = Db::builder(path, object_store.clone())
5369 .with_settings(test_db_options(0, 600, None))
5371 .with_system_clock(Arc::new(MockSystemClock::new()))
5372 .build()
5373 .await
5374 .unwrap();
5375 next_wal_id += 1;
5377
5378 let write_opts = WriteOptions {
5380 await_durable: false,
5381 ..Default::default()
5382 };
5383
5384 let l0_count: u64 = 3;
5386 for i in 0..l0_count {
5387 kv_store
5388 .put_with_options(
5389 &[b'a' + i as u8; 16],
5390 &[b'b' + i as u8; 48],
5391 &PutOptions::default(),
5392 &write_opts,
5393 )
5394 .await
5395 .unwrap();
5396 kv_store.flush().await.unwrap();
5397 kv_store
5398 .put_with_options(
5399 &[b'j' + i as u8; 16],
5400 &[b'k' + i as u8; 48],
5401 &PutOptions::default(),
5402 &write_opts,
5403 )
5404 .await
5405 .unwrap();
5406 kv_store.flush().await.unwrap();
5407 next_wal_id += 2;
5408 }
5409
5410 let sst_count: u64 = 5;
5412 for i in 0..sst_count {
5413 kv_store
5414 .put_with_options(
5415 &i.to_be_bytes(),
5416 &i.to_be_bytes(),
5417 &PutOptions::default(),
5418 &write_opts,
5419 )
5420 .await
5421 .unwrap();
5422 kv_store.flush().await.unwrap();
5423 next_wal_id += 1;
5424 }
5425
5426 kv_store.close().await.unwrap();
5427
5428 let kv_store_restored = Db::builder(path, object_store.clone())
5430 .with_settings(test_db_options(0, 128, None))
5431 .with_system_clock(Arc::new(MockSystemClock::new()))
5432 .build()
5433 .await
5434 .unwrap();
5435 next_wal_id += 1;
5437
5438 for i in 0..l0_count {
5439 let val = kv_store_restored.get([b'a' + i as u8; 16]).await.unwrap();
5440 assert_eq!(val, Some(Bytes::copy_from_slice(&[b'b' + i as u8; 48])));
5441 let val = kv_store_restored.get([b'j' + i as u8; 16]).await.unwrap();
5442 assert_eq!(val, Some(Bytes::copy_from_slice(&[b'k' + i as u8; 48])));
5443 }
5444 for i in 0..sst_count {
5445 let val = kv_store_restored.get(i.to_be_bytes()).await.unwrap();
5446 assert_eq!(val, Some(Bytes::copy_from_slice(&i.to_be_bytes())));
5447 }
5448 kv_store_restored.close().await.unwrap();
5449
5450 let manifest_store = Arc::new(ManifestStore::new(&Path::from(path), object_store.clone()));
5452 let stored_manifest =
5453 StoredManifest::load(manifest_store, Arc::new(DefaultSystemClock::new()))
5454 .await
5455 .unwrap();
5456 let db_state = stored_manifest.db_state();
5457 assert_eq!(db_state.next_wal_sst_id, next_wal_id);
5458 }
5459
5460 #[tokio::test]
5461 #[allow(clippy::await_holding_lock)]
5462 async fn test_restore_seq_number() {
5463 let fp_registry = Arc::new(FailPointRegistry::new());
5464 fail_parallel::cfg(
5468 fp_registry.clone(),
5469 "write-compacted-sst-io-error",
5470 "return",
5471 )
5472 .unwrap();
5473 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
5474 let path = "/tmp/test_kv_store";
5475 let db = Db::builder(path, object_store.clone())
5476 .with_settings(test_db_options(0, 512, None))
5477 .with_system_clock(Arc::new(MockSystemClock::new()))
5478 .with_fp_registry(fp_registry.clone())
5479 .build()
5480 .await
5481 .unwrap();
5482
5483 db.put_with_options(
5484 b"key1",
5485 b"val1",
5486 &PutOptions::default(),
5487 &WriteOptions {
5488 await_durable: false,
5489 ..Default::default()
5490 },
5491 )
5492 .await
5493 .unwrap();
5494 db.put_with_options(
5495 b"key2",
5496 b"val2",
5497 &PutOptions::default(),
5498 &WriteOptions {
5499 await_durable: false,
5500 ..Default::default()
5501 },
5502 )
5503 .await
5504 .unwrap();
5505 db.put_with_options(
5506 b"key3",
5507 b"val3",
5508 &PutOptions::default(),
5509 &WriteOptions {
5510 await_durable: false,
5511 ..Default::default()
5512 },
5513 )
5514 .await
5515 .unwrap();
5516 db.flush().await.unwrap();
5517 assert!(db.close().await.is_err());
5519
5520 fail_parallel::cfg(fp_registry.clone(), "write-compacted-sst-io-error", "off").unwrap();
5522
5523 let db_restored = Db::builder(path, object_store.clone())
5524 .with_settings(test_db_options(0, 512, None))
5525 .with_system_clock(Arc::new(MockSystemClock::new()))
5526 .with_fp_registry(fp_registry.clone())
5527 .build()
5528 .await
5529 .unwrap();
5530
5531 let state = db_restored.inner.state.read();
5532 let memtable = state.memtable();
5533 let mut iter = memtable.table().iter();
5534 assert_iterator(
5535 &mut iter,
5536 vec![
5537 RowEntry::new_value(b"key1", b"val1", 1).with_create_ts(0),
5538 RowEntry::new_value(b"key2", b"val2", 2).with_create_ts(0),
5539 RowEntry::new_value(b"key3", b"val3", 3).with_create_ts(0),
5540 ],
5541 )
5542 .await;
5543 }
5544
5545 #[tokio::test]
5546 async fn test_read_merges_from_snapshot_across_compaction() {
5547 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
5548 let path = "/tmp/testdb";
5549 let should_compact_l0 = Arc::new(AtomicBool::new(false));
5550 let this_should_compact_l0 = should_compact_l0.clone();
5551 let compaction_scheduler = Arc::new(OnDemandCompactionSchedulerSupplier::new(Arc::new(
5552 move |_state| this_should_compact_l0.swap(false, Ordering::SeqCst),
5553 )));
5554 let db = Db::builder(path, object_store.clone())
5555 .with_settings(test_db_options(0, 1024 * 1024, None))
5556 .with_merge_operator(Arc::new(StringConcatMergeOperator {}))
5557 .with_compactor_builder(
5558 CompactorBuilder::new(path, object_store.clone())
5559 .with_scheduler_supplier(compaction_scheduler.clone())
5560 .with_options(fast_compactor_options()),
5561 )
5562 .build()
5563 .await
5564 .unwrap();
5565 let db = Arc::new(db);
5566
5567 db.merge(b"foo", b"0").await.unwrap();
5568 let snapshot = db.snapshot().await.unwrap();
5569 db.flush_with_options(FlushOptions {
5570 flush_type: FlushType::MemTable,
5571 })
5572 .await
5573 .unwrap();
5574 db.merge(b"foo", b"1").await.unwrap();
5575 db.flush_with_options(FlushOptions {
5576 flush_type: FlushType::MemTable,
5577 })
5578 .await
5579 .unwrap();
5580
5581 should_compact_l0.store(true, Ordering::SeqCst);
5583 let db_poll = db.clone();
5584 tokio::time::timeout(Duration::from_secs(10), async move {
5585 loop {
5586 {
5587 let db_state = db_poll.inner.state.read();
5588 if !db_state.state().core().tree.compacted.is_empty() {
5589 return;
5590 }
5591 }
5592 tokio::time::sleep(Duration::from_millis(10)).await;
5593 }
5594 })
5595 .await
5596 .unwrap();
5597
5598 let result = snapshot.get(b"foo").await.unwrap();
5599 assert_eq!(result, Some(Bytes::copy_from_slice(b"0")));
5600 let result = db.get(b"foo").await.unwrap();
5601 assert_eq!(result, Some(Bytes::copy_from_slice(b"01")));
5602 }
5603
5604 #[tokio::test]
5605 async fn test_all_kv_seq_num_are_greater_than_0() {
5606 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
5607 let path = "/tmp/test_kv_store_seq_num";
5608 let db = Db::builder(path, object_store.clone())
5609 .with_settings(test_db_options(0, 1024 * 1024, None))
5610 .build()
5611 .await
5612 .unwrap();
5613
5614 db.put(b"key1", b"value1").await.unwrap();
5616
5617 let val = db.get(b"key1").await.unwrap();
5618 assert_eq!(val, Some(Bytes::from_static(b"value1")));
5619
5620 let state = db.inner.state.read();
5621 let memtable = state.memtable();
5622 assert_eq!(memtable.table().last_seq(), Some(1));
5623 }
5624
5625 #[tokio::test(flavor = "multi_thread", worker_threads = 4)]
5626 async fn test_should_read_uncommitted_data_if_read_level_uncommitted() {
5627 let fp_registry = Arc::new(FailPointRegistry::new());
5628 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
5629 let path = "/tmp/test_kv_store";
5630 let kv_store = Db::builder(path, object_store.clone())
5631 .with_settings(test_db_options(0, 1024, None))
5632 .with_fp_registry(fp_registry.clone())
5633 .build()
5634 .await
5635 .unwrap();
5636
5637 fail_parallel::cfg(fp_registry.clone(), "write-wal-sst-io-error", "pause").unwrap();
5638 kv_store
5639 .put_with_options(
5640 "foo".as_bytes(),
5641 "bar".as_bytes(),
5642 &PutOptions::default(),
5643 &WriteOptions {
5644 await_durable: false,
5645 ..Default::default()
5646 },
5647 )
5648 .await
5649 .unwrap();
5650
5651 let val = kv_store
5653 .get_with_options(
5654 "foo".as_bytes(),
5655 &ReadOptions::new().with_durability_filter(Memory),
5656 )
5657 .await
5658 .unwrap();
5659 assert_eq!(val, Some("bar".into()));
5660
5661 let val = kv_store
5663 .get_with_options(
5664 "foo".as_bytes(),
5665 &ReadOptions::new().with_durability_filter(Remote),
5666 )
5667 .await
5668 .unwrap();
5669 assert_eq!(val, None);
5670
5671 fail_parallel::cfg(fp_registry.clone(), "write-wal-sst-io-error", "off").unwrap();
5672 kv_store.close().await.unwrap();
5673 }
5674
5675 #[tokio::test(flavor = "multi_thread", worker_threads = 4)]
5676 async fn test_should_read_only_committed_data() {
5677 let fp_registry = Arc::new(FailPointRegistry::new());
5678 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
5679 let path = "/tmp/test_kv_store";
5680 let kv_store = Db::builder(path, object_store.clone())
5681 .with_settings(test_db_options(0, 1024, None))
5682 .with_fp_registry(fp_registry.clone())
5683 .build()
5684 .await
5685 .unwrap();
5686
5687 kv_store
5688 .put("foo".as_bytes(), "bar".as_bytes())
5689 .await
5690 .unwrap();
5691 fail_parallel::cfg(fp_registry.clone(), "write-wal-sst-io-error", "pause").unwrap();
5692 kv_store
5693 .put_with_options(
5694 "foo".as_bytes(),
5695 "bla".as_bytes(),
5696 &PutOptions::default(),
5697 &WriteOptions {
5698 await_durable: false,
5699 ..Default::default()
5700 },
5701 )
5702 .await
5703 .unwrap();
5704
5705 let val = kv_store
5706 .get_with_options(
5707 "foo".as_bytes(),
5708 &ReadOptions::new().with_durability_filter(Remote),
5709 )
5710 .await
5711 .unwrap();
5712 assert_eq!(val, Some("bar".into()));
5713 let val = kv_store
5714 .get_with_options(
5715 "foo".as_bytes(),
5716 &ReadOptions::new().with_durability_filter(Memory),
5717 )
5718 .await
5719 .unwrap();
5720 assert_eq!(val, Some("bla".into()));
5721
5722 fail_parallel::cfg(fp_registry.clone(), "write-wal-sst-io-error", "off").unwrap();
5723 kv_store.close().await.unwrap();
5724 }
5725
5726 #[tokio::test]
5727 async fn test_should_delete_without_awaiting_flush() {
5728 let fp_registry = Arc::new(FailPointRegistry::new());
5729 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
5730 let path = "/tmp/test_kv_store";
5731 let kv_store = Db::builder(path, object_store.clone())
5732 .with_settings(test_db_options(0, 1024, None))
5733 .with_fp_registry(fp_registry.clone())
5734 .build()
5735 .await
5736 .unwrap();
5737
5738 kv_store
5739 .put("foo".as_bytes(), "bar".as_bytes())
5740 .await
5741 .unwrap();
5742 fail_parallel::cfg(fp_registry.clone(), "write-wal-sst-io-error", "pause").unwrap();
5743 kv_store
5744 .delete_with_options(
5745 "foo".as_bytes(),
5746 &WriteOptions {
5747 await_durable: false,
5748 ..Default::default()
5749 },
5750 )
5751 .await
5752 .unwrap();
5753
5754 let val = kv_store
5755 .get_with_options(
5756 "foo".as_bytes(),
5757 &ReadOptions::new().with_durability_filter(Remote),
5758 )
5759 .await
5760 .unwrap();
5761 assert_eq!(val, Some("bar".into()));
5762 let val = kv_store
5763 .get_with_options(
5764 "foo".as_bytes(),
5765 &ReadOptions::new().with_durability_filter(Memory),
5766 )
5767 .await
5768 .unwrap();
5769 assert_eq!(val, None);
5770
5771 fail_parallel::cfg(fp_registry.clone(), "write-wal-sst-io-error", "off").unwrap();
5772 kv_store.close().await.unwrap();
5773 }
5774
5775 #[tokio::test]
5776 async fn test_scan_should_read_only_committed_data() {
5777 let fp_registry = Arc::new(FailPointRegistry::new());
5778 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
5779 let path = "/tmp/test_kv_store";
5780 let kv_store = Db::builder(path, object_store.clone())
5781 .with_settings(test_db_options(0, 1024, None))
5782 .with_fp_registry(fp_registry.clone())
5783 .build()
5784 .await
5785 .unwrap();
5786
5787 kv_store
5789 .put("key1".as_bytes(), "committed1".as_bytes())
5790 .await
5791 .unwrap();
5792 kv_store
5793 .put("key2".as_bytes(), "committed2".as_bytes())
5794 .await
5795 .unwrap();
5796 kv_store
5797 .put("key3".as_bytes(), "committed3".as_bytes())
5798 .await
5799 .unwrap();
5800
5801 fail_parallel::cfg(fp_registry.clone(), "write-wal-sst-io-error", "pause").unwrap();
5803
5804 kv_store
5806 .put_with_options(
5807 "key2".as_bytes(),
5808 "uncommitted2".as_bytes(),
5809 &PutOptions::default(),
5810 &WriteOptions {
5811 await_durable: false,
5812 ..Default::default()
5813 },
5814 )
5815 .await
5816 .unwrap();
5817 kv_store
5818 .put_with_options(
5819 "key4".as_bytes(),
5820 "uncommitted4".as_bytes(),
5821 &PutOptions::default(),
5822 &WriteOptions {
5823 await_durable: false,
5824 ..Default::default()
5825 },
5826 )
5827 .await
5828 .unwrap();
5829
5830 let mut iter = kv_store
5832 .scan_with_options(
5833 "key1".as_bytes().."key5".as_bytes(),
5834 &ScanOptions::new().with_durability_filter(Remote),
5835 )
5836 .await
5837 .unwrap();
5838
5839 let kv = iter.next().await.unwrap().unwrap();
5840 assert_eq!(kv.key.as_ref(), b"key1");
5841 assert_eq!(kv.value.as_ref(), b"committed1");
5842
5843 let kv = iter.next().await.unwrap().unwrap();
5844 assert_eq!(kv.key.as_ref(), b"key2");
5845 assert_eq!(kv.value.as_ref(), b"committed2"); let kv = iter.next().await.unwrap().unwrap();
5848 assert_eq!(kv.key.as_ref(), b"key3");
5849 assert_eq!(kv.value.as_ref(), b"committed3");
5850
5851 assert_eq!(iter.next().await.unwrap(), None);
5853
5854 let mut iter = kv_store
5856 .scan_with_options(
5857 "key1".as_bytes().."key5".as_bytes(),
5858 &ScanOptions::new().with_durability_filter(Memory),
5859 )
5860 .await
5861 .unwrap();
5862
5863 let kv = iter.next().await.unwrap().unwrap();
5864 assert_eq!(kv.key.as_ref(), b"key1");
5865 assert_eq!(kv.value.as_ref(), b"committed1");
5866
5867 let kv = iter.next().await.unwrap().unwrap();
5868 assert_eq!(kv.key.as_ref(), b"key2");
5869 assert_eq!(kv.value.as_ref(), b"uncommitted2"); let kv = iter.next().await.unwrap().unwrap();
5872 assert_eq!(kv.key.as_ref(), b"key3");
5873 assert_eq!(kv.value.as_ref(), b"committed3");
5874
5875 let kv = iter.next().await.unwrap().unwrap();
5876 assert_eq!(kv.key.as_ref(), b"key4");
5877 assert_eq!(kv.value.as_ref(), b"uncommitted4"); assert_eq!(iter.next().await.unwrap(), None);
5880
5881 fail_parallel::cfg(fp_registry.clone(), "write-wal-sst-io-error", "off").unwrap();
5882 kv_store.close().await.unwrap();
5883 }
5884
5885 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
5886 async fn test_should_recover_imm_from_wal() {
5887 let fp_registry = Arc::new(FailPointRegistry::new());
5888 fail_parallel::cfg(fp_registry.clone(), "write-compacted-sst-io-error", "pause").unwrap();
5889
5890 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
5891 let path = "/tmp/test_kv_store";
5892 let mut next_wal_id = 1;
5893 let db = Db::builder(path, object_store.clone())
5894 .with_settings(test_db_options(0, 128, None))
5895 .with_fp_registry(fp_registry.clone())
5896 .build()
5897 .await
5898 .unwrap();
5899 next_wal_id += 1;
5900
5901 let mut rx = db.inner.status_manager.subscribe();
5903
5904 let key1 = [b'a'; 32];
5906 let value1 = [b'b'; 96];
5907 db.put(key1, value1).await.unwrap();
5908 next_wal_id += 1;
5909 let key2 = [b'c'; 32];
5910 let value2 = [b'd'; 96];
5911 db.put(key2, value2).await.unwrap();
5912 next_wal_id += 1;
5913
5914 let reader = Db::builder(path, object_store.clone())
5915 .with_settings(test_db_options(0, 128, None))
5916 .with_fp_registry(fp_registry.clone())
5917 .build()
5918 .await
5919 .unwrap();
5920
5921 next_wal_id += 1;
5923
5924 let db_state = reader.inner.state.read().view();
5926 assert_eq!(db_state.state.imm_memtable.len(), 2);
5927
5928 assert_eq!(
5930 db_state
5931 .state
5932 .imm_memtable
5933 .front()
5934 .unwrap()
5935 .recent_flushed_wal_id(),
5936 1 + 2
5937 );
5938 assert_eq!(
5939 db_state
5940 .state
5941 .imm_memtable
5942 .get(1)
5943 .unwrap()
5944 .recent_flushed_wal_id(),
5945 2
5946 );
5947 assert_eq!(db_state.state.core().next_wal_sst_id, next_wal_id);
5948 assert_eq!(
5949 reader.get(key1).await.unwrap(),
5950 Some(Bytes::copy_from_slice(&value1))
5951 );
5952 assert_eq!(
5953 reader.get(key2).await.unwrap(),
5954 Some(Bytes::copy_from_slice(&value2))
5955 );
5956
5957 fail_parallel::cfg(fp_registry.clone(), "write-compacted-sst-io-error", "off").unwrap();
5958
5959 rx.wait_for(|status| status.close_reason.is_some())
5961 .await
5962 .unwrap();
5963 assert_eq!(
5964 db.inner.status_manager.status().close_reason,
5965 Some(crate::error::CloseReason::Fenced)
5966 );
5967
5968 db.close().await.unwrap();
5969 reader.close().await.unwrap();
5970 }
5971
5972 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
5973 async fn test_should_recover_imm_from_wal_after_flush_error() {
5974 let fp_registry = Arc::new(FailPointRegistry::new());
5975 fail_parallel::cfg(
5976 fp_registry.clone(),
5977 "write-compacted-sst-io-error",
5978 "return",
5979 )
5980 .unwrap();
5981 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
5982 let path = "/tmp/test_kv_store";
5983 let db = Db::builder(path, object_store.clone())
5984 .with_settings(test_db_options(0, 4096, None))
5985 .with_fp_registry(fp_registry.clone())
5986 .build()
5987 .await
5988 .unwrap();
5989
5990 let key1 = [b'a'; 32];
5992 let value1 = [b'b'; 96];
5993 let result = db.put(&key1, &value1).await;
5994 assert!(result.is_ok(), "Failed to write key1");
5995 assert_eq!(
5996 db.inner.wal_observer.status().unwrap().last_flushed_wal_id,
5997 2
5998 );
5999
6000 assert!(db.close().await.is_err());
6003
6004 fail_parallel::cfg(fp_registry.clone(), "write-compacted-sst-io-error", "pause").unwrap();
6008
6009 let db = Db::builder(path, object_store.clone())
6011 .with_settings(test_db_options(0, 128, None))
6012 .with_fp_registry(fp_registry.clone())
6013 .build()
6014 .await
6015 .unwrap();
6016
6017 let db_state = db.inner.state.read().view();
6018
6019 fail_parallel::cfg(fp_registry.clone(), "write-compacted-sst-io-error", "off").unwrap();
6022
6023 assert_eq!(db_state.state.imm_memtable.len(), 1);
6025
6026 assert_eq!(db_state.state.core().tree.l0.len(), 0);
6028 assert_eq!(db_state.state.core().tree.compacted.len(), 0);
6029
6030 assert_eq!(
6032 db_state
6033 .state
6034 .imm_memtable
6035 .front()
6036 .unwrap()
6037 .recent_flushed_wal_id(),
6038 1 + 1
6039 );
6040 assert!(db_state.state.imm_memtable.get(1).is_none());
6041
6042 assert_eq!(db_state.state.core().next_wal_sst_id, 4);
6043 assert_eq!(
6044 db.get(key1).await.unwrap(),
6045 Some(Bytes::copy_from_slice(&value1))
6046 );
6047 }
6048
6049 #[tokio::test]
6050 async fn test_should_fail_write_if_wal_flush_task_panics() {
6051 let fp_registry = Arc::new(FailPointRegistry::new());
6052 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
6053 let path = "/tmp/test_kv_store";
6054 let db = Arc::new(
6055 Db::builder(path, object_store.clone())
6056 .with_settings(test_db_options(0, 128, None))
6057 .with_fp_registry(fp_registry.clone())
6058 .build()
6059 .await
6060 .unwrap(),
6061 );
6062
6063 fail_parallel::cfg(fp_registry.clone(), "write-wal-sst-io-error", "panic").unwrap();
6064 let result = db.put(b"foo", b"bar").await.unwrap_err();
6065 assert!(result.to_string().contains("background task panicked"));
6066 }
6067
6068 #[tokio::test]
6069 async fn test_wal_id_last_seen_should_only_reflect_flushed_wals() {
6070 let fp_registry = Arc::new(FailPointRegistry::new());
6071 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
6072 let path = "/tmp/test_kv_store";
6073 let db = Arc::new(
6074 Db::builder(path, object_store.clone())
6075 .with_settings(test_db_options(0, 128, None))
6076 .with_fp_registry(fp_registry.clone())
6077 .build()
6078 .await
6079 .unwrap(),
6080 );
6081 db.put(b"foo", b"bar").await.unwrap();
6083
6084 fail_parallel::cfg(fp_registry.clone(), "write-wal-sst-io-error", "panic").unwrap();
6085
6086 let result = db.put(b"foo", b"bar").await.unwrap_err();
6088 assert_eq!(result.kind(), crate::ErrorKind::Closed(CloseReason::Panic));
6089 assert!(result
6090 .to_string()
6091 .contains("background task panicked. name=`wal_writer`"));
6092
6093 db.close().await.unwrap();
6097
6098 let manifest_store = ManifestStore::new(&Path::from(path), object_store.clone());
6099 let table_store = Arc::new(TableStore::new(
6100 ObjectStores::new(object_store.clone(), None),
6101 SsTableFormat::default(),
6102 path,
6103 None,
6104 TableStoreKind::Main,
6105 BlockCachePolicy::default(),
6106 ));
6107
6108 let next_wal_sst_id = table_store.next_wal_sst_id(0).await.unwrap();
6110
6111 let manifest = manifest_store.read_latest_manifest().await.unwrap();
6113
6114 assert_eq!(manifest.manifest.core.next_wal_sst_id, next_wal_sst_id);
6116 }
6117
6118 #[tokio::test]
6119 async fn test_close_should_return_error_if_wal_flush_fails() {
6120 let fp_registry = Arc::new(FailPointRegistry::new());
6121 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
6122 let path = "/tmp/test_kv_store";
6123
6124 let mut settings = test_db_options(0, 256, None);
6125 settings.flush_interval = None;
6127
6128 let db = Db::builder(path, object_store.clone())
6129 .with_settings(settings)
6130 .with_fp_registry(fp_registry.clone())
6131 .build()
6132 .await
6133 .unwrap();
6134
6135 fail_parallel::cfg(fp_registry.clone(), "write-wal-sst-io-error", "return").unwrap();
6137
6138 db.put_with_options(
6140 b"foo",
6141 b"bar",
6142 &PutOptions::default(),
6143 &WriteOptions {
6144 await_durable: false,
6145 ..Default::default()
6146 },
6147 )
6148 .await
6149 .unwrap();
6150
6151 db.close()
6153 .await
6154 .expect_err("close should error out due to WAL IO error");
6155 }
6156
6157 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
6158 async fn test_await_durable_write_returns_error_if_db_closes_before_durable() {
6159 let fp_registry = Arc::new(FailPointRegistry::new());
6160 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
6161 let mut settings = test_db_options(0, 1024, None);
6162 settings.flush_interval = None;
6163 let db = Arc::new(
6164 Db::builder(
6165 "/tmp/test_await_durable_write_returns_error_if_db_closes_before_durable",
6166 object_store,
6167 )
6168 .with_settings(settings)
6169 .with_fp_registry(fp_registry.clone())
6170 .build()
6171 .await
6172 .unwrap(),
6173 );
6174 fail_parallel::cfg(fp_registry.clone(), "write-wal-sst-io-error", "pause").unwrap();
6177 let write_db = db.clone();
6178 let write_task = tokio::spawn(async move {
6179 write_db
6180 .put_with_options(
6181 b"foo",
6182 b"bar",
6183 &PutOptions::default(),
6184 &WriteOptions::default(),
6185 )
6186 .await
6187 });
6188 tokio::time::timeout(Duration::from_secs(10), async {
6189 loop {
6190 if db
6191 .inner
6192 .wal_observer
6193 .status()
6194 .unwrap()
6195 .buffered_wal_entries_count
6196 == 1
6197 {
6198 break;
6199 }
6200 tokio::task::yield_now().await;
6201 }
6202 })
6203 .await
6204 .expect("write was not buffered");
6205 let close_db = db.clone();
6206 let close_task = tokio::spawn(async move { close_db.close().await });
6207
6208 let write_error = tokio::time::timeout(Duration::from_secs(10), write_task)
6209 .await
6210 .expect("timed out waiting for write")
6211 .expect("write task panicked")
6212 .expect_err("write unexpectedly reported success");
6213
6214 assert_eq!(
6215 write_error.kind(),
6216 crate::ErrorKind::Closed(CloseReason::Clean)
6217 );
6218 fail_parallel::cfg(fp_registry, "write-wal-sst-io-error", "off").unwrap();
6219 let _ = close_task.await.unwrap();
6220 }
6221
6222 async fn do_test_should_read_compacted_db(mut options: Settings) {
6223 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
6224 let path = "/tmp/test_kv_store";
6225 let should_compact_l0 = Arc::new(AtomicBool::new(false));
6226 let this_should_compact_l0 = should_compact_l0.clone();
6227 let compaction_scheduler = Arc::new(OnDemandCompactionSchedulerSupplier::new(Arc::new(
6228 move |_state| this_should_compact_l0.swap(false, Ordering::SeqCst),
6229 )));
6230
6231 let compactor_options = options.compactor_options.take();
6232 let db = Db::builder(path, object_store.clone())
6233 .with_settings(options)
6234 .with_compactor_builder(
6235 CompactorBuilder::new(path, object_store.clone())
6236 .with_scheduler_supplier(compaction_scheduler.clone())
6237 .with_options(compactor_options.unwrap()),
6238 )
6239 .build()
6240 .await
6241 .unwrap();
6242 let ms = ManifestStore::new(&Path::from(path), object_store.clone());
6243 let mut sm = StoredManifest::load(Arc::new(ms), Arc::new(DefaultSystemClock::new()))
6244 .await
6245 .unwrap();
6246
6247 for i in 0..4 {
6249 db.put(&[b'a' + i; 32], &[1u8 + i; 32]).await.unwrap();
6250 db.put(&[b'm' + i; 32], &[13u8 + i; 32]).await.unwrap();
6251 }
6252 wait_for_manifest_condition(
6254 &mut sm,
6255 |s| {
6256 should_compact_l0.store(true, Ordering::SeqCst);
6261 s.tree.last_compacted_l0_sst_view_id.is_some() && s.tree.l0.is_empty()
6262 },
6263 Duration::from_secs(10),
6264 )
6265 .await;
6266 let manifest = db.manifest();
6267 info!(
6268 "1 l0: {} {}",
6269 manifest.manifest.core.tree.l0.len(),
6270 manifest.manifest.core.tree.compacted.len()
6271 );
6272
6273 for i in 0..4 {
6275 db.put(&[b'f' + i; 32], &[6u8 + i; 32]).await.unwrap();
6276 db.put(&[b's' + i; 32], &[19u8 + i; 32]).await.unwrap();
6277 }
6278 wait_for_manifest_condition(
6280 &mut sm,
6281 |s| {
6282 should_compact_l0.store(true, Ordering::SeqCst);
6287 s.tree.last_compacted_l0_sst_view_id.is_some() && s.tree.l0.is_empty()
6288 },
6289 Duration::from_secs(10),
6290 )
6291 .await;
6292 let manifest = db.manifest();
6293 info!(
6294 "2 l0: {} {}",
6295 manifest.manifest.core.tree.l0.len(),
6296 manifest.manifest.core.tree.compacted.len()
6297 );
6298 db.put(&[b'a'; 32], &[128u8; 32]).await.unwrap();
6300 db.put(&[b'm'; 32], &[129u8; 32]).await.unwrap();
6301
6302 let val = db.get([b'a'; 32]).await.unwrap();
6303 assert_eq!(val, Some(Bytes::copy_from_slice(&[128u8; 32])));
6304 let val = db.get([b'm'; 32]).await.unwrap();
6305 assert_eq!(val, Some(Bytes::copy_from_slice(&[129u8; 32])));
6306 for i in 1..4 {
6307 let manifest = db.manifest();
6308 info!(
6309 "3 l0: {} {}",
6310 manifest.manifest.core.tree.l0.len(),
6311 manifest.manifest.core.tree.compacted.len()
6312 );
6313 let val = db.get([b'a' + i; 32]).await.unwrap();
6314 assert_eq!(val, Some(Bytes::copy_from_slice(&[1u8 + i; 32])));
6315 let val = db.get([b'm' + i; 32]).await.unwrap();
6316 assert_eq!(val, Some(Bytes::copy_from_slice(&[13u8 + i; 32])));
6317 }
6318 for i in 0..4 {
6319 let val = db.get([b'f' + i; 32]).await.unwrap();
6320 assert_eq!(val, Some(Bytes::copy_from_slice(&[6u8 + i; 32])));
6321 let val = db.get([b's' + i; 32]).await.unwrap();
6322 assert_eq!(val, Some(Bytes::copy_from_slice(&[19u8 + i; 32])));
6323 }
6324 let neg_lookup = db.get(b"abc").await;
6325 assert!(neg_lookup.unwrap().is_none());
6326 }
6327
6328 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
6329 async fn test_should_read_from_compacted_db() {
6330 do_test_should_read_compacted_db(test_db_options(
6331 0,
6332 127,
6333 Some(CompactorOptions {
6334 poll_interval: Duration::from_millis(100),
6335 max_concurrent_compactions: 1,
6336 manifest_update_timeout: Duration::from_secs(300),
6337 worker: Some(CompactionWorkerOptions {
6338 max_sst_size: 256,
6339 compactions_poll_interval: Duration::from_millis(100),
6340 ..Default::default()
6341 }),
6342 ..Default::default()
6343 }),
6344 ))
6345 .await;
6346 }
6347
6348 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
6349 async fn test_should_read_from_compacted_db_no_filters() {
6350 do_test_should_read_compacted_db(test_db_options(
6351 u32::MAX,
6352 127,
6353 Some(CompactorOptions {
6354 poll_interval: Duration::from_millis(100),
6355 manifest_update_timeout: Duration::from_secs(300),
6356 max_concurrent_compactions: 1,
6357 worker: Some(CompactionWorkerOptions {
6358 max_sst_size: 256,
6359 compactions_poll_interval: Duration::from_millis(100),
6360 ..Default::default()
6361 }),
6362 ..Default::default()
6363 }),
6364 ))
6365 .await
6366 }
6367
6368 #[tokio::test]
6369 async fn test_db_open_should_write_empty_wal() {
6370 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
6371 let path = "/tmp/test_kv_store";
6372 let db = Db::builder(path, object_store.clone())
6374 .with_settings(test_db_options(0, 128, None))
6375 .build()
6376 .await
6377 .unwrap();
6378 assert_eq!(db.inner.state.read().state().core().next_wal_sst_id, 2);
6379 let wal_ssts = db.inner.table_store.list_wal_ssts(..).await.unwrap();
6380 assert_eq!(wal_ssts.len(), 1);
6381 assert_eq!(wal_ssts[0].metadata.size, 0);
6382 db.put(b"1", b"1").await.unwrap();
6383 let db = Db::builder(path, object_store.clone())
6385 .with_settings(test_db_options(0, 128, None))
6386 .build()
6387 .await
6388 .unwrap();
6389 assert_eq!(db.inner.state.read().state().core().next_wal_sst_id, 4);
6390 }
6391
6392 #[tokio::test]
6393 async fn test_empty_wal_should_fence_old_writer() {
6394 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
6395 let path = "/tmp/test_kv_store";
6396
6397 async fn do_put(db: &Db, key: &[u8], val: &[u8]) -> Result<WriteHandle, crate::Error> {
6398 db.put_with_options(
6399 key,
6400 val,
6401 &PutOptions::default(),
6402 &WriteOptions {
6403 await_durable: true,
6404 ..Default::default()
6405 },
6406 )
6407 .await
6408 }
6409
6410 let db1 = Db::builder(path, object_store.clone())
6412 .with_settings(test_db_options(0, 128, None))
6413 .build()
6414 .await
6415 .unwrap();
6416 do_put(&db1, b"1", b"1").await.unwrap();
6417
6418 let db2 = Db::builder(path, object_store.clone())
6420 .with_settings(test_db_options(0, 128, None))
6421 .build()
6422 .await
6423 .unwrap();
6424
6425 let err = do_put(&db1, b"1", b"1").await.unwrap_err();
6427 assert_eq!(err.to_string(), "Closed error: detected newer DB client");
6428
6429 do_put(&db2, b"2", b"2").await.unwrap();
6430 assert_eq!(db2.inner.state.read().state().core().next_wal_sst_id, 5);
6431 }
6432
6433 #[tokio::test(flavor = "multi_thread", worker_threads = 4)]
6434 async fn test_writer_paused_in_replay_wal_should_be_fenced_by_concurrent_open() {
6435 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
6443 let path = "/tmp/test_writer_paused_in_replay_wal_race";
6444 let fp_registry = Arc::new(FailPointRegistry::new());
6445
6446 fail_parallel::cfg(fp_registry.clone(), "replay-wal-pause", "pause").unwrap();
6449
6450 let w1_settings = {
6455 let mut s = test_db_options(0, 128, None);
6456 s.manifest_poll_interval = Duration::from_secs(600);
6457 s
6458 };
6459 let w1_handle = {
6460 let object_store = object_store.clone();
6461 let fp_registry = fp_registry.clone();
6462 tokio::spawn(async move {
6463 Db::builder(path, object_store)
6464 .with_settings(w1_settings)
6465 .with_fp_registry(fp_registry)
6466 .build()
6467 .await
6468 })
6469 };
6470
6471 let probe_table_store = Arc::new(TableStore::new(
6475 ObjectStores::new(object_store.clone(), None),
6476 SsTableFormat::default(),
6477 path,
6478 None,
6479 TableStoreKind::Main,
6480 BlockCachePolicy::default(),
6481 ));
6482 let mut w1_paused = false;
6483 for _ in 0..600 {
6484 let wals = probe_table_store.list_wal_ssts(..).await.unwrap();
6485 if !wals.is_empty() {
6486 w1_paused = true;
6487 break;
6488 }
6489 tokio::time::sleep(Duration::from_millis(10)).await;
6490 }
6491 assert!(w1_paused, "W1 did not reach replay_wal pause in time");
6492 tokio::time::sleep(Duration::from_millis(100)).await;
6495
6496 let db2 = Db::builder(path, object_store.clone())
6500 .with_settings(test_db_options(0, 128, None))
6501 .with_fp_registry(fp_registry.clone())
6502 .build()
6503 .await
6504 .unwrap();
6505 assert_eq!(
6506 db2.inner.state.read().state().manifest.value.writer_epoch,
6507 2
6508 );
6509
6510 fail_parallel::cfg(fp_registry.clone(), "replay-wal-pause", "off").unwrap();
6512 let db1 = w1_handle.await.unwrap().unwrap();
6513 assert_eq!(
6514 db1.inner.state.read().state().manifest.value.writer_epoch,
6515 1
6516 );
6517
6518 let result = db1
6520 .put_with_options(
6521 b"w1",
6522 b"value",
6523 &PutOptions::default(),
6524 &WriteOptions {
6525 await_durable: true,
6526 ..Default::default()
6527 },
6528 )
6529 .await;
6530 assert!(result.is_err());
6531 }
6532
6533 async fn wait_for_wal_sst_count(table_store: &TableStore, min_count: usize, context: &str) {
6534 for _ in 0..6000 {
6535 let wals = table_store.list_wal_ssts(..).await.unwrap();
6536 if wals.len() >= min_count {
6537 return;
6538 }
6539 tokio::time::sleep(Duration::from_millis(10)).await;
6540 }
6541 panic!("{context}");
6542 }
6543
6544 #[tokio::test(flavor = "multi_thread", worker_threads = 4)]
6545 async fn wal_replay_not_found_should_be_fenced_when_writer_epoch_advanced() {
6546 let base_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
6547 let gated_store = Arc::new(GatedObjectStore::new(base_store.clone()));
6548 let gated_object_store: Arc<dyn ObjectStore> = gated_store.clone();
6549 let path = "/tmp/wal_replay_not_found_should_be_fenced_when_writer_epoch_advanced";
6550 let fp_registry = Arc::new(FailPointRegistry::new());
6551
6552 fail_parallel::cfg(fp_registry.clone(), "replay-wal-pause", "pause").unwrap();
6553
6554 let w1_settings = {
6555 let mut s = test_db_options(0, 128, None);
6556 s.manifest_poll_interval = Duration::from_secs(600);
6557 s
6558 };
6559 let w1_handle = {
6560 let object_store = gated_object_store.clone();
6561 let fp_registry = fp_registry.clone();
6562 tokio::spawn(async move {
6563 Db::builder(path, object_store)
6564 .with_settings(w1_settings)
6565 .with_fp_registry(fp_registry)
6566 .build()
6567 .await
6568 })
6569 };
6570
6571 let probe_table_store = TableStore::new(
6572 ObjectStores::new(base_store.clone(), None),
6573 SsTableFormat::default(),
6574 path,
6575 None,
6576 TableStoreKind::Main,
6577 BlockCachePolicy::default(),
6578 );
6579 wait_for_wal_sst_count(
6580 &probe_table_store,
6581 1,
6582 "W1 did not write its fence WAL in time",
6583 )
6584 .await;
6585
6586 let head_arrivals_before = gated_store.head_gate.arrivals();
6587 gated_store.head_gate.close();
6588 fail_parallel::cfg(fp_registry.clone(), "replay-wal-pause", "off").unwrap();
6589 gated_store
6590 .head_gate
6591 .wait_for_arrivals(head_arrivals_before + 1)
6592 .await;
6593
6594 let db2 = Db::builder(path, base_store.clone())
6595 .with_settings(test_db_options(0, 128, None))
6596 .with_fp_registry(fp_registry.clone())
6597 .build()
6598 .await
6599 .unwrap();
6600 assert_eq!(
6601 db2.inner.state.read().state().manifest.value.writer_epoch,
6602 2
6603 );
6604
6605 probe_table_store
6606 .delete_sst(&SsTableId::Wal(1))
6607 .await
6608 .unwrap();
6609 gated_store.head_gate.release();
6610
6611 let err = match w1_handle.await.unwrap() {
6612 Ok(_) => panic!("expected W1 open to fail"),
6613 Err(err) => err,
6614 };
6615 assert!(
6616 matches!(err.kind(), crate::ErrorKind::Closed(CloseReason::Fenced)),
6617 "expected fenced error, got {err:?}"
6618 );
6619
6620 db2.close().await.unwrap();
6621 }
6622
6623 #[tokio::test(flavor = "multi_thread", worker_threads = 4)]
6624 async fn wal_replay_not_found_should_remain_not_found_when_writer_epoch_unchanged() {
6625 let base_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
6626 let gated_store = Arc::new(GatedObjectStore::new(base_store.clone()));
6627 let gated_object_store: Arc<dyn ObjectStore> = gated_store.clone();
6628 let path = "/tmp/wal_replay_not_found_should_remain_not_found_when_writer_epoch_unchanged";
6629 let fp_registry = Arc::new(FailPointRegistry::new());
6630
6631 fail_parallel::cfg(fp_registry.clone(), "replay-wal-pause", "pause").unwrap();
6632
6633 let w1_settings = {
6634 let mut s = test_db_options(0, 128, None);
6635 s.manifest_poll_interval = Duration::from_secs(600);
6636 s
6637 };
6638 let w1_handle = {
6639 let object_store = gated_object_store.clone();
6640 let fp_registry = fp_registry.clone();
6641 tokio::spawn(async move {
6642 Db::builder(path, object_store)
6643 .with_settings(w1_settings)
6644 .with_fp_registry(fp_registry)
6645 .build()
6646 .await
6647 })
6648 };
6649
6650 let probe_table_store = TableStore::new(
6651 ObjectStores::new(base_store.clone(), None),
6652 SsTableFormat::default(),
6653 path,
6654 None,
6655 TableStoreKind::Main,
6656 BlockCachePolicy::default(),
6657 );
6658 wait_for_wal_sst_count(
6659 &probe_table_store,
6660 1,
6661 "W1 did not write its fence WAL in time",
6662 )
6663 .await;
6664
6665 let head_arrivals_before = gated_store.head_gate.arrivals();
6666 gated_store.head_gate.close();
6667 fail_parallel::cfg(fp_registry.clone(), "replay-wal-pause", "off").unwrap();
6668 gated_store
6669 .head_gate
6670 .wait_for_arrivals(head_arrivals_before + 1)
6671 .await;
6672
6673 probe_table_store
6674 .delete_sst(&SsTableId::Wal(1))
6675 .await
6676 .unwrap();
6677 gated_store.head_gate.release();
6678
6679 let err = match w1_handle.await.unwrap() {
6680 Ok(_) => panic!("expected W1 open to fail"),
6681 Err(err) => err,
6682 };
6683 assert_eq!(err.kind(), crate::ErrorKind::Data);
6684 }
6685
6686 #[tokio::test]
6687 async fn test_invalid_clock_progression() {
6688 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
6690 let path = "/tmp/test_kv_store";
6691
6692 let clock = Arc::new(MockSystemClock::new());
6693 let db = Db::builder(path, object_store.clone())
6694 .with_settings(test_db_options(0, 128, None))
6695 .with_system_clock(clock.clone())
6696 .build()
6697 .await
6698 .unwrap();
6699
6700 clock.set(10);
6703 db.put_with_options(
6704 b"1",
6705 b"1",
6706 &PutOptions::default(),
6707 &WriteOptions {
6708 await_durable: false,
6709 ..Default::default()
6710 },
6711 )
6712 .await
6713 .unwrap();
6714
6715 clock.set(5);
6718 match db
6719 .put_with_options(
6720 b"1",
6721 b"1",
6722 &PutOptions::default(),
6723 &WriteOptions {
6724 await_durable: false, ..Default::default()
6725 },
6726 )
6727 .await
6728 {
6729 Ok(_) => panic!("expected an error on inserting backwards time"),
6730 Err(e) => assert_eq!(e.to_string(), "Invalid error: invalid clock tick, must be monotonic. last_tick=`10`, next_tick=`5`"),
6731 }
6732 }
6733
6734 #[tokio::test]
6735 async fn test_invalid_clock_progression_across_db_instances() {
6736 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
6738 let path = "/tmp/test_kv_store";
6739
6740 let clock = Arc::new(MockSystemClock::new());
6741 let db = Db::builder(path, object_store.clone())
6742 .with_settings(test_db_options(0, 128, None))
6743 .with_system_clock(clock.clone())
6744 .build()
6745 .await
6746 .unwrap();
6747
6748 clock.set(10);
6751 db.put_with_options(
6752 b"1",
6753 b"1",
6754 &PutOptions::default(),
6755 &WriteOptions {
6756 await_durable: false,
6757 ..Default::default()
6758 },
6759 )
6760 .await
6761 .unwrap();
6762 db.flush().await.unwrap();
6763
6764 let db2 = Db::builder(path, object_store.clone())
6765 .with_settings(test_db_options(0, 128, None))
6766 .with_system_clock(clock.clone())
6767 .build()
6768 .await
6769 .unwrap();
6770 clock.set(5);
6771 match db2
6772 .put_with_options(
6773 b"1",
6774 b"1",
6775 &PutOptions::default(),
6776 &WriteOptions {
6777 await_durable: false, ..Default::default()
6778 },
6779 )
6780 .await
6781 {
6782 Ok(_) => panic!("expected an error on inserting backwards time"),
6783 Err(e) => assert_eq!(e.to_string(), "Invalid error: invalid clock tick, must be monotonic. last_tick=`10`, next_tick=`5`"),
6784 }
6785 }
6786
6787 #[tokio::test]
6788 #[cfg(feature = "wal_disable")]
6789 async fn should_flush_all_memtables_when_wal_disabled() {
6790 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
6792 let path = "/tmp/test_kv_store";
6793
6794 let db_options = Settings {
6795 wal_enabled: false,
6796 flush_interval: Some(Duration::from_secs(10)),
6797 ..Settings::default()
6798 };
6799
6800 let db = Db::builder(path, object_store.clone())
6801 .with_settings(db_options.clone())
6802 .build()
6803 .await
6804 .unwrap();
6805
6806 let mut rng = proptest_util::rng::new_test_rng(None);
6807 let table = sample::table(&mut rng, 1000, 5);
6808 test_utils::seed_database(&db, &table, false).await.unwrap();
6809 db.flush().await.unwrap();
6810
6811 let reopened_db = Db::builder(path, object_store.clone())
6813 .with_settings(db_options.clone())
6814 .build()
6815 .await
6816 .unwrap();
6817
6818 assert_records_in_range(
6820 &table,
6821 &reopened_db,
6822 &ScanOptions::default(),
6823 BytesRange::from(..),
6824 )
6825 .await
6826 }
6827
6828 #[tokio::test]
6829 async fn test_recover_clock_tick_from_wal() {
6830 let fp_registry = Arc::new(FailPointRegistry::new());
6831 fail_parallel::cfg(
6835 fp_registry.clone(),
6836 "write-compacted-sst-io-error",
6837 "return",
6838 )
6839 .unwrap();
6840 let clock = Arc::new(MockSystemClock::new());
6841 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
6842 let path = "/tmp/test_kv_store";
6843
6844 let db = Db::builder(path, object_store.clone())
6845 .with_settings(test_db_options(0, 1024, None))
6846 .with_system_clock(clock.clone())
6847 .with_fp_registry(fp_registry.clone())
6848 .build()
6849 .await
6850 .unwrap();
6851
6852 clock.set(10);
6853 db.put_with_options(
6854 &[b'a'; 4],
6855 &[b'j'; 8],
6856 &PutOptions::default(),
6857 &WriteOptions {
6858 await_durable: false,
6859 ..Default::default()
6860 },
6861 )
6862 .await
6863 .expect("write batch failed");
6864 clock.set(11);
6865 db.put_with_options(
6866 &[b'b'; 4],
6867 &[b'k'; 8],
6868 &PutOptions::default(),
6869 &WriteOptions {
6870 await_durable: false,
6871 ..Default::default()
6872 },
6873 )
6874 .await
6875 .expect("write batch failed");
6876
6877 db.flush().await.unwrap();
6878 assert!(db.close().await.is_err());
6880
6881 let manifest_store = Arc::new(ManifestStore::new(&Path::from(path), object_store.clone()));
6884 let stored_manifest =
6885 StoredManifest::load(manifest_store, Arc::new(DefaultSystemClock::new()))
6886 .await
6887 .unwrap();
6888 let db_state = stored_manifest.db_state();
6889 let last_clock_tick = db_state.last_l0_clock_tick;
6890 assert_eq!(last_clock_tick, i64::MIN);
6891
6892 fail_parallel::cfg(fp_registry.clone(), "write-compacted-sst-io-error", "off").unwrap();
6894
6895 let clock = Arc::new(MockSystemClock::new());
6896 let db = Db::builder(path, object_store.clone())
6897 .with_settings(test_db_options(0, 1024, None))
6898 .with_system_clock(clock.clone())
6899 .with_fp_registry(fp_registry.clone())
6900 .build()
6901 .await
6902 .unwrap();
6903
6904 assert_eq!(db.inner.mono_clock.last_tick.load(Ordering::SeqCst), 11);
6905 }
6906
6907 #[tokio::test]
6908 async fn test_should_update_manifest_clock_tick_on_l0_flush() {
6909 let clock = Arc::new(MockSystemClock::new());
6910 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
6911 let path = "/tmp/test_kv_store";
6912
6913 let db = Db::builder(path, object_store.clone())
6914 .with_settings(test_db_options(0, 32, None))
6915 .with_system_clock(clock.clone())
6916 .build()
6917 .await
6918 .unwrap();
6919
6920 clock.set(10);
6923 db.put(&[b'a'; 4], &[b'j'; 8])
6924 .await
6925 .expect("write batch failed");
6926 clock.set(11);
6927 db.put(&[b'b'; 4], &[b'k'; 8])
6928 .await
6929 .expect("write batch failed");
6930
6931 db.flush().await.unwrap();
6933 db.close().await.unwrap();
6934
6935 let manifest_store = Arc::new(ManifestStore::new(&Path::from(path), object_store.clone()));
6938 let stored_manifest =
6939 StoredManifest::load(manifest_store, Arc::new(DefaultSystemClock::new()))
6940 .await
6941 .unwrap();
6942 let db_state = stored_manifest.db_state();
6943 let last_clock_tick = db_state.last_l0_clock_tick;
6944 assert_eq!(last_clock_tick, 11);
6945 }
6946
6947 #[tokio::test]
6948 #[cfg(feature = "wal_disable")]
6949 async fn test_recover_clock_tick_from_manifest() {
6950 let clock = Arc::new(MockSystemClock::new());
6951 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
6952 let path = "/tmp/test_kv_store";
6953 let mut options = test_db_options(0, 32, None);
6954 options.wal_enabled = false;
6955
6956 let db = Db::builder(path, object_store.clone())
6957 .with_settings(options)
6958 .with_system_clock(clock.clone())
6959 .build()
6960 .await
6961 .unwrap();
6962
6963 clock.set(10);
6964 db.put(&[b'a'; 4], &[b'j'; 28])
6965 .await
6966 .expect("write batch failed");
6967 clock.set(11);
6968 db.put(&[b'b'; 4], &[b'k'; 28])
6969 .await
6970 .expect("write batch failed");
6971
6972 db.flush().await.unwrap();
6974 db.close().await.unwrap();
6975
6976 let clock = Arc::new(MockSystemClock::new());
6977 let mut options = test_db_options(0, 32, None);
6978 options.wal_enabled = false;
6979 let db = Db::builder(path, object_store.clone())
6980 .with_settings(options)
6981 .with_system_clock(clock.clone())
6982 .build()
6983 .await
6984 .unwrap();
6985
6986 assert_eq!(db.inner.mono_clock.last_tick.load(Ordering::SeqCst), 11);
6987 }
6988
6989 #[tokio::test]
6990 async fn test_put_get_reopen_delete_with_separate_wal_store() {
6991 async fn count_ssts_in(store: &Arc<InMemory>) -> usize {
6992 store
6993 .list(None)
6994 .filter(|r| {
6995 future::ready(
6996 r.as_ref()
6997 .unwrap()
6998 .location
6999 .extension()
7000 .unwrap()
7001 .to_lowercase()
7002 == "sst",
7003 )
7004 })
7005 .count()
7006 .await
7007 }
7008
7009 let fp_registry = Arc::new(FailPointRegistry::new());
7010
7011 let main_object_store = Arc::new(InMemory::new());
7012 let wal_object_store = Arc::new(InMemory::new());
7013 let kv_store = Db::builder("/tmp/test_kv_store", main_object_store.clone())
7014 .with_settings(test_db_options(0, 1024, None))
7015 .with_wal_object_store(wal_object_store.clone())
7016 .with_fp_registry(fp_registry.clone())
7017 .build()
7018 .await
7019 .unwrap();
7020 assert_eq!(count_ssts_in(&main_object_store).await, 0);
7021 assert_eq!(count_ssts_in(&wal_object_store).await, 1);
7022
7023 let key = b"test_key";
7024 let value = b"test_value";
7025
7026 fail_parallel::cfg(fp_registry.clone(), "write-compacted-sst-io-error", "pause").unwrap();
7028 kv_store.put(key, value).await.unwrap();
7029 kv_store.flush().await.unwrap();
7030 assert_eq!(count_ssts_in(&main_object_store).await, 0);
7031 assert_eq!(count_ssts_in(&wal_object_store).await, 2);
7032 assert_eq!(
7033 kv_store.get(key).await.unwrap(),
7034 Some(Bytes::from_static(value))
7035 );
7036 fail_parallel::cfg(fp_registry.clone(), "write-compacted-sst-io-error", "off").unwrap();
7038
7039 let mut batch = WriteBatch::default();
7041 for i in 0u32..128 {
7042 batch.put(i.to_be_bytes(), i.to_be_bytes());
7043 }
7044 kv_store.write(batch).await.unwrap();
7045 kv_store.flush().await.unwrap();
7046 assert_eq!(count_ssts_in(&main_object_store).await, 1);
7047 assert_eq!(count_ssts_in(&wal_object_store).await, 3);
7048 assert_eq!(
7049 kv_store.get(key).await.unwrap(),
7050 Some(Bytes::from_static(value))
7051 );
7052
7053 kv_store.close().await.unwrap();
7054 assert_eq!(count_ssts_in(&wal_object_store).await, 3);
7055
7056 let kv_store = Db::builder("/tmp/test_kv_store", main_object_store)
7057 .with_settings(test_db_options(0, 1024, None))
7058 .with_wal_object_store(wal_object_store.clone())
7059 .build()
7060 .await
7061 .unwrap();
7062
7063 assert_eq!(
7064 kv_store.get(key).await.unwrap(),
7065 Some(Bytes::from_static(value))
7066 );
7067
7068 kv_store.delete(key).await.unwrap();
7069 assert_eq!(None, kv_store.get(key).await.unwrap());
7070
7071 kv_store.close().await.unwrap();
7072 }
7073
7074 #[tokio::test]
7075 async fn test_wal_store_reconfiguration_fails() {
7076 let object_store = Arc::new(InMemory::new());
7077 let wal_object_store = Arc::new(InMemory::new());
7078
7079 let kv_store = Db::builder("/tmp/test_kv_store", object_store.clone())
7080 .with_settings(test_db_options(0, 1024, None))
7081 .with_wal_object_store(wal_object_store.clone())
7082 .build()
7083 .await
7084 .unwrap();
7085 kv_store.close().await.unwrap();
7086
7087 let result = Db::builder("/tmp/test_kv_store", object_store)
7088 .with_settings(test_db_options(0, 1024, None))
7089 .build()
7090 .await;
7091 match result {
7092 Err(err) => {
7093 assert!(err.to_string().contains("unsupported"));
7094 }
7095 _ => panic!("expected Unsupported error"),
7096 }
7097 }
7098
7099 #[test]
7100 fn test_write_option_defaults() {
7101 let write_options = WriteOptions::default();
7104 assert!(write_options.await_durable);
7105 }
7106
7107 #[tokio::test]
7108 #[cfg(feature = "zstd")]
7109 async fn test_compression_overflow_bug() {
7110 use crate::config::CompressionCodec;
7115 use std::str::FromStr;
7116
7117 let os: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
7119 let compress = CompressionCodec::from_str("zstd").unwrap();
7120 let db_builder = Db::builder("/tmp/test_kv_store", os.clone()).with_settings(Settings {
7121 compression_codec: Some(compress),
7122 ..Settings::default()
7123 });
7124 let db = db_builder.build().await.unwrap();
7125
7126 for i in 0..1000 {
7127 let key = format!("k{}", i);
7128 let value = format!("{}{}", "v".repeat(i), i);
7129 let put_option = PutOptions::default();
7130 let write_option = WriteOptions {
7131 await_durable: false,
7132 ..Default::default()
7133 };
7134 db.put_with_options(key.as_bytes(), value.clone(), &put_option, &write_option)
7135 .await
7136 .expect("failed to put");
7137 }
7138 db.flush().await.expect("flush failed");
7139 db.close().await.expect("failed to close db");
7140
7141 let db_builder = Db::builder("/tmp/test_kv_store", os.clone()).with_settings(Settings {
7143 compression_codec: Some(compress),
7144 ..Settings::default()
7145 });
7146 let db = db_builder.build().await.unwrap();
7147 let v = db.get("k1").await.expect("get failed").unwrap();
7148 assert_eq!(v.as_ref(), b"v1");
7149
7150 db.close().await.expect("failed to close db");
7151 }
7152
7153 async fn wait_for_manifest_condition(
7154 sm: &mut StoredManifest,
7155 cond: impl Fn(&ManifestCore) -> bool,
7156 timeout: Duration,
7157 ) -> ManifestCore {
7158 let start = tokio::time::Instant::now();
7159 while start.elapsed() < timeout {
7160 let manifest = sm.refresh().await.unwrap();
7161 if cond(&manifest.core) {
7162 return manifest.core.clone();
7163 }
7164 tokio::time::sleep(Duration::from_millis(10)).await;
7165 }
7166 panic!("manifest condition took longer than timeout")
7167 }
7168
7169 fn test_db_options(
7170 min_filter_keys: u32,
7171 l0_sst_size_bytes: usize,
7172 compactor_options: Option<CompactorOptions>,
7173 ) -> Settings {
7174 test_db_options_with_ttl(min_filter_keys, l0_sst_size_bytes, compactor_options, None)
7175 }
7176
7177 fn fast_compactor_options() -> CompactorOptions {
7182 CompactorOptions {
7183 poll_interval: Duration::from_millis(100),
7184 worker: Some(CompactionWorkerOptions {
7185 compactions_poll_interval: Duration::from_millis(100),
7186 ..Default::default()
7187 }),
7188 ..Default::default()
7189 }
7190 }
7191
7192 fn test_db_options_with_ttl(
7193 min_filter_keys: u32,
7194 l0_sst_size_bytes: usize,
7195 compactor_options: Option<CompactorOptions>,
7196 ttl: Option<u64>,
7197 ) -> Settings {
7198 Settings {
7199 flush_interval: Some(Duration::from_millis(100)),
7200 #[cfg(feature = "wal_disable")]
7201 wal_enabled: true,
7202 manifest_poll_interval: Duration::from_millis(100),
7203 manifest_update_timeout: Duration::from_secs(300),
7204 max_unflushed_bytes: 134_217_728,
7205 l0_max_ssts: 8,
7206 l0_max_ssts_per_key: 8,
7207 l0_flush_parallelism: 1,
7208 min_filter_keys,
7209 l0_sst_size_bytes,
7210 max_wal_flushes_before_l0_flush: 4096,
7211 compactor_options,
7212 compression_codec: None,
7213 object_store_cache_options: ObjectStoreCacheOptions::default(),
7214 garbage_collector_options: None,
7215 metric_level: MetricLevel::default(),
7216 default_ttl: ttl,
7217 object_store_max_retries: None,
7218 block_format: None,
7219 }
7220 }
7221
7222 #[tokio::test]
7223 async fn test_snapshot_basic_functionality() {
7224 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
7225 let db = Db::open("test_db", object_store).await.unwrap();
7226
7227 db.put(b"key1", b"value1").await.unwrap();
7229 db.put(b"key2", b"value2").await.unwrap();
7230
7231 let snapshot = db.snapshot().await.unwrap();
7233
7234 assert_eq!(
7236 snapshot.get(b"key1").await.unwrap(),
7237 Some(Bytes::from(b"value1".as_ref()))
7238 );
7239 assert_eq!(
7240 snapshot.get(b"key2").await.unwrap(),
7241 Some(Bytes::from(b"value2".as_ref()))
7242 );
7243
7244 db.put(b"key3", b"value3").await.unwrap();
7246
7247 assert_eq!(snapshot.get(b"key3").await.unwrap(), None);
7249
7250 assert_eq!(
7252 db.get(b"key3").await.unwrap(),
7253 Some(Bytes::from(b"value3".as_ref()))
7254 );
7255 }
7256
7257 #[tokio::test]
7258 async fn test_recent_snapshot_min_seq_monotonic() {
7259 use crate::oracle::Oracle;
7260
7261 let path = "/tmp/test_recent_snapshot_min_seq_monotonic";
7262 let object_store = Arc::new(InMemory::new());
7263 let settings = Settings {
7264 l0_sst_size_bytes: 2 * 1024, max_unflushed_bytes: 4 * 1024, min_filter_keys: 0,
7267 flush_interval: Some(Duration::from_millis(100)),
7268 ..Default::default()
7269 };
7270
7271 let db = Db::builder(path, object_store)
7272 .with_settings(settings)
7273 .build()
7274 .await
7275 .unwrap();
7276
7277 {
7279 let state = db.inner.state.read();
7280 assert_eq!(state.state().core().recent_snapshot_min_seq, 0);
7281 }
7282
7283 db.put(b"key1", b"value1").await.unwrap();
7285 db.inner.flush_memtables(FlushTarget::All).await.unwrap();
7286
7287 {
7288 let state = db.inner.state.read();
7289 let recent_min_seq = state.state().core().recent_snapshot_min_seq;
7290 assert!(
7292 recent_min_seq > 0,
7293 "recent_snapshot_min_seq should be > 0 after flush"
7294 );
7295 }
7296
7297 let _snapshot = db.snapshot().await.unwrap();
7299 let snapshot_seq = db.inner.oracle.last_committed_seq();
7300
7301 db.put(b"key2", b"value2").await.unwrap();
7303 db.inner.flush_memtables(FlushTarget::All).await.unwrap();
7304
7305 let min_active_seq = db.inner.snapshot_manager.min_active_seq();
7307 assert!(min_active_seq.is_some());
7308 assert_eq!(min_active_seq.unwrap(), snapshot_seq);
7309
7310 {
7311 let state = db.inner.state.read();
7312 let recent_min_seq = state.state().core().recent_snapshot_min_seq;
7313 assert_eq!(
7314 recent_min_seq,
7315 min_active_seq.unwrap(),
7316 "recent_snapshot_min_seq should equal snapshot_manager.min_active_seq() after flush"
7317 );
7318 }
7319
7320 drop(_snapshot);
7322
7323 db.put(b"key3", b"value3").await.unwrap();
7325 db.inner.flush_memtables(FlushTarget::All).await.unwrap();
7326
7327 {
7329 let state = db.inner.state.read();
7330 let recent_min_seq = state.state().core().recent_snapshot_min_seq;
7331 let last_l0_seq = state.state().core().last_l0_seq;
7332
7333 assert_eq!(
7335 recent_min_seq, last_l0_seq,
7336 "recent_snapshot_min_seq should equal last_l0_seq when no active snapshots"
7337 );
7338 assert!(recent_min_seq > snapshot_seq);
7339 }
7340 }
7341
7342 #[tokio::test(flavor = "multi_thread", worker_threads = 4)]
7343 async fn test_compaction_resume_loses_merge_operands_after_snapshot_retention_advances() {
7344 let path =
7345 "/tmp/test_compaction_resume_loses_merge_operands_after_snapshot_retention_advances";
7346 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
7347 let fp_registry = Arc::new(FailPointRegistry::new());
7348 let should_compact = Arc::new(AtomicBool::new(false));
7349 let compactor_options = CompactorOptions {
7350 poll_interval: Duration::from_millis(10),
7351 commit_compacted_interval: Duration::from_millis(10),
7352 worker: Some(CompactionWorkerOptions {
7353 compactions_poll_interval: Duration::from_millis(10),
7354 heartbeat_interval: Duration::from_millis(10),
7355 max_sst_size: 1,
7356 ..Default::default()
7357 }),
7358 ..Default::default()
7359 };
7360 let settings_without_compactor = test_db_options(0, 1024, None);
7361
7362 let gated = Arc::new(crate::test_utils::GatedObjectStore::new(
7367 object_store.clone(),
7368 ));
7369 let gated_store: Arc<dyn ObjectStore> = gated.clone();
7370
7371 let db = Db::builder(path, object_store.clone())
7372 .with_settings(settings_without_compactor.clone())
7373 .with_sst_block_size(SstBlockSize::Other(1))
7374 .with_fp_registry(fp_registry.clone())
7375 .with_merge_operator(Arc::new(StringConcatMergeOperator))
7376 .with_compactor_builder(
7377 CompactorBuilder::new(path, gated_store)
7378 .with_options(compactor_options.clone())
7379 .with_scheduler_supplier(Arc::new(OnDemandCompactionSchedulerSupplier::new({
7380 let should_compact = should_compact.clone();
7381 Arc::new(move |_| should_compact.load(Ordering::SeqCst))
7382 }))),
7383 )
7384 .build()
7385 .await
7386 .unwrap();
7387
7388 db.merge(b"k", b"1").await.unwrap();
7389 let snapshot = db.snapshot().await.unwrap();
7390 let snapshot_seq = db.inner.oracle.last_committed_seq();
7391 db.flush().await.unwrap();
7392
7393 for operand in [b"2", b"3", b"4", b"5", b"6"] {
7394 db.merge(b"k", operand).await.unwrap();
7395 db.flush().await.unwrap();
7396 }
7397
7398 let manifest_store = Arc::new(ManifestStore::new(&Path::from(path), object_store.clone()));
7399 let mut stored_manifest =
7400 StoredManifest::load(manifest_store.clone(), Arc::new(DefaultSystemClock::new()))
7401 .await
7402 .unwrap();
7403 let staged = wait_for_manifest_condition(
7404 &mut stored_manifest,
7405 |core| core.last_l0_seq >= 6 && core.recent_snapshot_min_seq == snapshot_seq,
7406 Duration::from_secs(10),
7407 )
7408 .await;
7409 assert!(
7410 staged.tree.l0.len() > 1,
7411 "the test requires multiple L0s so compaction has work to resume"
7412 );
7413
7414 fail_parallel::cfg(
7419 fp_registry.clone(),
7420 "compactor-heartbeat-after-output-sst",
7421 "return",
7422 )
7423 .unwrap();
7424
7425 let mut status_rx = db.subscribe();
7426
7427 let baseline_puts = gated.put_opts_gate.arrivals();
7432 gated.put_opts_gate.close();
7433 should_compact.store(true, Ordering::SeqCst);
7434 tokio::time::timeout(
7435 Duration::from_secs(10),
7436 gated.put_opts_gate.wait_for_arrivals(baseline_puts + 1),
7437 )
7438 .await
7439 .expect("compaction should upload output SSTs");
7440 gated.put_opts_gate.admit(1);
7441
7442 tokio::time::timeout(Duration::from_secs(10), async {
7443 loop {
7444 match status_rx.borrow().close_reason {
7445 Some(CloseReason::Fenced) => break,
7446 Some(reason) => {
7447 panic!("expected compactor failpoint to fence DB, got {reason:?}")
7448 }
7449 None => {}
7450 }
7451 status_rx.changed().await.expect("db status channel closed");
7452 }
7453 })
7454 .await
7455 .expect("compactor did not hit the failpoint");
7456
7457 drop(snapshot);
7458 gated.put_opts_gate.release();
7459 db.close().await.unwrap();
7460 fail_parallel::cfg(
7461 fp_registry.clone(),
7462 "compactor-heartbeat-after-output-sst",
7463 "off",
7464 )
7465 .unwrap();
7466
7467 let db = Db::builder(path, object_store.clone())
7468 .with_settings(settings_without_compactor.clone())
7469 .with_sst_block_size(SstBlockSize::Other(1))
7470 .with_fp_registry(fp_registry.clone())
7471 .with_merge_operator(Arc::new(StringConcatMergeOperator))
7472 .build()
7473 .await
7474 .unwrap();
7475 db.put(b"zz-advance-retention", b"x").await.unwrap();
7476 db.flush().await.unwrap();
7477 wait_for_manifest_condition(
7478 &mut stored_manifest,
7479 |core| core.last_l0_seq >= 7 && core.recent_snapshot_min_seq > snapshot_seq,
7480 Duration::from_secs(10),
7481 )
7482 .await;
7483 db.close().await.unwrap();
7484
7485 let db = Db::builder(path, object_store.clone())
7486 .with_settings(settings_without_compactor)
7487 .with_sst_block_size(SstBlockSize::Other(1))
7488 .with_fp_registry(fp_registry)
7489 .with_merge_operator(Arc::new(StringConcatMergeOperator))
7490 .with_compactor_builder(
7491 CompactorBuilder::new(path, object_store.clone())
7492 .with_options(compactor_options)
7493 .with_scheduler_supplier(Arc::new(OnDemandCompactionSchedulerSupplier::new(
7494 Arc::new(|_| false),
7495 ))),
7496 )
7497 .build()
7498 .await
7499 .unwrap();
7500
7501 let manifest_store = Arc::new(ManifestStore::new(&Path::from(path), object_store.clone()));
7502 let mut stored_manifest =
7503 StoredManifest::load(manifest_store, Arc::new(DefaultSystemClock::new()))
7504 .await
7505 .unwrap();
7506 wait_for_manifest_condition(
7507 &mut stored_manifest,
7508 |core| !core.tree.compacted.is_empty(),
7509 Duration::from_secs(10),
7510 )
7511 .await;
7512 db.refresh_manifest().await.unwrap();
7513
7514 let actual = db.get(b"k").await.unwrap();
7515 db.close().await.unwrap();
7516
7517 assert_eq!(actual, Some(Bytes::from_static(b"123456")));
7518 }
7519
7520 #[tokio::test]
7521 async fn test_recent_snapshot_min_seq_uses_transaction_seq() {
7522 let path = "/tmp/test_recent_snapshot_min_seq_uses_transaction_seq";
7523 let object_store = Arc::new(InMemory::new());
7524 let db = Db::builder(path, object_store).build().await.unwrap();
7525
7526 {
7527 let state = db.inner.state.read();
7528 assert_eq!(state.state().core().recent_snapshot_min_seq, 0);
7529 }
7530
7531 db.put(b"key1", b"value1").await.unwrap();
7532 db.inner
7533 .flush_memtables(crate::memtable_flusher::FlushTarget::All)
7534 .await
7535 .unwrap();
7536
7537 let txn = db.begin(IsolationLevel::Snapshot).await.unwrap();
7538 let txn_seq = txn.seqnum();
7539
7540 db.put(b"key2", b"value2").await.unwrap();
7541 db.inner
7542 .flush_memtables(crate::memtable_flusher::FlushTarget::All)
7543 .await
7544 .unwrap();
7545
7546 let min_active_seq = db.inner.txn_manager.min_active_seq();
7547 assert_eq!(min_active_seq, Some(txn_seq));
7548
7549 {
7550 let state = db.inner.state.read();
7551 let recent_min_seq = state.state().core().recent_snapshot_min_seq;
7552 assert_eq!(
7553 recent_min_seq, txn_seq,
7554 "recent_snapshot_min_seq should equal txn_manager.min_active_seq() after flush"
7555 );
7556 }
7557
7558 drop(txn);
7559
7560 db.put(b"key3", b"value3").await.unwrap();
7561 db.inner
7562 .flush_memtables(crate::memtable_flusher::FlushTarget::All)
7563 .await
7564 .unwrap();
7565
7566 {
7567 let state = db.inner.state.read();
7568 let recent_min_seq = state.state().core().recent_snapshot_min_seq;
7569 let last_l0_seq = state.state().core().last_l0_seq;
7570 assert_eq!(
7571 recent_min_seq, last_l0_seq,
7572 "recent_snapshot_min_seq should equal last_l0_seq when no active transactions"
7573 );
7574 assert!(recent_min_seq > txn_seq);
7575 }
7576 }
7577
7578 #[tokio::test]
7579 async fn test_recent_snapshot_min_seq_prefers_snapshot_when_snapshot_seq_is_lower() {
7580 let path = "/tmp/test_recent_snapshot_min_seq_prefers_snapshot_when_snapshot_seq_is_lower";
7581 let object_store = Arc::new(InMemory::new());
7582 let db = Db::builder(path, object_store).build().await.unwrap();
7583
7584 db.put(b"key1", b"value1").await.unwrap();
7585 db.inner
7586 .flush_memtables(crate::memtable_flusher::FlushTarget::All)
7587 .await
7588 .unwrap();
7589
7590 let snapshot = db.snapshot().await.unwrap();
7591 let snapshot_seq = snapshot.seq();
7592
7593 db.put(b"key2", b"value2").await.unwrap();
7594 db.inner
7595 .flush_memtables(crate::memtable_flusher::FlushTarget::All)
7596 .await
7597 .unwrap();
7598
7599 let txn = db.begin(IsolationLevel::Snapshot).await.unwrap();
7600 let txn_seq = txn.seqnum();
7601
7602 assert_eq!(
7603 db.inner.snapshot_manager.min_active_seq(),
7604 Some(snapshot_seq)
7605 );
7606 assert_eq!(db.inner.txn_manager.min_active_seq(), Some(txn_seq));
7607 assert!(snapshot_seq < txn_seq);
7608
7609 db.put(b"key3", b"value3").await.unwrap();
7610 db.inner
7611 .flush_memtables(crate::memtable_flusher::FlushTarget::All)
7612 .await
7613 .unwrap();
7614
7615 {
7616 let state = db.inner.state.read();
7617 let recent_min_seq = state.state().core().recent_snapshot_min_seq;
7618 assert_eq!(
7619 recent_min_seq,
7620 snapshot_seq,
7621 "recent_snapshot_min_seq should use the snapshot seq when it is smaller than the transaction seq"
7622 );
7623 }
7624
7625 drop(snapshot);
7626
7627 db.put(b"key4", b"value4").await.unwrap();
7628 db.inner
7629 .flush_memtables(crate::memtable_flusher::FlushTarget::All)
7630 .await
7631 .unwrap();
7632
7633 assert_eq!(db.inner.snapshot_manager.min_active_seq(), None);
7634 assert_eq!(db.inner.txn_manager.min_active_seq(), Some(txn_seq));
7635
7636 {
7637 let state = db.inner.state.read();
7638 let recent_min_seq = state.state().core().recent_snapshot_min_seq;
7639 assert_eq!(
7640 recent_min_seq,
7641 txn_seq,
7642 "recent_snapshot_min_seq should move to the transaction seq after the snapshot is dropped"
7643 );
7644 }
7645 }
7646
7647 #[tokio::test]
7648 async fn test_recent_snapshot_min_seq_prefers_transaction_when_transaction_seq_is_lower() {
7649 let path =
7650 "/tmp/test_recent_snapshot_min_seq_prefers_transaction_when_transaction_seq_is_lower";
7651 let object_store = Arc::new(InMemory::new());
7652 let db = Db::builder(path, object_store).build().await.unwrap();
7653
7654 db.put(b"key1", b"value1").await.unwrap();
7655 db.inner
7656 .flush_memtables(crate::memtable_flusher::FlushTarget::All)
7657 .await
7658 .unwrap();
7659
7660 let txn = db.begin(IsolationLevel::Snapshot).await.unwrap();
7661 let txn_seq = txn.seqnum();
7662
7663 db.put(b"key2", b"value2").await.unwrap();
7664 db.inner
7665 .flush_memtables(crate::memtable_flusher::FlushTarget::All)
7666 .await
7667 .unwrap();
7668
7669 let snapshot = db.snapshot().await.unwrap();
7670 let snapshot_seq = snapshot.seq();
7671
7672 assert_eq!(db.inner.txn_manager.min_active_seq(), Some(txn_seq));
7673 assert_eq!(
7674 db.inner.snapshot_manager.min_active_seq(),
7675 Some(snapshot_seq)
7676 );
7677 assert!(txn_seq < snapshot_seq);
7678
7679 db.put(b"key3", b"value3").await.unwrap();
7680 db.inner
7681 .flush_memtables(crate::memtable_flusher::FlushTarget::All)
7682 .await
7683 .unwrap();
7684
7685 {
7686 let state = db.inner.state.read();
7687 let recent_min_seq = state.state().core().recent_snapshot_min_seq;
7688 assert_eq!(
7689 recent_min_seq,
7690 txn_seq,
7691 "recent_snapshot_min_seq should use the transaction seq when it is smaller than the snapshot seq"
7692 );
7693 }
7694
7695 drop(txn);
7696
7697 db.put(b"key4", b"value4").await.unwrap();
7698 db.inner
7699 .flush_memtables(crate::memtable_flusher::FlushTarget::All)
7700 .await
7701 .unwrap();
7702
7703 assert_eq!(db.inner.txn_manager.min_active_seq(), None);
7704 assert_eq!(
7705 db.inner.snapshot_manager.min_active_seq(),
7706 Some(snapshot_seq)
7707 );
7708
7709 {
7710 let state = db.inner.state.read();
7711 let recent_min_seq = state.state().core().recent_snapshot_min_seq;
7712 assert_eq!(
7713 recent_min_seq,
7714 snapshot_seq,
7715 "recent_snapshot_min_seq should move to the snapshot seq after the transaction is dropped"
7716 );
7717 }
7718 }
7719
7720 #[tokio::test]
7721 async fn test_memtable_flush_updates_last_remote_persisted_seq() {
7722 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
7723 let path = "/tmp/test";
7724 let mut opts = test_db_options(0, 256, None);
7725 opts.flush_interval = Some(Duration::MAX);
7726 let db = Db::builder(path, object_store.clone())
7727 .with_settings(opts)
7728 .build()
7729 .await
7730 .unwrap();
7731
7732 let write_opts = WriteOptions {
7734 await_durable: false,
7735 ..Default::default()
7736 };
7737 db.put_with_options(&b"foo", &b"bar", &PutOptions::default(), &write_opts)
7738 .await
7739 .unwrap();
7740 db.flush_with_options(FlushOptions {
7741 flush_type: FlushType::MemTable,
7742 })
7743 .await
7744 .unwrap();
7745
7746 let v = db
7748 .get_with_options(&b"foo", &ReadOptions::new().with_durability_filter(Memory))
7749 .await
7750 .unwrap();
7751 assert_eq!(v, Some(Bytes::from(b"bar".as_ref())));
7752 let v = db
7753 .get_with_options(&b"foo", &ReadOptions::new().with_durability_filter(Remote))
7754 .await
7755 .unwrap();
7756 assert_eq!(v, Some(Bytes::from(b"bar".as_ref())));
7757 }
7758
7759 #[tokio::test]
7760 async fn should_merge_operand_into_empty_key() {
7761 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
7763 let db = Db::builder("/tmp/test_merge_1", object_store.clone())
7764 .with_settings(test_db_options(0, 1024, None))
7765 .with_merge_operator(Arc::new(StringConcatMergeOperator))
7766 .build()
7767 .await
7768 .unwrap();
7769
7770 db.merge(b"key1", b"value1").await.unwrap();
7772
7773 let result = db.get(b"key1").await.unwrap();
7775 assert_eq!(result, Some(Bytes::from("value1")));
7776 }
7777
7778 #[tokio::test]
7779 async fn should_merge_multiple_operands_into_same_key() {
7780 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
7782 let db = Db::builder("/tmp/test_merge_2", object_store.clone())
7783 .with_settings(test_db_options(0, 1024, None))
7784 .with_merge_operator(Arc::new(StringConcatMergeOperator))
7785 .build()
7786 .await
7787 .unwrap();
7788
7789 db.merge(b"key1", b"a").await.unwrap();
7791 db.merge(b"key1", b"b").await.unwrap();
7792 db.merge(b"key1", b"c").await.unwrap();
7793
7794 let result = db.get(b"key1").await.unwrap();
7796 assert_eq!(result, Some(Bytes::from("abc")));
7797 }
7798
7799 #[tokio::test]
7800 async fn should_persist_merge_operands_across_flush() {
7801 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
7803 let db = Db::builder("/tmp/test_merge_3", object_store.clone())
7804 .with_settings(test_db_options(0, 1024, None))
7805 .with_merge_operator(Arc::new(StringConcatMergeOperator))
7806 .build()
7807 .await
7808 .unwrap();
7809
7810 db.merge(b"key1", b"a").await.unwrap();
7811 db.merge(b"key1", b"b").await.unwrap();
7812
7813 db.flush().await.unwrap();
7815
7816 let result = db.get(b"key1").await.unwrap();
7818 assert_eq!(result, Some(Bytes::from("ab")));
7819
7820 db.merge(b"key1", b"c").await.unwrap();
7822 let result = db.get(b"key1").await.unwrap();
7823 assert_eq!(result, Some(Bytes::from("abc")));
7824 }
7825
7826 #[tokio::test]
7827 async fn should_error_when_merging_without_merge_operator() {
7828 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
7830 let path = "/tmp/test_merge_4";
7831 let db = Db::builder(path, object_store.clone())
7832 .with_settings(test_db_options(0, 1024, None))
7833 .with_merge_operator(Arc::new(StringConcatMergeOperator))
7834 .build()
7835 .await
7836 .unwrap();
7837
7838 db.merge(b"key1", b"value1").await.unwrap();
7839 db.flush().await.unwrap();
7840 db.close().await.unwrap();
7841
7842 let db = Db::builder(path, object_store.clone())
7844 .with_settings(test_db_options(0, 1024, None))
7845 .build()
7846 .await
7847 .unwrap();
7848
7849 let err = db.get(b"key1").await.unwrap_err();
7851 assert_eq!(err.kind(), crate::ErrorKind::Invalid);
7852 }
7853
7854 #[tokio::test]
7855 async fn should_error_when_writing_merge_without_merge_operator() {
7856 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
7857 let db = Db::builder("/tmp/test_merge_4_write_fails", object_store.clone())
7858 .with_settings(test_db_options(0, 1024, None))
7859 .build()
7860 .await
7861 .unwrap();
7862
7863 let err = db.merge(b"key1", b"value1").await.unwrap_err();
7864 assert_eq!(err.kind(), crate::ErrorKind::Invalid);
7865
7866 let result = db.get(b"key1").await.unwrap();
7867 assert_eq!(result, None);
7868 }
7869
7870 #[tokio::test]
7871 async fn should_error_when_writing_batch_with_merge_without_merge_operator() {
7872 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
7873 let db = Db::builder("/tmp/test_merge_4_batch_write_fails", object_store.clone())
7874 .with_settings(test_db_options(0, 1024, None))
7875 .build()
7876 .await
7877 .unwrap();
7878
7879 let mut batch = WriteBatch::new();
7880 batch.put(b"key1", b"value1");
7881 batch.merge(b"key2", b"value2");
7882
7883 let err = db.write(batch).await.unwrap_err();
7884 assert_eq!(err.kind(), crate::ErrorKind::Invalid);
7885
7886 assert_eq!(db.get(b"key1").await.unwrap(), None);
7887 assert_eq!(db.get(b"key2").await.unwrap(), None);
7888 }
7889
7890 #[tokio::test]
7891 async fn should_merge_operands_after_reopen() {
7892 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
7894 let path = "/tmp/test_merge_5";
7895 let db = Db::builder(path, object_store.clone())
7896 .with_settings(test_db_options(0, 1024, None))
7897 .with_merge_operator(Arc::new(StringConcatMergeOperator))
7898 .build()
7899 .await
7900 .unwrap();
7901
7902 db.merge(b"key1", b"a").await.unwrap();
7903 db.merge(b"key1", b"b").await.unwrap();
7904 db.flush().await.unwrap();
7905 db.close().await.unwrap();
7906
7907 let db_reopened = Db::builder(path, object_store.clone())
7909 .with_settings(test_db_options(0, 1024, None))
7910 .with_merge_operator(Arc::new(StringConcatMergeOperator))
7911 .build()
7912 .await
7913 .unwrap();
7914
7915 let result = db_reopened.get(b"key1").await.unwrap();
7917 assert_eq!(result, Some(Bytes::from("ab")));
7918
7919 db_reopened.merge(b"key1", b"c").await.unwrap();
7921 let result = db_reopened.get(b"key1").await.unwrap();
7922 assert_eq!(result, Some(Bytes::from("abc")));
7923 }
7924
7925 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
7935 async fn test_gc_race_deletes_l0_before_manifest_update() {
7936 let fp_registry = Arc::new(FailPointRegistry::new());
7937 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
7938 let path = Path::from("/tmp/test_gc_race_deletes_l0_before_manifest_update");
7939
7940 let mut settings = test_db_options(0, 1024, None);
7941 settings.flush_interval = None;
7942
7943 let db = Db::builder(path.clone(), object_store.clone())
7944 .with_settings(settings)
7945 .with_fp_registry(fp_registry.clone())
7946 .build()
7947 .await
7948 .expect("failed to build DB");
7949 let db = Arc::new(db);
7950
7951 fail_parallel::cfg(
7953 fp_registry.clone(),
7954 "after-flush-imm-to-l0-before-manifest",
7955 "pause",
7956 )
7957 .expect("failed to set failpoint");
7958
7959 db.put_with_options(
7961 b"key1",
7962 b"value1",
7963 &PutOptions::default(),
7964 &WriteOptions {
7965 await_durable: false,
7966 ..Default::default()
7967 },
7968 )
7969 .await
7970 .expect("failed to put");
7971
7972 let this_db = db.clone();
7974 let flush_handle = tokio::spawn(async move {
7975 this_db
7976 .flush_with_options(FlushOptions {
7977 flush_type: FlushType::MemTable,
7978 })
7979 .await
7980 });
7981
7982 let mut ssts = Vec::new();
7984 for _ in 0..200 {
7985 ssts = db
7986 .inner
7987 .table_store
7988 .list_compacted_ssts(..)
7989 .await
7990 .expect("failed to list compacted ssts");
7991 if !ssts.is_empty() {
7992 break;
7993 }
7994 tokio::time::sleep(Duration::from_millis(10)).await;
7995 }
7996 assert_eq!(
7997 ssts.len(),
7998 1,
7999 "expected exactly one L0 SST after GC, but found {:?}",
8000 ssts.iter().map(|sst| sst.id).collect::<Vec<_>>()
8001 );
8002
8003 let gc_options = GarbageCollectorOptions {
8006 wal_options: Some(GarbageCollectorDirectoryOptions {
8007 interval: None,
8008 min_age: Duration::from_millis(0),
8009 dry_run: false,
8010 }),
8011 wal_fence_options: None,
8012 manifest_options: Some(GarbageCollectorDirectoryOptions {
8013 interval: None,
8014 min_age: Duration::from_millis(0),
8015 dry_run: false,
8016 }),
8017 compacted_options: Some(GarbageCollectorDirectoryOptions {
8018 interval: None,
8019 min_age: Duration::from_millis(0),
8020 dry_run: false,
8021 }),
8022 compactions_options: Some(GarbageCollectorDirectoryOptions {
8023 interval: None,
8024 min_age: Duration::from_millis(0),
8025 dry_run: false,
8026 }),
8027 detach_options: None,
8028 metric_level: None,
8029 boundary_files_enabled: true,
8030 object_store_max_retries: None,
8031 };
8032
8033 let gc = GarbageCollectorBuilder::new(path.clone(), object_store.clone())
8034 .with_options(gc_options)
8035 .with_system_clock(db.inner.system_clock.clone())
8036 .build();
8037
8038 for _ in 0..5 {
8040 gc.run_gc_once().await;
8041 ssts = db
8042 .inner
8043 .table_store
8044 .list_compacted_ssts(..)
8045 .await
8046 .expect("failed to list compacted ssts after manual GC");
8047 if ssts.is_empty() {
8048 break;
8049 }
8050 tokio::time::sleep(Duration::from_millis(10)).await;
8051 }
8052 assert_eq!(
8053 ssts.len(),
8054 1,
8055 "expected exactly one L0 SST after GC, but found {:?}",
8056 ssts.iter().map(|sst| sst.id).collect::<Vec<_>>()
8057 );
8058
8059 fail_parallel::cfg(
8061 fp_registry.clone(),
8062 "after-flush-imm-to-l0-before-manifest",
8063 "off",
8064 )
8065 .expect("failed to set failpoint");
8066 flush_handle
8067 .await
8068 .expect("failed to join flush handle")
8069 .expect("flush failed");
8070
8071 let manifest_store = ManifestStore::new(&path, object_store.clone());
8073 let manifest = manifest_store
8074 .read_latest_manifest()
8075 .await
8076 .expect("failed to read latest manifest");
8077 assert_eq!(
8078 manifest.manifest.core.tree.l0.len(),
8079 1,
8080 "expected exactly one L0 SST in manifest"
8081 );
8082 let l0_id = manifest.manifest.core.tree.l0[0].sst.id;
8083 assert_eq!(
8084 l0_id, ssts[0].id,
8085 "expected SST {:?} but found SST {:?}",
8086 ssts[0].id, l0_id,
8087 );
8088
8089 let table_store = TableStore::new(
8092 ObjectStores::new(object_store.clone(), None),
8093 SsTableFormat::default(),
8094 path.clone(),
8095 None,
8096 TableStoreKind::Main,
8097 BlockCachePolicy::default(),
8098 );
8099 let compacted_ssts = table_store
8100 .list_compacted_ssts(..)
8101 .await
8102 .expect("failed to list compacted ssts");
8103 let still_exists = compacted_ssts.iter().any(|m| m.id == l0_id);
8104 assert!(
8105 still_exists,
8106 "manifest references L0 SST {:?} that GC has already deleted",
8107 l0_id
8108 );
8109
8110 db.close().await.expect("failed to close DB");
8111 }
8112
8113 #[tokio::test]
8114 async fn test_write_handle() {
8115 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
8116 let path = "/tmp/test_write_handle_db";
8117 let clock = Arc::new(MockSystemClock::new());
8118 let db = Db::builder(path, object_store)
8119 .with_settings(test_db_options(0, 1024, None))
8120 .with_system_clock(clock.clone())
8121 .build()
8122 .await
8123 .unwrap();
8124
8125 let key = b"key1";
8127 let value = b"value1";
8128 clock.set(100);
8129 let handle = db
8130 .put_with_options(
8131 key,
8132 value,
8133 &PutOptions::default(),
8134 &WriteOptions {
8135 await_durable: false,
8136 ..Default::default()
8137 },
8138 )
8139 .await
8140 .unwrap();
8141 assert_eq!(handle.seqnum(), 1);
8142 assert_eq!(handle.create_ts(), 100);
8143
8144 clock.set(200);
8146 let put_opts = PutOptions {
8147 ttl: Ttl::ExpireAfter(1000),
8148 };
8149 let handle = db
8150 .put_with_options(
8151 b"key2",
8152 b"value2",
8153 &put_opts,
8154 &WriteOptions {
8155 await_durable: false,
8156 ..Default::default()
8157 },
8158 )
8159 .await
8160 .unwrap();
8161 assert_eq!(handle.seqnum(), 2);
8162 assert_eq!(handle.create_ts(), 200);
8163
8164 clock.set(300);
8166 let handle = db
8167 .delete_with_options(
8168 b"key1",
8169 &WriteOptions {
8170 await_durable: false,
8171 ..Default::default()
8172 },
8173 )
8174 .await
8175 .unwrap();
8176 assert_eq!(handle.seqnum(), 3);
8177 assert_eq!(handle.create_ts(), 300);
8178
8179 clock.set(400);
8181 let mut batch = WriteBatch::new();
8182 batch.put(b"key3", b"value3");
8183 batch.delete(b"key2");
8184 let handle = db
8185 .write_with_options(
8186 batch,
8187 &WriteOptions {
8188 await_durable: false,
8189 ..Default::default()
8190 },
8191 )
8192 .await
8193 .unwrap();
8194 assert_eq!(handle.seqnum(), 4);
8195 assert_eq!(handle.create_ts(), 400);
8196 }
8197
8198 #[tokio::test]
8199 async fn test_write_handle_with_batch() {
8200 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
8201 let path = "/tmp/test_write_batch_handle";
8202 let clock = Arc::new(MockSystemClock::new());
8203 let db = Db::builder(path, object_store)
8204 .with_settings(test_db_options(0, 1024, None))
8205 .with_system_clock(clock.clone())
8206 .build()
8207 .await
8208 .unwrap();
8209
8210 clock.set(100);
8212 let mut batch = WriteBatch::new();
8213 batch.put(b"key1", b"value1");
8214 batch.delete(b"key2");
8215 let handle = db
8216 .write_with_options(
8217 batch,
8218 &WriteOptions {
8219 await_durable: false,
8220 ..Default::default()
8221 },
8222 )
8223 .await
8224 .unwrap();
8225 assert_eq!(handle.seqnum(), 1);
8226 assert_eq!(handle.create_ts(), 100);
8227
8228 clock.set(200);
8230 let mut batch = WriteBatch::new();
8231 batch.put(b"key3", b"value3");
8232 batch.put(b"key4", b"value4");
8233 let handle = db
8234 .write_with_options(
8235 batch,
8236 &WriteOptions {
8237 await_durable: false,
8238 ..Default::default()
8239 },
8240 )
8241 .await
8242 .unwrap();
8243 assert_eq!(handle.seqnum(), 2);
8244 assert_eq!(handle.create_ts(), 200);
8245
8246 clock.set(300);
8248 let mut batch = WriteBatch::new();
8249 batch.delete(b"key1");
8250 let handle = db
8251 .write_with_options(
8252 batch,
8253 &WriteOptions {
8254 await_durable: false,
8255 ..Default::default()
8256 },
8257 )
8258 .await
8259 .unwrap();
8260 assert_eq!(handle.seqnum(), 3);
8261 assert_eq!(handle.create_ts(), 300);
8262 }
8263
8264 #[tokio::test]
8265 async fn test_write_with_options_empty_batch_returns_empty_batch_error() {
8266 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
8267 let db = Db::builder("/tmp/test_write_with_options_empty_batch", object_store)
8268 .with_settings(test_db_options(0, 1024, None))
8269 .build()
8270 .await
8271 .unwrap();
8272
8273 let err = db
8274 .inner
8275 .write_with_options(
8276 WriteBatch::new(),
8277 &WriteOptions {
8278 await_durable: false,
8279 ..Default::default()
8280 },
8281 None,
8282 )
8283 .await
8284 .unwrap_err();
8285 assert!(matches!(err, SlateDBError::EmptyBatch));
8286 }
8287
8288 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
8289 async fn test_txn_conflict_when_first_commit_paused_post_commit() {
8290 let fp_registry = Arc::new(FailPointRegistry::new());
8295 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
8296 let db = Db::builder("/tmp/test_txn_conflict_post_commit_pause", object_store)
8297 .with_settings(test_db_options(0, 1024, None))
8298 .with_fp_registry(fp_registry.clone())
8299 .build()
8300 .await
8301 .unwrap();
8302
8303 let txn1 = db
8305 .begin(IsolationLevel::SerializableSnapshot)
8306 .await
8307 .unwrap();
8308 txn1.put(b"k1", b"v1").unwrap();
8309
8310 fail_parallel::cfg(fp_registry.clone(), "write-batch-post-commit", "pause").unwrap();
8312
8313 let txn1_start_seq = txn1.seqnum();
8314
8315 let txn1_commit_task = tokio::spawn(async move { txn1.commit().await });
8317
8318 let pause_reached = tokio::time::timeout(Duration::from_secs(5), async {
8321 loop {
8322 let txn1_removed_from_active = db.inner.txn_manager.min_active_seq().is_none();
8323 if txn1_removed_from_active {
8324 break;
8325 }
8326 tokio::time::sleep(Duration::from_millis(10)).await;
8327 }
8328 })
8329 .await
8330 .is_ok();
8331 if !pause_reached {
8332 fail_parallel::cfg(fp_registry.clone(), "write-batch-post-commit", "off").unwrap();
8333 let _ = txn1_commit_task.await;
8334 panic!("txn1 did not pause at write-batch-post-commit");
8335 }
8336
8337 let txn_dropped = db
8339 .begin(IsolationLevel::SerializableSnapshot)
8340 .await
8341 .unwrap();
8342 drop(txn_dropped);
8343
8344 let txn2 = db
8348 .begin(IsolationLevel::SerializableSnapshot)
8349 .await
8350 .unwrap();
8351
8352 assert_eq!(
8355 txn2.get(b"k1").await.unwrap(),
8356 Some(Bytes::from_static(b"v1"))
8357 );
8358
8359 fail_parallel::cfg(fp_registry.clone(), "write-batch-post-commit", "off").unwrap();
8361
8362 let _ = txn1_commit_task
8364 .await
8365 .expect("failed to join txn1 commit task")
8366 .expect("txn1 commit should succeed");
8367 assert_eq!(
8368 txn2.seqnum(),
8369 txn1_start_seq + 1, "txn2 should see the commit seqnum after txn1's commit"
8371 );
8372
8373 txn2.put(b"k1", b"v2").unwrap();
8375 txn2.put(b"k2", b"v2").unwrap();
8376 assert!(txn2.commit().await.is_ok());
8377
8378 assert_eq!(
8380 db.get(b"k1").await.unwrap(),
8381 Some(Bytes::from_static(b"v2"))
8382 );
8383
8384 db.close().await.unwrap();
8385 }
8386
8387 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
8398 async fn test_commit_future_cancel_bypasses_conflict_detection() {
8399 let fp_registry = Arc::new(FailPointRegistry::new());
8400 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
8401 let db = Db::builder(
8402 "/tmp/test_commit_future_cancel_bypasses_conflict_detection",
8403 object_store,
8404 )
8405 .with_fp_registry(fp_registry.clone())
8406 .build()
8407 .await
8408 .unwrap();
8409
8410 db.put(b"x", b"v0").await.unwrap();
8412 let initial_last_seq = db.inner.oracle.last_seq();
8413
8414 let txn1 = db.begin(IsolationLevel::Snapshot).await.unwrap();
8416 let txn1_started_seq = txn1.seqnum();
8417 txn1.put(b"x", b"txn1_value").unwrap();
8418
8419 fail_parallel::cfg(fp_registry.clone(), "write-batch-pre-commit", "pause").unwrap();
8423
8424 let txn1_commit = tokio::spawn(async move { txn1.commit().await });
8427
8428 let reached = tokio::time::timeout(Duration::from_secs(30), async {
8433 while db.inner.oracle.last_seq() <= initial_last_seq {
8434 tokio::task::yield_now().await;
8435 }
8436 })
8437 .await;
8438 assert!(reached.is_ok(), "writer did not start processing txn1");
8439
8440 txn1_commit.abort();
8444 let _ = txn1_commit.await;
8445
8446 assert!(
8448 db.inner.txn_manager.min_active_seq().is_some(),
8449 "txn1 should still be in active_txns (writer owns the in-flight txn)"
8450 );
8451
8452 let txn2 = db.begin(IsolationLevel::Snapshot).await.unwrap();
8455 assert_eq!(
8456 txn2.seqnum(),
8457 txn1_started_seq,
8458 "txn2 should start at the same seq as txn1 (committed_seq not yet advanced)"
8459 );
8460 txn2.put(b"x", b"txn2_value").unwrap();
8461
8462 fail_parallel::cfg(fp_registry.clone(), "write-batch-pre-commit", "off").unwrap();
8465
8466 let result = txn2.commit().await;
8472 assert!(
8473 result.is_err(),
8474 "txn2 should detect a WW conflict with txn1"
8475 );
8476
8477 db.close().await.unwrap();
8478 }
8479
8480 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
8488 async fn test_cancelled_commit_writer_error_cleans_up_active_txn() {
8489 let fp_registry = Arc::new(FailPointRegistry::new());
8490 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
8491 let db = Db::builder(
8492 "/tmp/test_cancelled_commit_writer_error_cleans_up_active_txn",
8493 object_store,
8494 )
8495 .with_fp_registry(fp_registry.clone())
8496 .build()
8497 .await
8498 .unwrap();
8499
8500 let txn_a = db.begin(IsolationLevel::Snapshot).await.unwrap();
8503 let txn_b = db.begin(IsolationLevel::Snapshot).await.unwrap();
8504 txn_a.put(b"y", b"txn_a_value").unwrap();
8505 txn_b.put(b"y", b"txn_b_value").unwrap();
8506
8507 txn_a.commit().await.expect("txn_a commit should succeed");
8510 let seq_after_a = db.inner.oracle.last_seq();
8511
8512 fail_parallel::cfg(fp_registry.clone(), "write-batch-post-commit", "pause").unwrap();
8517 let filler_db = db.clone();
8518 let filler = tokio::spawn(async move { filler_db.put(b"z", b"filler_value").await });
8519 let reached = tokio::time::timeout(Duration::from_secs(30), async {
8520 while db.inner.oracle.last_seq() <= seq_after_a {
8521 tokio::task::yield_now().await;
8522 }
8523 })
8524 .await;
8525 assert!(
8526 reached.is_ok(),
8527 "writer did not start processing the filler"
8528 );
8529 let seq_after_filler = db.inner.oracle.last_seq();
8530
8531 assert!(
8537 txn_b.commit().now_or_never().is_none(),
8538 "commit should be pending while the writer is paused"
8539 );
8540
8541 assert!(
8544 db.inner.txn_manager.min_active_seq().is_some(),
8545 "txn_b should still be in active_txns (writer owns the in-flight txn)"
8546 );
8547
8548 fail_parallel::cfg(fp_registry.clone(), "write-batch-post-commit", "off").unwrap();
8552 filler
8553 .await
8554 .expect("failed to join filler task")
8555 .expect("filler write should succeed");
8556
8557 let processed = tokio::time::timeout(Duration::from_secs(30), async {
8560 while db.inner.oracle.last_seq() <= seq_after_filler {
8561 tokio::task::yield_now().await;
8562 }
8563 })
8564 .await;
8565 assert!(processed.is_ok(), "writer did not start processing txn_b");
8566
8567 let cleaned = tokio::time::timeout(Duration::from_secs(30), async {
8569 while db.inner.txn_manager.min_active_seq().is_some() {
8570 tokio::task::yield_now().await;
8571 }
8572 })
8573 .await;
8574 assert!(
8575 cleaned.is_ok(),
8576 "txn_b should have been cleaned up from active_txns by the writer"
8577 );
8578
8579 assert_eq!(
8581 db.get(b"y").await.unwrap(),
8582 Some(Bytes::from_static(b"txn_a_value"))
8583 );
8584
8585 db.close().await.unwrap();
8586 }
8587
8588 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
8594 async fn test_commit_enqueue_failure_cleans_up_active_txn() {
8595 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
8596 let db = Db::builder(
8597 "/tmp/test_commit_enqueue_failure_cleans_up_active_txn",
8598 object_store,
8599 )
8600 .build()
8601 .await
8602 .unwrap();
8603
8604 let txn_manager = db.inner.txn_manager.clone();
8607
8608 let txn = db.begin(IsolationLevel::Snapshot).await.unwrap();
8609 txn.put(b"k", b"v").unwrap();
8610 assert!(
8611 txn_manager.min_active_seq().is_some(),
8612 "txn should be registered in active_txns before commit"
8613 );
8614
8615 db.close().await.unwrap();
8618
8619 let result = txn.commit().await;
8622 assert!(
8623 result.is_err(),
8624 "commit should fail once the writer is closed"
8625 );
8626 assert!(
8627 txn_manager.min_active_seq().is_none(),
8628 "txn must be cleaned up on the caller side when enqueue fails"
8629 );
8630 }
8631
8632 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
8633 async fn test_txn_conflict_commit_seq_gap_does_not_block_l0_retirement() {
8634 const REPRO_SEED: u64 = 2_985_011_763_506_195_159;
8635 const MAX_UNFLUSHED_BYTES: usize = 8 * 1024;
8636 const LARGE_VALUE_BYTES: usize = 16 * 1024;
8637
8638 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
8639 let mut options = test_db_options(0, 1024, None);
8640 options.flush_interval = None;
8641 options.manifest_poll_interval = Duration::from_millis(10);
8642 options.max_unflushed_bytes = MAX_UNFLUSHED_BYTES;
8643 options.l0_max_ssts = 16;
8644
8645 let db = Db::builder(
8646 "/tmp/test_txn_conflict_commit_seq_gap_blocks_l0_manifest_retirement",
8647 object_store,
8648 )
8649 .with_seed(REPRO_SEED)
8650 .with_settings(options)
8651 .build()
8652 .await
8653 .unwrap();
8654 let write_opts = WriteOptions {
8655 await_durable: false,
8656 ..Default::default()
8657 };
8658
8659 db.put_with_options(b"conflict-key", b"v1", &PutOptions::default(), &write_opts)
8662 .await
8663 .unwrap();
8664 tokio::time::timeout(
8665 Duration::from_secs(5),
8666 db.flush_with_options(FlushOptions {
8667 flush_type: FlushType::MemTable,
8668 }),
8669 )
8670 .await
8671 .expect("timed out flushing base memtable")
8672 .expect("base memtable flush should succeed");
8673
8674 let txn = db
8678 .begin(IsolationLevel::SerializableSnapshot)
8679 .await
8680 .unwrap();
8681 assert_eq!(
8682 txn.get(b"conflict-key").await.unwrap(),
8683 Some(Bytes::from_static(b"v1"))
8684 );
8685
8686 db.put_with_options(b"conflict-key", b"v2", &PutOptions::default(), &write_opts)
8689 .await
8690 .unwrap();
8691 tokio::time::timeout(
8692 Duration::from_secs(5),
8693 db.flush_with_options(FlushOptions {
8694 flush_type: FlushType::MemTable,
8695 }),
8696 )
8697 .await
8698 .expect("timed out flushing conflict-producing write")
8699 .expect("conflict-producing write flush should succeed");
8700
8701 txn.put(b"txn-write", b"will-conflict").unwrap();
8705 let err = txn.commit().await.expect_err("transaction should conflict");
8706 assert_eq!(err.kind(), crate::ErrorKind::Transaction);
8707
8708 let large_value = vec![b'x'; LARGE_VALUE_BYTES];
8711 let large_handle = db
8712 .put_with_options(
8713 b"large-after-conflict",
8714 &large_value,
8715 &PutOptions::default(),
8716 &write_opts,
8717 )
8718 .await
8719 .expect("large write after conflict should be accepted");
8720 assert_eq!(
8721 large_handle.seqnum(),
8722 4,
8723 "the conflicted commit should have consumed commit_seq=3"
8724 );
8725 tokio::time::timeout(
8726 Duration::from_secs(5),
8727 db.flush_with_options(FlushOptions {
8728 flush_type: FlushType::Wal,
8729 }),
8730 )
8731 .await
8732 .expect("timed out flushing WAL after large write")
8733 .expect("WAL flush after large write should succeed");
8734
8735 let large_seq = large_handle.seqnum();
8739 tokio::time::timeout(Duration::from_secs(5), async {
8740 loop {
8741 let (last_l0_seq, has_immutable_memtable) = {
8742 let guard = db.inner.state.read();
8743 (
8744 guard.state().core().last_l0_seq,
8745 !guard.state().imm_memtable.is_empty(),
8746 )
8747 };
8748 if last_l0_seq >= large_seq || has_immutable_memtable {
8749 break;
8750 }
8751 tokio::time::sleep(Duration::from_millis(10)).await;
8752 }
8753 })
8754 .await
8755 .expect("large write never froze or flushed");
8756
8757 tokio::time::timeout(
8762 Duration::from_secs(5),
8763 db.put_with_options(
8764 b"write-after-gap",
8765 b"v",
8766 &PutOptions::default(),
8767 &write_opts,
8768 ),
8769 )
8770 .await
8771 .expect("timed out waiting for write after conflicted transaction consumed commit_seq=3")
8772 .expect("write after conflicted commit sequence gap should succeed");
8773
8774 db.close().await.unwrap();
8775 }
8776
8777 #[tokio::test]
8778 async fn should_notify_seq_watcher_on_wal_flush() {
8779 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
8781 let db = Db::builder("/tmp/test_watch_wal", object_store)
8782 .build()
8783 .await
8784 .unwrap();
8785 let mut watcher = db.subscribe();
8786
8787 db.put(b"key1", b"value1").await.unwrap();
8789 db.put(b"key2", b"value2").await.unwrap();
8790 db.put(b"key3", b"value3").await.unwrap();
8791 db.flush_with_options(FlushOptions {
8792 flush_type: FlushType::Wal,
8793 })
8794 .await
8795 .unwrap();
8796
8797 let status = tokio::time::timeout(
8799 Duration::from_secs(10),
8800 watcher.wait_for(|s| s.durable_seq >= 3),
8801 )
8802 .await
8803 .expect("timed out waiting for seq update")
8804 .expect("watch channel closed")
8805 .clone();
8806 assert!(
8807 status.durable_seq >= 3,
8808 "expected durable seq >= 3, got {}",
8809 status.durable_seq
8810 );
8811
8812 db.close().await.unwrap();
8813 }
8814
8815 #[tokio::test]
8816 async fn should_subscribe_to_current_manifest_updates_after_flush() {
8817 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
8819 let path = Path::from("/tmp/test_watch_current_manifest");
8820 let db = Db::builder(path.clone(), object_store.clone())
8821 .with_settings(test_db_options(0, 1024, None))
8822 .build()
8823 .await
8824 .unwrap();
8825 let mut watcher = db.subscribe();
8826 let manifest_store = Arc::new(ManifestStore::new(&path, object_store));
8827 let mut stored_manifest =
8828 StoredManifest::load(manifest_store, Arc::new(DefaultSystemClock::new()))
8829 .await
8830 .unwrap();
8831
8832 assert_eq!(watcher.borrow().current_manifest, db.manifest());
8833
8834 db.put(b"key1", b"value1").await.unwrap();
8836 db.put(b"key2", b"value2").await.unwrap();
8837 db.flush_with_options(FlushOptions {
8838 flush_type: FlushType::MemTable,
8839 })
8840 .await
8841 .unwrap();
8842 wait_for_manifest_condition(
8843 &mut stored_manifest,
8844 |manifest| manifest.last_l0_seq >= 2,
8845 Duration::from_secs(10),
8846 )
8847 .await;
8848
8849 let status = tokio::time::timeout(
8851 Duration::from_secs(10),
8852 watcher.wait_for(|s| {
8853 s.current_manifest.manifest.core.last_l0_seq >= 2 && s.durable_seq >= 2
8854 }),
8855 )
8856 .await
8857 .expect("timed out waiting for manifest update")
8858 .expect("watch channel closed")
8859 .clone();
8860 assert!(status.durable_seq >= 2);
8861 assert_eq!(status.current_manifest.manifest.core.last_l0_seq, 2);
8862
8863 db.close().await.unwrap();
8864 }
8865
8866 #[tokio::test]
8867 async fn should_publish_remote_manifest_updates_via_poll() {
8868 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
8870 let path = Path::from("/tmp/test_watch_remote_manifest");
8871 let db = Db::builder(path.clone(), object_store.clone())
8872 .with_settings(test_db_options(0, 1024, None))
8873 .build()
8874 .await
8875 .unwrap();
8876 let mut watcher = db.subscribe();
8877 let initial_checkpoint_count = watcher
8878 .borrow()
8879 .current_manifest
8880 .manifest
8881 .core
8882 .checkpoints
8883 .len();
8884
8885 let manifest_store = Arc::new(ManifestStore::new(&path, object_store));
8886 let mut stored_manifest =
8887 StoredManifest::load(manifest_store, Arc::new(DefaultSystemClock::new()))
8888 .await
8889 .unwrap();
8890
8891 stored_manifest
8893 .write_checkpoint(uuid::Uuid::new_v4(), &CheckpointOptions::default())
8894 .await
8895 .unwrap();
8896 wait_for_manifest_condition(
8897 &mut stored_manifest,
8898 |manifest| manifest.checkpoints.len() > initial_checkpoint_count,
8899 Duration::from_secs(10),
8900 )
8901 .await;
8902
8903 let status = tokio::time::timeout(
8905 Duration::from_secs(10),
8906 watcher.wait_for(|s| {
8907 s.current_manifest.manifest.core.checkpoints.len() > initial_checkpoint_count
8908 }),
8909 )
8910 .await
8911 .expect("timed out waiting for remote manifest update")
8912 .expect("watch channel closed")
8913 .clone();
8914 assert_eq!(
8915 status.current_manifest.manifest.core.checkpoints.len(),
8916 initial_checkpoint_count + 1
8917 );
8918
8919 db.close().await.unwrap();
8920 }
8921
8922 #[tokio::test]
8923 async fn should_close_watcher_on_db_drop() {
8924 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
8926 let db = Db::builder("/tmp/test_watch_drop", object_store)
8927 .build()
8928 .await
8929 .unwrap();
8930 let mut watcher = db.subscribe();
8931
8932 db.close().await.unwrap();
8934
8935 let status = watcher
8937 .wait_for(|s| s.close_reason.is_some())
8938 .await
8939 .expect("watch channel closed")
8940 .clone();
8941 assert_eq!(
8942 status.close_reason,
8943 Some(CloseReason::Clean),
8944 "expected close_reason = Clean after db close",
8945 );
8946
8947 drop(db);
8949
8950 let result = watcher.changed().await;
8952 assert!(
8953 result.is_err(),
8954 "expected watch channel closed after db drop, got Ok",
8955 );
8956 }
8957
8958 #[tokio::test]
8959 async fn should_report_close_reason_clean_on_db_close() {
8960 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
8962 let db = Db::builder("/tmp/test_close_reason_clean", object_store)
8963 .build()
8964 .await
8965 .unwrap();
8966 let mut watcher = db.subscribe();
8967
8968 db.close().await.unwrap();
8970
8971 let status = watcher
8973 .wait_for(|s| s.close_reason.is_some())
8974 .await
8975 .expect("watch channel closed")
8976 .clone();
8977 assert_eq!(status.close_reason, Some(CloseReason::Clean));
8978 }
8979
8980 #[tokio::test]
8981 async fn should_report_close_reason_panic_on_background_task_failure() {
8982 let fp_registry = Arc::new(FailPointRegistry::new());
8984 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
8985 let db = Db::builder("/tmp/test_close_reason_panic", object_store)
8986 .with_settings(test_db_options(0, 128, None))
8987 .with_fp_registry(fp_registry.clone())
8988 .build()
8989 .await
8990 .unwrap();
8991 let mut watcher = db.subscribe();
8992
8993 fail_parallel::cfg(fp_registry.clone(), "write-wal-sst-io-error", "panic").unwrap();
8995 let _ = db.put(b"foo", b"bar").await;
8996
8997 let status = tokio::time::timeout(
8999 Duration::from_secs(10),
9000 watcher.wait_for(|s| s.close_reason.is_some()),
9001 )
9002 .await
9003 .expect("timed out waiting for close reason")
9004 .expect("watch channel closed")
9005 .clone();
9006 assert_eq!(status.close_reason, Some(CloseReason::Panic));
9007 }
9008
9009 #[tokio::test]
9010 async fn should_report_close_reason_fenced_on_fenced_error() {
9011 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
9013 let db = Db::builder("/tmp/test_close_reason_fenced", object_store)
9014 .with_settings(test_db_options(0, 1024, None))
9015 .build()
9016 .await
9017 .unwrap();
9018 let mut watcher = db.subscribe();
9019
9020 db.inner
9022 .status_manager
9023 .write_result(Err(crate::error::SlateDBError::Fenced));
9024
9025 let status = tokio::time::timeout(
9027 Duration::from_secs(10),
9028 watcher.wait_for(|s| s.close_reason.is_some()),
9029 )
9030 .await
9031 .expect("timed out waiting for close reason")
9032 .expect("watch channel closed")
9033 .clone();
9034 assert_eq!(status.close_reason, Some(CloseReason::Fenced));
9035 }
9036
9037 #[cfg(feature = "wal_disable")]
9038 #[tokio::test]
9039 async fn should_notify_seq_watcher_on_l0_flush_when_wal_disabled() {
9040 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
9042 let mut options = test_db_options(0, 256, None);
9043 options.wal_enabled = false;
9044 let db = Db::builder("/tmp/test_watch_l0", object_store)
9045 .with_settings(options)
9046 .build()
9047 .await
9048 .unwrap();
9049 let mut watcher = db.subscribe();
9050
9051 db.put_with_options(
9053 b"key1",
9054 b"value1",
9055 &PutOptions::default(),
9056 &WriteOptions {
9057 await_durable: false,
9058 ..Default::default()
9059 },
9060 )
9061 .await
9062 .unwrap();
9063 db.put_with_options(
9064 b"key2",
9065 b"value2",
9066 &PutOptions::default(),
9067 &WriteOptions {
9068 await_durable: false,
9069 ..Default::default()
9070 },
9071 )
9072 .await
9073 .unwrap();
9074 db.flush_with_options(FlushOptions {
9075 flush_type: FlushType::MemTable,
9076 })
9077 .await
9078 .unwrap();
9079
9080 let status = tokio::time::timeout(
9082 Duration::from_secs(10),
9083 watcher.wait_for(|s| s.durable_seq >= 2),
9084 )
9085 .await
9086 .expect("timed out waiting for seq update")
9087 .expect("watch channel closed")
9088 .clone();
9089 assert!(
9090 status.durable_seq >= 2,
9091 "expected durable seq >= 2, got {}",
9092 status.durable_seq
9093 );
9094
9095 db.close().await.unwrap();
9096 }
9097
9098 #[cfg(feature = "wal_disable")]
9099 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
9100 async fn reads_succeed_when_compacted_sr_splits_same_key_across_ssts() {
9101 use crate::SstBlockSize;
9102 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
9103 let path = "/tmp/test_merge_split_sr_repro";
9104 let should_compact = Arc::new(AtomicBool::new(false));
9105 let should_compact2 = should_compact.clone();
9106 let compaction_scheduler = Arc::new(OnDemandCompactionSchedulerSupplier::new(Arc::new(
9107 move |_state| {
9108 let result = should_compact2
9109 .compare_exchange(true, false, Ordering::SeqCst, Ordering::SeqCst)
9110 .unwrap_or(false);
9111 if result {
9112 info!("TRIGGER COMPACT");
9113 }
9114 result
9115 },
9116 )));
9117
9118 let mut settings = test_db_options(
9120 0,
9121 128,
9122 Some(CompactorOptions {
9123 poll_interval: Duration::from_millis(20),
9124 max_concurrent_compactions: 1,
9125 manifest_update_timeout: Duration::from_secs(300),
9126 worker: Some(CompactionWorkerOptions {
9127 max_sst_size: 128,
9128 ..Default::default()
9129 }),
9130 ..Default::default()
9131 }),
9132 );
9133 settings.l0_max_ssts = 10_000;
9134 settings.l0_max_ssts_per_key = 10_000;
9135 settings.flush_interval = None;
9136 settings.wal_enabled = false;
9137
9138 let compactor_options = settings.compactor_options.take().unwrap();
9139 let db = Db::builder(path, object_store.clone())
9140 .with_settings(settings)
9141 .with_sst_block_size(SstBlockSize::Other(64))
9142 .with_merge_operator(Arc::new(StringConcatMergeOperator))
9143 .with_compactor_builder(
9144 CompactorBuilder::new(path, object_store.clone())
9145 .with_scheduler_supplier(compaction_scheduler)
9146 .with_options(compactor_options),
9147 )
9148 .build()
9149 .await
9150 .unwrap();
9151
9152 db.put_with_options(
9154 b"k",
9155 b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0",
9156 &PutOptions::default(),
9157 &WriteOptions {
9158 await_durable: false,
9159 ..Default::default()
9160 },
9161 )
9162 .await
9163 .unwrap();
9164 let _snapshot = db.snapshot().await.unwrap();
9165
9166 for i in 0..16u16 {
9168 let val = format!("{}{}", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", i + 1);
9169 db.put_with_options(
9170 b"k",
9171 val.as_bytes(),
9172 &PutOptions::default(),
9173 &WriteOptions {
9174 await_durable: false,
9175 ..Default::default()
9176 },
9177 )
9178 .await
9179 .unwrap();
9180 }
9181
9182 db.flush_with_options(FlushOptions {
9184 flush_type: FlushType::MemTable,
9185 })
9186 .await
9187 .unwrap();
9188
9189 tokio::time::timeout(Duration::from_secs(60), async {
9193 loop {
9194 should_compact.store(true, Ordering::SeqCst);
9195 {
9196 let state = db.inner.state.read();
9197 info!(
9198 "l0: {:?}",
9199 state
9200 .state()
9201 .core()
9202 .tree
9203 .l0
9204 .iter()
9205 .map(|t| t.estimate_size())
9206 .collect::<Vec<_>>()
9207 );
9208 info!(
9209 "compacted: {:?}",
9210 state
9211 .state()
9212 .core()
9213 .tree
9214 .compacted
9215 .iter()
9216 .map(|t| t.estimate_size())
9217 .collect::<Vec<_>>()
9218 );
9219 if state
9220 .state()
9221 .core()
9222 .tree
9223 .compacted
9224 .first()
9225 .is_some_and(|sr| sr.sst_views.len() > 1)
9226 {
9227 break;
9228 }
9229 }
9230 tokio::time::sleep(Duration::from_millis(3000)).await;
9231 }
9232 })
9233 .await
9234 .expect("timed out waiting for compacted SR where one key spans multiple SSTs");
9235
9236 let data = db.get(b"k").await.unwrap().unwrap();
9237 let expected = Bytes::from(b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa16".as_ref());
9238 let data_scan = db
9239 .scan(b"k".as_slice()..)
9240 .await
9241 .unwrap()
9242 .next()
9243 .await
9244 .unwrap()
9245 .unwrap();
9246 info!("data: {:?}", data);
9247 info!("data (scan): {:?}", data_scan.value);
9248 assert_eq!(data, expected);
9249 assert_eq!(data_scan.value, expected);
9250 }
9251
9252 #[cfg(feature = "wal_disable")]
9253 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
9254 async fn reads_succeed_when_compacted_sr_splits_same_merge_key_across_ssts() {
9255 use crate::SstBlockSize;
9256 use bytes::{BufMut as _, BytesMut};
9257 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
9258 let path = "/tmp/test_merge_split_sr_repro";
9259 let should_compact = Arc::new(AtomicBool::new(false));
9260 let should_compact2 = should_compact.clone();
9261 let compaction_scheduler = Arc::new(OnDemandCompactionSchedulerSupplier::new(Arc::new(
9262 move |_state| {
9263 let result = should_compact2
9264 .compare_exchange(true, false, Ordering::SeqCst, Ordering::SeqCst)
9265 .unwrap_or(false);
9266 if result {
9267 info!("TRIGGER COMPACT");
9268 }
9269 result
9270 },
9271 )));
9272
9273 let mut settings = test_db_options(
9275 0,
9276 128,
9277 Some(CompactorOptions {
9278 poll_interval: Duration::from_millis(20),
9279 max_concurrent_compactions: 1,
9280 manifest_update_timeout: Duration::from_secs(300),
9281 worker: Some(CompactionWorkerOptions {
9282 max_sst_size: 128,
9283 ..Default::default()
9284 }),
9285 ..Default::default()
9286 }),
9287 );
9288 settings.l0_max_ssts = 10_000;
9289 settings.l0_max_ssts_per_key = 10_000;
9290 settings.flush_interval = None;
9291 settings.wal_enabled = false;
9292
9293 let compactor_options = settings.compactor_options.take().unwrap();
9294 let db = Db::builder(path, object_store.clone())
9295 .with_settings(settings)
9296 .with_sst_block_size(SstBlockSize::Other(64))
9297 .with_merge_operator(Arc::new(StringConcatMergeOperator))
9298 .with_compactor_builder(
9299 CompactorBuilder::new(path, object_store.clone())
9300 .with_scheduler_supplier(compaction_scheduler)
9301 .with_options(compactor_options),
9302 )
9303 .build()
9304 .await
9305 .unwrap();
9306
9307 let mut expected = BytesMut::new();
9309 db.put_with_options(
9310 b"k",
9311 b"base",
9312 &PutOptions::default(),
9313 &WriteOptions {
9314 await_durable: false,
9315 ..Default::default()
9316 },
9317 )
9318 .await
9319 .unwrap();
9320 expected.put(b"base".as_slice());
9321 let _snapshot = db.snapshot().await.unwrap();
9322
9323 for i in 0..16u8 {
9325 let operand = vec![b'a' + i; 32];
9326 expected.put(operand.as_slice());
9327 db.merge_with_options(
9328 b"k",
9329 operand,
9330 &MergeOptions::default(),
9331 &WriteOptions {
9332 await_durable: false,
9333 ..Default::default()
9334 },
9335 )
9336 .await
9337 .unwrap();
9338 }
9339
9340 db.flush_with_options(FlushOptions {
9342 flush_type: FlushType::MemTable,
9343 })
9344 .await
9345 .unwrap();
9346
9347 tokio::time::timeout(Duration::from_secs(60), async {
9351 loop {
9352 should_compact.store(true, Ordering::SeqCst);
9353 {
9354 let state = db.inner.state.read();
9355 info!(
9356 "l0: {:?}",
9357 state
9358 .state()
9359 .core()
9360 .tree
9361 .l0
9362 .iter()
9363 .map(|t| t.estimate_size())
9364 .collect::<Vec<_>>()
9365 );
9366 info!(
9367 "compacted: {:?}",
9368 state
9369 .state()
9370 .core()
9371 .tree
9372 .compacted
9373 .iter()
9374 .map(|t| t.estimate_size())
9375 .collect::<Vec<_>>()
9376 );
9377 if state
9378 .state()
9379 .core()
9380 .tree
9381 .compacted
9382 .first()
9383 .is_some_and(|sr| sr.sst_views.len() > 1)
9384 {
9385 break;
9386 }
9387 }
9388 tokio::time::sleep(Duration::from_millis(3000)).await;
9389 }
9390 })
9391 .await
9392 .expect("timed out waiting for compacted SR where one key spans multiple SSTs");
9393
9394 let data = db.get(b"k").await.unwrap().unwrap();
9395 let expected = expected.freeze();
9396 let data_scan = db
9397 .scan(b"k".as_slice()..)
9398 .await
9399 .unwrap()
9400 .next()
9401 .await
9402 .unwrap()
9403 .unwrap();
9404 info!("data: {:?}", data);
9405 info!("data (scan): {:?}", data_scan.value);
9406 assert_eq!(data, expected);
9407 assert_eq!(data_scan.value, expected);
9408 }
9409
9410 #[tokio::test]
9411 async fn test_get_key_value() {
9412 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
9413 let path = "/tmp/test_get_key_value";
9414 let clock = Arc::new(MockSystemClock::new());
9415 let db = Db::builder(path, object_store)
9416 .with_settings(test_db_options(0, 1024, None))
9417 .with_system_clock(clock.clone())
9418 .build()
9419 .await
9420 .unwrap();
9421
9422 clock.set(100);
9423 let key = b"key1";
9424 let value = b"value1";
9425 db.put_with_options(
9426 key,
9427 value,
9428 &PutOptions {
9429 ttl: Ttl::ExpireAfter(50),
9430 },
9431 &WriteOptions {
9432 await_durable: false,
9433 ..Default::default()
9434 },
9435 )
9436 .await
9437 .unwrap();
9438
9439 let kv = db.get_key_value(key).await.unwrap().unwrap();
9440 assert_eq!(kv.key, Bytes::from_static(key));
9441 assert_eq!(kv.value, Bytes::from_static(value));
9442 assert_eq!(kv.seq, 1);
9443 assert_eq!(kv.create_ts, 100);
9444 assert_eq!(kv.expire_ts, Some(150));
9445 }
9446
9447 #[tokio::test]
9448 async fn test_scan_row_entry() {
9449 use crate::types::ValueDeletable;
9450
9451 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
9452 let path = "/tmp/test_scan_row_entry";
9453 let clock = Arc::new(MockSystemClock::new());
9454 let db = Db::builder(path, object_store)
9455 .with_settings(test_db_options(0, 1024, None))
9456 .with_system_clock(clock.clone())
9457 .build()
9458 .await
9459 .unwrap();
9460
9461 let put_opts = PutOptions {
9462 ttl: Ttl::ExpireAfter(50),
9463 };
9464 let write_opts = WriteOptions {
9465 await_durable: false,
9466 ..Default::default()
9467 };
9468
9469 clock.set(100);
9470 db.put_with_options(b"key1", b"value1", &put_opts, &write_opts)
9471 .await
9472 .unwrap();
9473
9474 clock.set(110);
9475 db.put_with_options(b"key2", b"value2", &put_opts, &write_opts)
9476 .await
9477 .unwrap();
9478
9479 clock.set(120);
9480 db.put_with_options(b"key3", b"value3", &put_opts, &write_opts)
9481 .await
9482 .unwrap();
9483
9484 let mut iter = db.scan(..).await.unwrap();
9485
9486 let row_entry1 = iter.next_entry().await.unwrap().unwrap();
9487 assert_eq!(row_entry1.key, Bytes::from_static(b"key1"));
9488 assert_eq!(
9489 row_entry1.value,
9490 ValueDeletable::Value(Bytes::from_static(b"value1"))
9491 );
9492 assert_eq!(row_entry1.seq, 1);
9493 assert_eq!(row_entry1.create_ts, Some(100));
9494 assert_eq!(row_entry1.expire_ts, Some(150));
9495
9496 let row_entry2 = iter.next_entry().await.unwrap().unwrap();
9497 assert_eq!(row_entry2.key, Bytes::from_static(b"key2"));
9498 assert_eq!(
9499 row_entry2.value,
9500 ValueDeletable::Value(Bytes::from_static(b"value2"))
9501 );
9502 assert_eq!(row_entry2.seq, 2);
9503 assert_eq!(row_entry2.create_ts, Some(110));
9504 assert_eq!(row_entry2.expire_ts, Some(160));
9505
9506 let row_entry3 = iter.next_entry().await.unwrap().unwrap();
9507 assert_eq!(row_entry3.key, Bytes::from_static(b"key3"));
9508 assert_eq!(
9509 row_entry3.value,
9510 ValueDeletable::Value(Bytes::from_static(b"value3"))
9511 );
9512 assert_eq!(row_entry3.seq, 3);
9513 assert_eq!(row_entry3.create_ts, Some(120));
9514 assert_eq!(row_entry3.expire_ts, Some(170));
9515
9516 assert!(iter.next_entry().await.unwrap().is_none());
9517 }
9518
9519 #[tokio::test]
9520 async fn should_get_key_value_with_expire_at() {
9521 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
9523 let path = "/tmp/test_get_key_value_expire_at";
9524 let clock = Arc::new(MockSystemClock::new());
9525 let db = Db::builder(path, object_store)
9526 .with_settings(test_db_options(0, 1024, None))
9527 .with_system_clock(clock.clone())
9528 .build()
9529 .await
9530 .unwrap();
9531
9532 clock.set(100);
9534 db.put_with_options(
9535 b"key1",
9536 b"value1",
9537 &PutOptions {
9538 ttl: Ttl::ExpireAt(500),
9539 },
9540 &WriteOptions {
9541 await_durable: false,
9542 ..Default::default()
9543 },
9544 )
9545 .await
9546 .unwrap();
9547
9548 clock.set(200);
9549 db.put_with_options(
9550 b"key2",
9551 b"value2",
9552 &PutOptions {
9553 ttl: Ttl::ExpireAt(500),
9554 },
9555 &WriteOptions {
9556 await_durable: false,
9557 ..Default::default()
9558 },
9559 )
9560 .await
9561 .unwrap();
9562
9563 let kv1 = db.get_key_value(b"key1").await.unwrap().unwrap();
9565 assert_eq!(kv1.expire_ts, Some(500));
9566 assert_eq!(kv1.create_ts, 100);
9567
9568 let kv2 = db.get_key_value(b"key2").await.unwrap().unwrap();
9569 assert_eq!(kv2.expire_ts, Some(500));
9570 assert_eq!(kv2.create_ts, 200);
9571 }
9572
9573 #[tokio::test]
9574 async fn test_should_record_scan_request_count() {
9575 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
9577 let metrics_recorder = Arc::new(DefaultMetricsRecorder::new());
9578 let db = Db::builder("/tmp/test_should_record_scan_request_count", object_store)
9579 .with_metrics_recorder(metrics_recorder.clone())
9580 .build()
9581 .await
9582 .unwrap();
9583 db.put(b"k1", b"v1").await.unwrap();
9584
9585 let mut iter = db.scan(..).await.unwrap();
9587 let _ = iter.next().await;
9588
9589 assert_eq!(
9591 lookup_metric_with_labels(
9592 &metrics_recorder,
9593 crate::db_stats::REQUEST_COUNT,
9594 &[("op", "scan")]
9595 ),
9596 Some(1)
9597 );
9598 db.close().await.unwrap();
9599 }
9600
9601 #[tokio::test]
9602 async fn test_should_record_flush_request_count() {
9603 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
9605 let metrics_recorder = Arc::new(DefaultMetricsRecorder::new());
9606 let db = Db::builder("/tmp/test_should_record_flush_request_count", object_store)
9607 .with_metrics_recorder(metrics_recorder.clone())
9608 .build()
9609 .await
9610 .unwrap();
9611 db.put(b"k1", b"v1").await.unwrap();
9612
9613 db.flush().await.unwrap();
9615
9616 assert_eq!(
9618 lookup_metric_with_labels(
9619 &metrics_recorder,
9620 crate::db_stats::REQUEST_COUNT,
9621 &[("op", "flush")]
9622 ),
9623 Some(1)
9624 );
9625 db.close().await.unwrap();
9626 }
9627
9628 #[tokio::test]
9629 async fn test_should_record_write_ops_and_batch_count() {
9630 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
9632 let metrics_recorder = Arc::new(DefaultMetricsRecorder::new());
9633 let db = Db::builder(
9634 "/tmp/test_should_record_write_ops_and_batch_count",
9635 object_store,
9636 )
9637 .with_metrics_recorder(metrics_recorder.clone())
9638 .build()
9639 .await
9640 .unwrap();
9641
9642 db.put(b"k1", b"v1").await.unwrap();
9644 db.put(b"k2", b"v2").await.unwrap();
9645
9646 assert_eq!(
9648 lookup_metric(&metrics_recorder, crate::db_stats::WRITE_OPS),
9649 Some(2)
9650 );
9651 assert_eq!(
9652 lookup_metric(&metrics_recorder, crate::db_stats::WRITE_BATCH_COUNT),
9653 Some(2)
9654 );
9655 db.close().await.unwrap();
9656 }
9657
9658 #[tokio::test]
9659 async fn test_should_record_merge_operator_operands_on_flush_path_during_batch_write() {
9660 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
9661 let metrics_recorder = Arc::new(DefaultMetricsRecorder::new());
9662 let path =
9663 "/tmp/test_should_record_merge_operator_operands_on_flush_path_during_batch_write";
9664 let mut options = test_db_options(0, 1024, None);
9665 options.flush_interval = None;
9666 options.max_unflushed_bytes = 1024 * 1024;
9667 let db = Db::builder(path, object_store.clone())
9668 .with_settings(options)
9669 .with_metrics_recorder(metrics_recorder.clone())
9670 .with_merge_operator(Arc::new(StringConcatMergeOperator))
9671 .build()
9672 .await
9673 .unwrap();
9674
9675 let mut batch = WriteBatch::new();
9676 batch.merge(b"key1", b"a");
9677 batch.merge(b"key1", b"b");
9678 db.write_with_options(
9679 batch,
9680 &WriteOptions {
9681 await_durable: false,
9682 ..Default::default()
9683 },
9684 )
9685 .await
9686 .unwrap();
9687
9688 assert_eq!(
9689 lookup_merge_operator_operands(&metrics_recorder, MERGE_OPERATOR_READ_PATH),
9690 Some(0)
9691 );
9692 assert_eq!(
9693 lookup_merge_operator_operands(&metrics_recorder, MERGE_OPERATOR_FLUSH_PATH),
9694 Some(3)
9695 );
9696 assert!(
9697 lookup_merge_operator_operands(&metrics_recorder, MERGE_OPERATOR_COMPACT_PATH)
9698 .is_none_or(|value| value == 0)
9699 );
9700
9701 db.close().await.unwrap();
9702 }
9703
9704 #[tokio::test]
9705 async fn should_reject_batch_local_merges_across_differing_ttls() {
9706 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
9707 let db = Db::builder(
9708 "/tmp/test_reject_batch_local_merges_across_differing_ttls",
9709 object_store,
9710 )
9711 .with_settings(test_db_options(0, 1024, None))
9712 .with_merge_operator(Arc::new(StringConcatMergeOperator))
9713 .build()
9714 .await
9715 .unwrap();
9716
9717 let mut batch = WriteBatch::new();
9718 batch.merge_with_options(
9719 b"key1",
9720 b"a",
9721 &MergeOptions {
9722 ttl: Ttl::ExpireAfter(3600),
9723 },
9724 );
9725 batch.merge_with_options(
9726 b"key1",
9727 b"b",
9728 &MergeOptions {
9729 ttl: Ttl::ExpireAfter(7200),
9730 },
9731 );
9732
9733 let err = db.write(batch).await.unwrap_err();
9734 assert_eq!(err.kind(), crate::ErrorKind::Invalid);
9735 assert!(
9736 err.to_string()
9737 .contains("only one merge TTL per-key allowed"),
9738 "unexpected error: {err}"
9739 );
9740 assert_eq!(db.get(b"key1").await.unwrap(), None);
9741
9742 db.close().await.unwrap();
9743 }
9744
9745 #[cfg(feature = "wal_disable")]
9746 #[tokio::test]
9747 async fn test_should_record_total_mem_size_bytes_with_wal_disabled() {
9748 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
9750 let metrics_recorder = Arc::new(DefaultMetricsRecorder::new());
9751 let mut opts = test_db_options(0, 1024, None);
9752 opts.flush_interval = None;
9753 opts.max_unflushed_bytes = 1024 * 1024;
9754 opts.wal_enabled = false;
9755 let db = Db::builder(
9756 "/tmp/test_should_record_total_mem_size_bytes_with_wal_disabled",
9757 object_store,
9758 )
9759 .with_settings(opts)
9760 .with_metrics_recorder(metrics_recorder.clone())
9761 .build()
9762 .await
9763 .unwrap();
9764
9765 let write_opts = WriteOptions {
9767 await_durable: false,
9768 ..Default::default()
9769 };
9770 db.put_with_options(b"k1", b"v1", &PutOptions::default(), &write_opts)
9771 .await
9772 .unwrap();
9773 db.put_with_options(b"k2", b"v2", &PutOptions::default(), &write_opts)
9774 .await
9775 .unwrap();
9776
9777 let mem_size = lookup_metric(&metrics_recorder, crate::db_stats::TOTAL_MEM_SIZE_BYTES);
9779 assert!(
9780 mem_size.is_some_and(|v| v > 0),
9781 "expected total_mem_size_bytes > 0 with WAL disabled, got {:?}",
9782 mem_size
9783 );
9784 db.close().await.unwrap();
9785 }
9786
9787 #[tokio::test]
9788 async fn test_should_record_total_mem_size_bytes() {
9789 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
9791 let metrics_recorder = Arc::new(DefaultMetricsRecorder::new());
9792 let mut opts = test_db_options(0, 1024, None);
9793 opts.flush_interval = None;
9794 opts.max_unflushed_bytes = 1024 * 1024;
9795 let db = Db::builder("/tmp/test_should_record_total_mem_size_bytes", object_store)
9796 .with_settings(opts)
9797 .with_metrics_recorder(metrics_recorder.clone())
9798 .build()
9799 .await
9800 .unwrap();
9801
9802 db.put_with_options(
9804 b"k1",
9805 b"v1",
9806 &PutOptions::default(),
9807 &WriteOptions {
9808 await_durable: false,
9809 ..Default::default()
9810 },
9811 )
9812 .await
9813 .unwrap();
9814 db.put_with_options(
9816 b"k2",
9817 b"v2",
9818 &PutOptions::default(),
9819 &WriteOptions {
9820 await_durable: false,
9821 ..Default::default()
9822 },
9823 )
9824 .await
9825 .unwrap();
9826
9827 let mem_size = lookup_metric(&metrics_recorder, crate::db_stats::TOTAL_MEM_SIZE_BYTES);
9829 assert!(
9830 mem_size.is_some_and(|v| v > 0),
9831 "expected total_mem_size_bytes > 0, got {:?}",
9832 mem_size
9833 );
9834 db.close().await.unwrap();
9835 }
9836
9837 #[tokio::test]
9838 async fn test_should_record_wal_buffer_estimated_bytes() {
9839 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
9841 let metrics_recorder = Arc::new(DefaultMetricsRecorder::new());
9842 let mut opts = test_db_options(0, 1024, None);
9843 opts.flush_interval = None;
9844 let db = Db::builder(
9845 "/tmp/test_should_record_wal_buffer_estimated_bytes",
9846 object_store,
9847 )
9848 .with_settings(opts)
9849 .with_metrics_recorder(metrics_recorder.clone())
9850 .build()
9851 .await
9852 .unwrap();
9853
9854 db.put_with_options(
9856 b"k1",
9857 b"v1",
9858 &PutOptions::default(),
9859 &WriteOptions {
9860 await_durable: false,
9861 ..Default::default()
9862 },
9863 )
9864 .await
9865 .unwrap();
9866
9867 let estimated = lookup_metric(
9869 &metrics_recorder,
9870 crate::wal_buffer::stats::WAL_BUFFER_ESTIMATED_BYTES,
9871 );
9872 assert!(
9873 estimated.is_some_and(|v| v > 0),
9874 "expected wal_buffer_estimated_bytes > 0, got {:?}",
9875 estimated
9876 );
9877 db.close().await.unwrap();
9878 }
9879
9880 #[tokio::test]
9881 async fn test_should_record_manifest_structural_counts() {
9882 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
9884 let metrics_recorder = Arc::new(DefaultMetricsRecorder::new());
9885 let db = Db::builder(
9886 "/tmp/test_should_record_manifest_structural_counts",
9887 object_store,
9888 )
9889 .with_settings(test_db_options(0, 1024, None))
9890 .with_metrics_recorder(metrics_recorder.clone())
9891 .build()
9892 .await
9893 .unwrap();
9894
9895 db.put(b"k1", b"v1").await.unwrap();
9897 db.flush_with_options(FlushOptions {
9898 flush_type: FlushType::MemTable,
9899 })
9900 .await
9901 .unwrap();
9902
9903 tokio::time::sleep(Duration::from_millis(500)).await;
9905
9906 let l0_count = lookup_metric(&metrics_recorder, crate::db_stats::L0_SST_COUNT);
9908 assert!(
9909 l0_count.is_some_and(|v| v > 0),
9910 "expected l0_sst_count > 0, got {:?}",
9911 l0_count
9912 );
9913 let segment_max =
9917 lookup_metric(&metrics_recorder, crate::db_stats::SEGMENT_MAX_L0_SST_COUNT);
9918 assert_eq!(
9919 segment_max, l0_count,
9920 "expected segment_max_l0_sst_count == l0_sst_count for an unsegmented DB"
9921 );
9922
9923 assert_eq!(
9926 lookup_metric(&metrics_recorder, crate::db_stats::SST_VIEW_COUNT),
9927 Some(1),
9928 "expected sst_view_count == 1 for a single flushed L0 SST"
9929 );
9930 assert_eq!(
9931 lookup_metric(&metrics_recorder, crate::db_stats::SST_COUNT),
9932 Some(1),
9933 "expected sst_count == 1 (one distinct physical SST) for a single flushed L0 SST"
9934 );
9935 assert_eq!(
9936 lookup_metric(&metrics_recorder, crate::db_stats::SORTED_RUN_COUNT),
9937 Some(0),
9938 "expected sorted_run_count == 0 before any compaction"
9939 );
9940 assert_eq!(
9941 lookup_metric(&metrics_recorder, crate::db_stats::EXTERNAL_DB_COUNT),
9942 Some(0),
9943 "expected external_db_count == 0 for a standalone DB"
9944 );
9945 db.close().await.unwrap();
9946 }
9947
9948 #[tokio::test]
9949 async fn test_should_record_segment_max_l0_sst_count_with_extractor() {
9950 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
9957 let metrics_recorder = Arc::new(DefaultMetricsRecorder::new());
9958 let extractor = Arc::new(test_utils::FixedThreeBytePrefixExtractor);
9959 let db = Db::builder(
9960 "/tmp/test_should_record_segment_max_l0_sst_count_with_extractor",
9961 object_store,
9962 )
9963 .with_settings(test_db_options(0, 1024, None))
9964 .with_segment_extractor(extractor)
9965 .with_metrics_recorder(metrics_recorder.clone())
9966 .build()
9967 .await
9968 .unwrap();
9969
9970 db.put(b"aaa-1", b"v").await.unwrap();
9972 db.put(b"bbb-1", b"v").await.unwrap();
9973 db.flush_with_options(FlushOptions {
9974 flush_type: FlushType::MemTable,
9975 })
9976 .await
9977 .unwrap();
9978
9979 db.put(b"aaa-2", b"v").await.unwrap();
9982 db.flush_with_options(FlushOptions {
9983 flush_type: FlushType::MemTable,
9984 })
9985 .await
9986 .unwrap();
9987
9988 tokio::time::sleep(Duration::from_millis(500)).await;
9990
9991 let total = lookup_metric(&metrics_recorder, crate::db_stats::L0_SST_COUNT);
9992 let segment_max =
9993 lookup_metric(&metrics_recorder, crate::db_stats::SEGMENT_MAX_L0_SST_COUNT);
9994 assert_eq!(
9995 total,
9996 Some(3),
9997 "expected l0_sst_count to sum across trees, got {:?}",
9998 total
9999 );
10000 assert_eq!(
10001 segment_max,
10002 Some(2),
10003 "expected segment_max_l0_sst_count to track the largest tree, got {:?}",
10004 segment_max
10005 );
10006 db.close().await.unwrap();
10007 }
10008
10009 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
10010 async fn test_wal_replay_l0_boundary_does_not_skip_unflushed_replay_batches() {
10011 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
10012 let path = "/tmp/test_wal_replay_l0_boundary_does_not_skip_unflushed_replay_batches";
10013
10014 let mut source_settings = test_db_options(0, 16 * 1024, None);
10017 source_settings.flush_interval = None;
10018
10019 let source = Db::builder(path, object_store.clone())
10020 .with_settings(source_settings)
10021 .build()
10022 .await
10023 .unwrap();
10024
10025 let write_opts = WriteOptions {
10026 await_durable: false,
10027 ..Default::default()
10028 };
10029
10030 let l0_flushed_records = [
10035 (b"l0-flushed-replay-batch-1".as_slice(), vec![b'a'; 128]),
10036 (b"l0-flushed-replay-batch-2".as_slice(), vec![b'b'; 1024]),
10037 ];
10038 let mut l0_flushed_seq = 0;
10039 let mut first_l0_flushed_wal_id = 0;
10040 for (i, (key, value)) in l0_flushed_records.iter().enumerate() {
10041 let write = source
10042 .put_with_options(*key, value, &PutOptions::default(), &write_opts)
10043 .await
10044 .unwrap();
10045 source
10046 .flush_with_options(FlushOptions {
10047 flush_type: FlushType::Wal,
10048 })
10049 .await
10050 .unwrap();
10051 if i == 0 {
10052 first_l0_flushed_wal_id = source
10053 .inner
10054 .wal_observer
10055 .status()
10056 .unwrap()
10057 .last_flushed_wal_id;
10058 }
10059 l0_flushed_seq = write.seqnum();
10060 }
10061 let l0_flushed_boundary_wal_id = source
10062 .inner
10063 .wal_observer
10064 .status()
10065 .unwrap()
10066 .last_flushed_wal_id;
10067 assert!(l0_flushed_boundary_wal_id > first_l0_flushed_wal_id);
10068
10069 let unflushed_replay_records = [
10074 (b"unflushed-replay-batch-1".as_slice(), vec![b'c'; 128]),
10075 (b"unflushed-replay-batch-2".as_slice(), vec![b'd'; 128]),
10076 (b"unflushed-replay-batch-3".as_slice(), vec![b'e'; 128]),
10077 ];
10078 for (key, value) in &unflushed_replay_records {
10079 source
10080 .put_with_options(*key, value, &PutOptions::default(), &write_opts)
10081 .await
10082 .unwrap();
10083 source
10084 .flush_with_options(FlushOptions {
10085 flush_type: FlushType::Wal,
10086 })
10087 .await
10088 .unwrap();
10089 }
10090 let final_source_wal_id = source
10091 .inner
10092 .wal_observer
10093 .status()
10094 .unwrap()
10095 .last_flushed_wal_id;
10096 assert!(final_source_wal_id >= l0_flushed_boundary_wal_id + 2);
10097
10098 let mut replay_settings = test_db_options(0, 512, None);
10102 replay_settings.flush_interval = None;
10103 replay_settings.l0_max_ssts = 1;
10104 replay_settings.l0_max_ssts_per_key = 1;
10105
10106 let _first_recovery = Db::builder(path, object_store.clone())
10109 .with_settings(replay_settings.clone())
10110 .build()
10111 .await
10112 .unwrap();
10113
10114 let manifest_store = Arc::new(ManifestStore::new(&Path::from(path), object_store.clone()));
10119 let mut stored_manifest =
10120 StoredManifest::load(manifest_store, Arc::new(DefaultSystemClock::new()))
10121 .await
10122 .unwrap();
10123 let first_l0_manifest = wait_for_manifest_condition(
10124 &mut stored_manifest,
10125 |state| !state.tree.l0.is_empty() && state.last_l0_seq >= l0_flushed_seq,
10126 Duration::from_secs(60),
10127 )
10128 .await;
10129 assert_eq!(first_l0_manifest.last_l0_seq, l0_flushed_seq);
10130 assert_eq!(
10131 first_l0_manifest.replay_after_wal_id,
10132 l0_flushed_boundary_wal_id
10133 );
10134 assert!(final_source_wal_id >= first_l0_manifest.replay_after_wal_id + 2);
10135 assert_eq!(first_l0_manifest.tree.l0.len(), 1);
10136
10137 let recovered = Db::builder(path, object_store.clone())
10142 .with_settings(replay_settings)
10143 .build()
10144 .await
10145 .unwrap();
10146
10147 for (key, value) in &l0_flushed_records {
10151 assert_eq!(
10152 recovered.get(*key).await.unwrap(),
10153 Some(Bytes::copy_from_slice(value))
10154 );
10155 }
10156 for (key, value) in &unflushed_replay_records {
10157 assert_eq!(
10158 recovered.get(*key).await.unwrap(),
10159 Some(Bytes::copy_from_slice(value))
10160 );
10161 }
10162 }
10163
10164 #[tokio::test(flavor = "multi_thread", worker_threads = 4)]
10165 async fn test_wal_replay_flushes_oversized_active_memtable_before_backpressure() {
10166 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
10167 let path = "/tmp/test_wal_replay_flushes_oversized_active_memtable";
10168
10169 let mut source_settings = test_db_options(0, 64 * 1024, None);
10173 source_settings.flush_interval = None;
10174 let source = Db::builder(path, object_store.clone())
10175 .with_settings(source_settings)
10176 .build()
10177 .await
10178 .unwrap();
10179 let value = vec![b'x'; 16 * 1024];
10180 source
10181 .put_with_options(
10182 b"oversized-replay-value",
10183 &value,
10184 &PutOptions::default(),
10185 &WriteOptions {
10186 await_durable: false,
10187 ..Default::default()
10188 },
10189 )
10190 .await
10191 .unwrap();
10192 source
10193 .flush_with_options(FlushOptions {
10194 flush_type: FlushType::Wal,
10195 })
10196 .await
10197 .unwrap();
10198
10199 let mut replay_settings = test_db_options(0, 1024, None);
10203 replay_settings.flush_interval = None;
10204 replay_settings.max_unflushed_bytes = 2 * 1024;
10205 let recovered = tokio::time::timeout(
10206 Duration::from_secs(5),
10207 Db::builder(path, object_store)
10208 .with_settings(replay_settings)
10209 .build(),
10210 )
10211 .await
10212 .expect("WAL replay deadlocked on an oversized active memtable")
10213 .expect("failed to recover database");
10214
10215 assert_eq!(
10216 recovered.get(b"oversized-replay-value").await.unwrap(),
10217 Some(Bytes::from(value))
10218 );
10219 recovered
10220 .put_with_options(
10221 b"write-after-replay",
10222 b"value",
10223 &PutOptions::default(),
10224 &WriteOptions {
10225 await_durable: false,
10226 ..Default::default()
10227 },
10228 )
10229 .await
10230 .expect("write after oversized WAL replay should succeed");
10231
10232 recovered.close().await.unwrap();
10233 }
10234
10235 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
10238 async fn test_wal_replay_with_extractor_preserves_keys() {
10239 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
10240 let path = "/tmp/test_wal_replay_with_extractor_preserves_keys";
10241
10242 let extractor = Arc::new(test_utils::FixedThreeBytePrefixExtractor);
10243 let mut settings = test_db_options(0, 16 * 1024, None);
10244 settings.flush_interval = None;
10245
10246 let source = Db::builder(path, object_store.clone())
10247 .with_settings(settings.clone())
10248 .with_segment_extractor(extractor.clone())
10249 .build()
10250 .await
10251 .unwrap();
10252 let write_opts = WriteOptions {
10253 await_durable: false,
10254 ..Default::default()
10255 };
10256 for (key, value) in [
10257 (b"aaa-1".as_slice(), b"v1".as_slice()),
10258 (b"bbb-1".as_slice(), b"v2".as_slice()),
10259 ] {
10260 source
10261 .put_with_options(key, value, &PutOptions::default(), &write_opts)
10262 .await
10263 .unwrap();
10264 }
10265 source
10266 .flush_with_options(FlushOptions {
10267 flush_type: FlushType::Wal,
10268 })
10269 .await
10270 .unwrap();
10271 drop(source);
10273
10274 let recovered = Db::builder(path, object_store.clone())
10275 .with_settings(settings)
10276 .with_segment_extractor(extractor)
10277 .build()
10278 .await
10279 .unwrap();
10280 assert_eq!(
10281 recovered.get(b"aaa-1").await.unwrap().unwrap().as_ref(),
10282 b"v1"
10283 );
10284 assert_eq!(
10285 recovered.get(b"bbb-1").await.unwrap().unwrap().as_ref(),
10286 b"v2"
10287 );
10288 recovered.close().await.unwrap();
10289 }
10290
10291 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
10299 async fn test_wal_replay_rejects_empty_extractor_prefix() {
10300 #[derive(Debug)]
10301 struct AliasedAlwaysEmptyExtractor;
10302 impl crate::PrefixExtractor for AliasedAlwaysEmptyExtractor {
10303 fn name(&self) -> &str {
10304 "fixed-3"
10305 }
10306 fn prefix_len(&self, _target: &crate::PrefixTarget) -> Option<usize> {
10307 Some(0)
10308 }
10309 }
10310
10311 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
10312 let path = "/tmp/test_wal_replay_rejects_empty_extractor_prefix";
10313
10314 let mut source_settings = test_db_options(0, 16 * 1024, None);
10317 source_settings.flush_interval = None;
10318 let source = Db::builder(path, object_store.clone())
10319 .with_settings(source_settings)
10320 .with_segment_extractor(Arc::new(test_utils::FixedThreeBytePrefixExtractor))
10321 .build()
10322 .await
10323 .unwrap();
10324 let write_opts = WriteOptions {
10325 await_durable: false,
10326 ..Default::default()
10327 };
10328 source
10329 .put_with_options(b"abc-1", b"v1", &PutOptions::default(), &write_opts)
10330 .await
10331 .unwrap();
10332 source
10333 .flush_with_options(FlushOptions {
10334 flush_type: FlushType::Wal,
10335 })
10336 .await
10337 .unwrap();
10338 drop(source);
10339
10340 let result = Db::builder(path, object_store)
10343 .with_settings(test_db_options(0, 16 * 1024, None))
10344 .with_segment_extractor(Arc::new(AliasedAlwaysEmptyExtractor))
10345 .build()
10346 .await;
10347 let err = match result {
10348 Ok(_) => panic!("expected empty-prefix rejection at replay, got Ok"),
10349 Err(e) => e,
10350 };
10351 assert!(matches!(err.kind(), crate::error::ErrorKind::Invalid));
10352 assert!(
10353 err.to_string().contains("empty prefix"),
10354 "expected empty-prefix error, got: {err}"
10355 );
10356 }
10357
10358 #[tokio::test]
10359 async fn should_report_new_memtable_segments_in_subscription() {
10360 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
10362 let path = "/tmp/test_subscribe_reports_memtable_segments";
10363 let mut settings = test_db_options(0, 16 * 1024, None);
10364 settings.flush_interval = None;
10365 let db = Db::builder(path, object_store.clone())
10366 .with_settings(settings)
10367 .with_segment_extractor(Arc::new(test_utils::FixedThreeBytePrefixExtractor))
10368 .build()
10369 .await
10370 .unwrap();
10371 let mut rx = db.subscribe();
10372 assert!(rx.borrow_and_update().list_segments().is_empty());
10373 let write_opts = WriteOptions {
10374 await_durable: false,
10375 ..Default::default()
10376 };
10377
10378 db.put_with_options(b"abc-1", b"v1", &PutOptions::default(), &write_opts)
10380 .await
10381 .unwrap();
10382
10383 rx.wait_for(|s| {
10385 s.list_segments()
10386 .iter()
10387 .any(|seg| seg.prefix.as_ref() == b"abc")
10388 })
10389 .await
10390 .unwrap();
10391
10392 db.put_with_options(b"xyz-1", b"v2", &PutOptions::default(), &write_opts)
10394 .await
10395 .unwrap();
10396
10397 rx.wait_for(|s| {
10399 s.list_segments()
10400 .into_iter()
10401 .map(|seg| seg.prefix)
10402 .collect::<Vec<_>>()
10403 == vec![Bytes::from_static(b"abc"), Bytes::from_static(b"xyz")]
10404 })
10405 .await
10406 .unwrap();
10407 }
10408
10409 #[tokio::test]
10410 async fn should_report_segments_in_manifest_after_flush() {
10411 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
10413 let path = "/tmp/test_report_segments_in_manifest_after_flush";
10414 let mut settings = test_db_options(0, 16 * 1024, None);
10415 settings.flush_interval = None;
10416 let db = Db::builder(path, object_store.clone())
10417 .with_settings(settings)
10418 .with_segment_extractor(Arc::new(test_utils::FixedThreeBytePrefixExtractor))
10419 .build()
10420 .await
10421 .unwrap();
10422 let mut rx = db.subscribe();
10423 rx.borrow_and_update();
10424
10425 db.put_with_options(
10427 b"abc-1",
10428 b"v1",
10429 &PutOptions::default(),
10430 &WriteOptions {
10431 await_durable: false,
10432 ..Default::default()
10433 },
10434 )
10435 .await
10436 .unwrap();
10437
10438 rx.wait_for(|s| {
10440 s.list_segments()
10441 .iter()
10442 .any(|seg| seg.prefix.as_ref() == b"abc")
10443 })
10444 .await
10445 .unwrap();
10446
10447 db.flush_with_options(FlushOptions {
10449 flush_type: FlushType::MemTable,
10450 })
10451 .await
10452 .unwrap();
10453
10454 rx.wait_for(|s| {
10456 s.list_segments()
10457 .into_iter()
10458 .map(|seg| seg.prefix)
10459 .collect::<Vec<_>>()
10460 == vec![Bytes::from_static(b"abc")]
10461 })
10462 .await
10463 .unwrap();
10464
10465 db.inner
10468 .state
10469 .write()
10470 .modify(|m| m.state.manifest.value.core.segments.clear());
10471 let manifest = db.inner.state.read().state().manifest.clone();
10472 db.inner.status_manager.report_manifest(manifest.into());
10473
10474 assert!(db.status().list_segments().is_empty());
10476 }
10477
10478 #[tokio::test]
10479 async fn should_not_report_segments_without_extractor() {
10480 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
10482 let path = "/tmp/test_no_segments_without_extractor";
10483 let mut settings = test_db_options(0, 16 * 1024, None);
10484 settings.flush_interval = None;
10485 let db = Db::builder(path, object_store.clone())
10486 .with_settings(settings)
10487 .build()
10488 .await
10489 .unwrap();
10490 let mut rx = db.subscribe();
10491 assert!(rx.borrow_and_update().list_segments().is_empty());
10492
10493 db.put_with_options(
10495 b"abc-1",
10496 b"v1",
10497 &PutOptions::default(),
10498 &WriteOptions {
10499 await_durable: false,
10500 ..Default::default()
10501 },
10502 )
10503 .await
10504 .unwrap();
10505 db.flush_with_options(FlushOptions {
10508 flush_type: FlushType::MemTable,
10509 })
10510 .await
10511 .unwrap();
10512
10513 assert!(db.status().list_segments().is_empty());
10515 }
10516
10517 #[derive(Clone, Copy, Debug)]
10518 enum ExtractorConfig {
10519 None,
10520 Fixed3,
10521 Other,
10522 }
10523
10524 impl ExtractorConfig {
10525 fn to_extractor(self) -> Option<Arc<dyn crate::PrefixExtractor>> {
10526 #[derive(Debug)]
10527 struct OtherExtractor;
10528 impl crate::PrefixExtractor for OtherExtractor {
10529 fn name(&self) -> &str {
10530 "other"
10531 }
10532 fn prefix_len(&self, _target: &crate::PrefixTarget) -> Option<usize> {
10533 Some(3)
10534 }
10535 }
10536 match self {
10537 ExtractorConfig::None => None,
10538 ExtractorConfig::Fixed3 => {
10539 Some(Arc::new(test_utils::FixedThreeBytePrefixExtractor))
10540 }
10541 ExtractorConfig::Other => Some(Arc::new(OtherExtractor)),
10542 }
10543 }
10544 }
10545
10546 #[rstest::rstest]
10552 #[case::no_extractor_round_trip(ExtractorConfig::None, ExtractorConfig::None, true)]
10553 #[case::same_extractor_round_trip(ExtractorConfig::Fixed3, ExtractorConfig::Fixed3, true)]
10554 #[case::name_mismatch(ExtractorConfig::Fixed3, ExtractorConfig::Other, false)]
10555 #[case::removed(ExtractorConfig::Fixed3, ExtractorConfig::None, false)]
10556 #[case::added(ExtractorConfig::None, ExtractorConfig::Fixed3, false)]
10557 #[tokio::test]
10558 async fn test_open_extractor_reconciliation(
10559 #[case] initial: ExtractorConfig,
10560 #[case] reopen: ExtractorConfig,
10561 #[case] expect_ok: bool,
10562 ) {
10563 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
10564 let path = format!("/tmp/test_open_extractor_reconciliation_{initial:?}_{reopen:?}");
10565
10566 let mut builder = Db::builder(path.clone(), object_store.clone());
10567 if let Some(extractor) = initial.to_extractor() {
10568 builder = builder.with_segment_extractor(extractor);
10569 }
10570 builder.build().await.unwrap().close().await.unwrap();
10571
10572 let mut builder = Db::builder(path, object_store);
10573 if let Some(extractor) = reopen.to_extractor() {
10574 builder = builder.with_segment_extractor(extractor);
10575 }
10576 match (expect_ok, builder.build().await) {
10577 (true, Ok(reopened)) => reopened.close().await.unwrap(),
10578 (true, Err(err)) => panic!("expected reopen to succeed, got {err:?}"),
10579 (false, Ok(_)) => panic!("expected reopen to fail"),
10580 (false, Err(err)) => {
10581 assert!(matches!(err.kind(), crate::error::ErrorKind::Invalid));
10582 }
10583 }
10584 }
10585
10586 #[tokio::test]
10592 async fn test_open_rejects_when_segment_prefix_unrecognized() {
10593 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
10594 let path = "/tmp/test_open_rejects_unrecognized_segment_prefix";
10595
10596 let db = Db::builder(path, object_store.clone())
10597 .with_segment_extractor(Arc::new(test_utils::FixedThreeBytePrefixExtractor))
10598 .build()
10599 .await
10600 .unwrap();
10601 db.put(b"abc-1", b"v1").await.unwrap();
10606 db.put(b"ab--1", b"v2").await.unwrap();
10607 db.close().await.unwrap();
10608
10609 let result = Db::builder(path, object_store)
10614 .with_segment_extractor(Arc::new(test_utils::AliasedFixed3PrefixExtractor))
10615 .build()
10616 .await;
10617 let err = match result {
10618 Ok(_) => panic!("expected unrecognized-prefix rejection, got Ok"),
10619 Err(e) => e,
10620 };
10621 assert!(matches!(err.kind(), crate::error::ErrorKind::Invalid));
10622 assert!(
10623 err.to_string().contains("not recognized"),
10624 "expected error to mention recognition, got: {err}"
10625 );
10626 }
10627
10628 fn segment_prefixes(db: &Db) -> Vec<Bytes> {
10631 let guard = db.inner.state.read();
10632 let cow = guard.state();
10633 cow.core()
10634 .segments
10635 .iter()
10636 .map(|s| s.prefix.clone())
10637 .collect()
10638 }
10639
10640 #[tokio::test]
10647 async fn test_open_with_extractor_and_wal_store_round_trips() {
10648 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
10649 let wal_object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
10650 let path = "/tmp/test_open_extractor_with_wal_store";
10651 let extractor = Arc::new(test_utils::FixedThreeBytePrefixExtractor);
10652
10653 let db = Db::builder(path, object_store.clone())
10654 .with_segment_extractor(extractor.clone())
10655 .with_wal_object_store(wal_object_store.clone())
10656 .build()
10657 .await
10658 .unwrap();
10659 db.close().await.unwrap();
10660
10661 let reopened = Db::builder(path, object_store)
10662 .with_segment_extractor(extractor)
10663 .with_wal_object_store(wal_object_store)
10664 .build()
10665 .await
10666 .unwrap();
10667 reopened.close().await.unwrap();
10668 }
10669
10670 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
10674 async fn test_segments_round_trip_through_flush_and_reopen() {
10675 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
10676 let path = "/tmp/test_segments_round_trip";
10677 let extractor = Arc::new(test_utils::FixedThreeBytePrefixExtractor);
10678
10679 let db = Db::builder(path, object_store.clone())
10680 .with_segment_extractor(extractor.clone())
10681 .build()
10682 .await
10683 .unwrap();
10684
10685 let entries: &[(&[u8], &[u8])] = &[
10686 (b"aaa-1", b"v1"),
10687 (b"aaa-2", b"v2"),
10688 (b"bbb-1", b"v3"),
10689 (b"ccc-1", b"v4"),
10690 (b"ccc-2", b"v5"),
10691 ];
10692 for (k, v) in entries {
10693 db.put(*k, *v).await.unwrap();
10694 }
10695 db.flush_with_options(FlushOptions {
10696 flush_type: FlushType::MemTable,
10697 })
10698 .await
10699 .unwrap();
10700
10701 let prefixes = segment_prefixes(&db);
10702 assert_eq!(
10703 prefixes,
10704 vec![
10705 Bytes::from_static(b"aaa"),
10706 Bytes::from_static(b"bbb"),
10707 Bytes::from_static(b"ccc"),
10708 ],
10709 "expected one segment per touched prefix, in sorted order"
10710 );
10711 db.close().await.unwrap();
10712
10713 let reopened = Db::builder(path, object_store)
10714 .with_segment_extractor(extractor)
10715 .build()
10716 .await
10717 .unwrap();
10718 for (k, v) in entries {
10719 assert_eq!(
10720 reopened.get(*k).await.unwrap().unwrap().as_ref(),
10721 *v,
10722 "round-trip mismatch for key {:?}",
10723 k
10724 );
10725 }
10726 assert_eq!(
10728 segment_prefixes(&reopened),
10729 vec![
10730 Bytes::from_static(b"aaa"),
10731 Bytes::from_static(b"bbb"),
10732 Bytes::from_static(b"ccc"),
10733 ]
10734 );
10735 reopened.close().await.unwrap();
10736 }
10737
10738 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
10742 async fn test_mixed_batch_publishes_atomically() {
10743 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
10744 let path = "/tmp/test_mixed_batch_atomic";
10745
10746 let db = Db::builder(path, object_store)
10747 .with_segment_extractor(Arc::new(test_utils::FixedThreeBytePrefixExtractor))
10748 .build()
10749 .await
10750 .unwrap();
10751 let mut batch = WriteBatch::new();
10752 batch.put(b"aaa-1", b"v1");
10753 batch.put(b"bbb-1", b"v2");
10754 batch.put(b"ccc-1", b"v3");
10755 db.write(batch).await.unwrap();
10756
10757 assert!(segment_prefixes(&db).is_empty());
10759
10760 db.flush_with_options(FlushOptions {
10761 flush_type: FlushType::MemTable,
10762 })
10763 .await
10764 .unwrap();
10765
10766 {
10768 let guard = db.inner.state.read();
10769 let cow = guard.state();
10770 let core = cow.core();
10771 assert_eq!(core.segments.len(), 3);
10772 for segment in &core.segments {
10773 assert_eq!(
10774 segment.tree.l0.len(),
10775 1,
10776 "expected exactly one L0 SST in segment {:?}, got {}",
10777 segment.prefix,
10778 segment.tree.l0.len()
10779 );
10780 assert!(
10781 segment.tree.compacted.is_empty(),
10782 "no compaction expected yet"
10783 );
10784 }
10785 }
10786 db.close().await.unwrap();
10787 }
10788
10789 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
10794 async fn test_replay_reconstructs_segments_from_wal() {
10795 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
10796 let path = "/tmp/test_replay_reconstructs_segments";
10797 let extractor = Arc::new(test_utils::FixedThreeBytePrefixExtractor);
10798
10799 let mut settings = test_db_options(0, 16 * 1024, None);
10800 settings.flush_interval = None;
10801
10802 let source = Db::builder(path, object_store.clone())
10803 .with_settings(settings.clone())
10804 .with_segment_extractor(extractor.clone())
10805 .build()
10806 .await
10807 .unwrap();
10808 let write_opts = WriteOptions {
10809 await_durable: false,
10810 ..Default::default()
10811 };
10812 for (k, v) in [(b"aaa-1".as_slice(), b"v1"), (b"bbb-1".as_slice(), b"v2")] {
10813 source
10814 .put_with_options(k, v, &PutOptions::default(), &write_opts)
10815 .await
10816 .unwrap();
10817 }
10818 source
10819 .flush_with_options(FlushOptions {
10820 flush_type: FlushType::Wal,
10821 })
10822 .await
10823 .unwrap();
10824 drop(source);
10826
10827 let recovered = Db::builder(path, object_store)
10829 .with_settings(settings)
10830 .with_segment_extractor(extractor)
10831 .build()
10832 .await
10833 .unwrap();
10834 assert!(
10835 segment_prefixes(&recovered).is_empty(),
10836 "replay should not stamp segments until the memtable flushes"
10837 );
10838 assert_eq!(
10840 recovered.get(b"aaa-1").await.unwrap().unwrap().as_ref(),
10841 b"v1"
10842 );
10843 assert_eq!(
10844 recovered.get(b"bbb-1").await.unwrap().unwrap().as_ref(),
10845 b"v2"
10846 );
10847
10848 recovered
10850 .flush_with_options(FlushOptions {
10851 flush_type: FlushType::MemTable,
10852 })
10853 .await
10854 .unwrap();
10855 assert_eq!(
10856 segment_prefixes(&recovered),
10857 vec![Bytes::from_static(b"aaa"), Bytes::from_static(b"bbb")]
10858 );
10859 recovered.close().await.unwrap();
10860 }
10861
10862 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
10866 async fn test_backfill_alongside_active_segment() {
10867 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
10868 let path = "/tmp/test_backfill_active";
10869
10870 let db = Db::builder(path, object_store)
10871 .with_segment_extractor(Arc::new(test_utils::FixedThreeBytePrefixExtractor))
10872 .build()
10873 .await
10874 .unwrap();
10875
10876 let mut expected: Vec<(Vec<u8>, Vec<u8>)> = Vec::new();
10880 for i in 0..20u32 {
10881 let cur_key = format!("cur-{i:03}").into_bytes();
10882 let cur_val = format!("c{i}").into_bytes();
10883 db.put(&cur_key, &cur_val).await.unwrap();
10884 expected.push((cur_key, cur_val));
10885 if i % 7 == 0 {
10886 let old_key = format!("old-{i:03}").into_bytes();
10887 let old_val = format!("o{i}").into_bytes();
10888 db.put(&old_key, &old_val).await.unwrap();
10889 expected.push((old_key, old_val));
10890 }
10891 }
10892 db.flush_with_options(FlushOptions {
10893 flush_type: FlushType::MemTable,
10894 })
10895 .await
10896 .unwrap();
10897
10898 assert_eq!(
10899 segment_prefixes(&db),
10900 vec![Bytes::from_static(b"cur"), Bytes::from_static(b"old")]
10901 );
10902 for (k, v) in &expected {
10903 assert_eq!(
10904 db.get(k).await.unwrap().unwrap().as_ref(),
10905 v.as_slice(),
10906 "missing value for {:?}",
10907 k
10908 );
10909 }
10910 db.close().await.unwrap();
10911 }
10912
10913 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
10917 async fn test_restart_resumes_writes_into_existing_segment() {
10918 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
10919 let path = "/tmp/test_restart_resume";
10920 let extractor = Arc::new(test_utils::FixedThreeBytePrefixExtractor);
10921
10922 let initial = Db::builder(path, object_store.clone())
10923 .with_segment_extractor(extractor.clone())
10924 .build()
10925 .await
10926 .unwrap();
10927 initial.put(b"aaa-1", b"v1").await.unwrap();
10928 initial.put(b"aaa-2", b"v2").await.unwrap();
10929 initial.close().await.unwrap();
10930
10931 let reopened = Db::builder(path, object_store)
10932 .with_segment_extractor(extractor)
10933 .build()
10934 .await
10935 .unwrap();
10936 assert_eq!(
10938 reopened.get(b"aaa-1").await.unwrap().unwrap().as_ref(),
10939 b"v1"
10940 );
10941 assert_eq!(
10942 reopened.get(b"aaa-2").await.unwrap().unwrap().as_ref(),
10943 b"v2"
10944 );
10945
10946 reopened.put(b"aaa-3", b"v3").await.unwrap();
10948 reopened.put(b"aaa-4", b"v4").await.unwrap();
10949 reopened
10950 .flush_with_options(FlushOptions {
10951 flush_type: FlushType::MemTable,
10952 })
10953 .await
10954 .unwrap();
10955
10956 let prefixes = segment_prefixes(&reopened);
10958 assert_eq!(prefixes, vec![Bytes::from_static(b"aaa")]);
10959 {
10960 let guard = reopened.inner.state.read();
10961 let cow = guard.state();
10962 let segment = &cow.core().segments[0];
10963 assert_eq!(
10964 segment.tree.l0.len(),
10965 2,
10966 "expected two L0 SSTs after the second flush, got {}",
10967 segment.tree.l0.len()
10968 );
10969 }
10970
10971 for (k, v) in [
10972 (b"aaa-1".as_slice(), b"v1".as_slice()),
10973 (b"aaa-2".as_slice(), b"v2".as_slice()),
10974 (b"aaa-3".as_slice(), b"v3".as_slice()),
10975 (b"aaa-4".as_slice(), b"v4".as_slice()),
10976 ] {
10977 assert_eq!(
10978 reopened.get(k).await.unwrap().unwrap().as_ref(),
10979 v,
10980 "missing value for {:?}",
10981 k
10982 );
10983 }
10984 reopened.close().await.unwrap();
10985 }
10986
10987 async fn create_segmented_scan_fixture(
10988 path: &str,
10989 object_store: Arc<dyn ObjectStore>,
10990 ) -> (Db, BTreeMap<Bytes, Bytes>) {
10991 let db = Db::builder(path, object_store)
10992 .with_segment_extractor(Arc::new(test_utils::FixedThreeBytePrefixExtractor))
10993 .build()
10994 .await
10995 .unwrap();
10996
10997 let table = BTreeMap::from([
10998 (Bytes::from_static(b"aaa-001"), Bytes::from_static(b"v1")),
10999 (Bytes::from_static(b"aaa-003"), Bytes::from_static(b"v2")),
11000 (Bytes::from_static(b"bbb-001"), Bytes::from_static(b"v3")),
11001 (Bytes::from_static(b"bbb-002"), Bytes::from_static(b"v4")),
11002 (Bytes::from_static(b"ddd-001"), Bytes::from_static(b"v5")),
11003 (Bytes::from_static(b"ddd-004"), Bytes::from_static(b"v6")),
11004 ]);
11005 test_utils::seed_database(&db, &table, true).await.unwrap();
11006 db.flush_with_options(FlushOptions {
11007 flush_type: FlushType::MemTable,
11008 })
11009 .await
11010 .unwrap();
11011
11012 (db, table)
11013 }
11014
11015 async fn assert_segmented_scan_matrix<R>(reader: &R, table: &BTreeMap<Bytes, Bytes>)
11016 where
11017 R: DbReadOps + Sync,
11018 {
11019 let mut prefix_iter = reader.scan_prefix(b"bbb", ..).await.unwrap();
11020 test_utils::assert_ranged_db_scan(
11021 table,
11022 Bytes::from_static(b"bbb")..Bytes::from_static(b"bbc"),
11023 IterationOrder::Ascending,
11024 &mut prefix_iter,
11025 )
11026 .await;
11027
11028 let mut subrange_iter = reader
11031 .scan_prefix(b"bbb", b"-002".as_slice()..)
11032 .await
11033 .unwrap();
11034 test_utils::assert_ranged_db_scan(
11035 table,
11036 Bytes::from_static(b"bbb-002")..Bytes::from_static(b"bbc"),
11037 IterationOrder::Ascending,
11038 &mut subrange_iter,
11039 )
11040 .await;
11041
11042 let mut subrange_iter = reader
11044 .scan_prefix(b"ddd", b"-001".as_slice()..b"-004".as_slice())
11045 .await
11046 .unwrap();
11047 test_utils::assert_ranged_db_scan(
11048 table,
11049 Bytes::from_static(b"ddd-001")..Bytes::from_static(b"ddd-004"),
11050 IterationOrder::Ascending,
11051 &mut subrange_iter,
11052 )
11053 .await;
11054
11055 let mut asc_iter = reader
11056 .scan(b"aaa".to_vec()..=b"ddd-999".to_vec())
11057 .await
11058 .unwrap();
11059 test_utils::assert_ranged_db_scan(
11060 table,
11061 Bytes::from_static(b"aaa")..=Bytes::from_static(b"ddd-999"),
11062 IterationOrder::Ascending,
11063 &mut asc_iter,
11064 )
11065 .await;
11066
11067 let desc_options = ScanOptions::default().with_order(IterationOrder::Descending);
11068 let mut desc_iter = reader
11069 .scan_with_options(b"aaa".to_vec()..=b"ddd-999".to_vec(), &desc_options)
11070 .await
11071 .unwrap();
11072 test_utils::assert_ranged_db_scan(
11073 table,
11074 Bytes::from_static(b"aaa")..=Bytes::from_static(b"ddd-999"),
11075 IterationOrder::Descending,
11076 &mut desc_iter,
11077 )
11078 .await;
11079
11080 let mut gap_iter = reader
11081 .scan(b"bbc".to_vec()..=b"ddd-002".to_vec())
11082 .await
11083 .unwrap();
11084 test_utils::assert_ranged_db_scan(
11085 table,
11086 Bytes::from_static(b"bbc")..=Bytes::from_static(b"ddd-002"),
11087 IterationOrder::Ascending,
11088 &mut gap_iter,
11089 )
11090 .await;
11091 }
11092
11093 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
11094 async fn test_segmented_scans_on_db() {
11095 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
11096 let (db, table) =
11097 create_segmented_scan_fixture("/tmp/test_segmented_scans_on_db", object_store).await;
11098
11099 assert_segmented_scan_matrix(&db, &table).await;
11100 db.close().await.unwrap();
11101 }
11102
11103 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
11104 async fn test_segmented_scans_on_snapshot() {
11105 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
11106 let (db, table) =
11107 create_segmented_scan_fixture("/tmp/test_segmented_scans_on_snapshot", object_store)
11108 .await;
11109
11110 let snapshot = db.snapshot().await.unwrap();
11111 assert_segmented_scan_matrix(snapshot.as_ref(), &table).await;
11112 drop(snapshot);
11113 db.close().await.unwrap();
11114 }
11115
11116 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
11117 async fn test_segmented_scans_on_db_reader() {
11118 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
11119 let path = "/tmp/test_segmented_scans_on_db_reader";
11120 let (db, table) = create_segmented_scan_fixture(path, object_store.clone()).await;
11121 db.close().await.unwrap();
11122
11123 let reader = DbReaderBuilder::new(path, object_store)
11124 .with_segment_extractor(Arc::new(test_utils::FixedThreeBytePrefixExtractor))
11125 .build()
11126 .await
11127 .unwrap();
11128 assert_segmented_scan_matrix(&reader, &table).await;
11129 }
11130
11131 #[tokio::test]
11132 async fn test_db_reader_cache_scoping() {
11133 use crate::db_cache::{DbCache, SplitCache};
11134
11135 let object_store_a: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
11137 let object_store_b: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
11138
11139 let db_a = Db::builder("/tmp/test_reader_cache_a", object_store_a.clone())
11141 .with_settings(test_db_options(0, 1024, None))
11142 .build()
11143 .await
11144 .unwrap();
11145 db_a.put(b"key1", b"value_from_db_a").await.unwrap();
11146 db_a.flush().await.unwrap();
11147 db_a.close().await.unwrap();
11148
11149 let db_b = Db::builder("/tmp/test_reader_cache_b", object_store_b.clone())
11150 .with_settings(test_db_options(0, 1024, None))
11151 .build()
11152 .await
11153 .unwrap();
11154 db_b.put(b"key1", b"value_from_db_b").await.unwrap();
11155 db_b.flush().await.unwrap();
11156 db_b.close().await.unwrap();
11157
11158 let shared_cache: Arc<dyn DbCache> = Arc::new(SplitCache::new().build());
11160
11161 let reader_a = DbReaderBuilder::new("/tmp/test_reader_cache_a", object_store_a)
11163 .with_db_cache(shared_cache.clone())
11164 .build()
11165 .await
11166 .unwrap();
11167
11168 let reader_b = DbReaderBuilder::new("/tmp/test_reader_cache_b", object_store_b)
11169 .with_db_cache(shared_cache.clone())
11170 .build()
11171 .await
11172 .unwrap();
11173
11174 let value_a = reader_a.get(b"key1").await.unwrap();
11176 assert_eq!(value_a, Some(Bytes::from("value_from_db_a")));
11177
11178 let value_b = reader_b.get(b"key1").await.unwrap();
11179 assert_eq!(value_b, Some(Bytes::from("value_from_db_b")));
11180
11181 let value_a_cached = reader_a.get(b"key1").await.unwrap();
11183 assert_eq!(value_a_cached, Some(Bytes::from("value_from_db_a")));
11184
11185 reader_a.close().await.unwrap();
11187
11188 let value_b_cached = reader_b.get(b"key1").await.unwrap();
11189 assert_eq!(value_b_cached, Some(Bytes::from("value_from_db_b")));
11190
11191 reader_b.close().await.unwrap();
11192 }
11193
11194 #[cfg(feature = "foyer")]
11195 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
11196 async fn test_close_does_not_kill_shared_hybrid_cache() {
11197 use crate::db_cache::foyer_hybrid::FoyerHybridCache;
11198 use crate::db_cache::{CachedEntry, CachedKey, DbCache};
11199 use crate::db_state::SsTableId;
11200 use crate::format::sst::BlockBuilder;
11201 use foyer::{
11202 BlockEngineConfig, DeviceBuilder, FsDeviceBuilder, HybridCacheBuilder,
11203 PsyncIoEngineConfig,
11204 };
11205
11206 fn probe_entry() -> CachedEntry {
11207 use rand::RngCore;
11208 let mut rng = rand::rng();
11209 let mut builder = BlockBuilder::new_latest(1024);
11210 loop {
11211 let mut k = vec![0u8; 32];
11212 rng.fill_bytes(&mut k);
11213 let mut v = vec![0u8; 128];
11214 rng.fill_bytes(&mut v);
11215 if builder.add_value(&k, &v, None, None) {
11216 break;
11217 }
11218 }
11219 CachedEntry::with_block(Arc::new(builder.build().unwrap()))
11220 }
11221
11222 async fn open_shared_cache(path: &std::path::Path) -> Arc<dyn DbCache> {
11223 let hybrid = HybridCacheBuilder::new()
11224 .with_name("shared_hybrid")
11225 .memory(1024 * 1024)
11226 .with_weighter(|_, v: &CachedEntry| v.size())
11227 .storage()
11228 .with_io_engine_config(PsyncIoEngineConfig::new())
11229 .with_engine_config(
11230 BlockEngineConfig::new(
11231 FsDeviceBuilder::new(path)
11232 .with_capacity(4 * 1024 * 1024)
11233 .build()
11234 .unwrap(),
11235 )
11236 .with_block_size(64 * 1024),
11237 )
11238 .build()
11239 .await
11240 .unwrap();
11241 Arc::new(FoyerHybridCache::new_with_cache(hybrid))
11242 }
11243
11244 let cache_dir = tempfile::tempdir().unwrap();
11245 let shared_cache = open_shared_cache(cache_dir.path()).await;
11246
11247 let object_store_a: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
11248 let object_store_b: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
11249
11250 let db_a = Db::builder("/tmp/test_shared_hybrid_a", object_store_a)
11251 .with_settings(test_db_options(0, 1024, None))
11252 .with_db_cache(shared_cache.clone())
11253 .build()
11254 .await
11255 .unwrap();
11256 let db_b = Db::builder("/tmp/test_shared_hybrid_b", object_store_b)
11257 .with_settings(test_db_options(0, 1024, None))
11258 .with_db_cache(shared_cache.clone())
11259 .build()
11260 .await
11261 .unwrap();
11262
11263 db_a.put(b"key1", b"value_from_db_a").await.unwrap();
11264 db_a.flush().await.unwrap();
11265 db_b.put(b"key1", b"value_from_db_b").await.unwrap();
11266 db_b.flush().await.unwrap();
11267
11268 db_a.close().await.unwrap();
11270
11271 let value_b = db_b.get(b"key1").await.unwrap();
11273 assert_eq!(value_b, Some(Bytes::from("value_from_db_b")));
11274
11275 let mut keys = Vec::new();
11283 for b in 0u64..64 {
11284 let k = CachedKey::from((SsTableId::Wal(u64::MAX - 2), b));
11285 shared_cache.insert(k.clone(), probe_entry()).await;
11286 keys.push(k);
11287 }
11288
11289 db_b.close().await.unwrap();
11290 shared_cache.close().await.unwrap();
11293 drop(db_a);
11294 drop(db_b);
11295 drop(shared_cache);
11296
11297 let reopened = open_shared_cache(cache_dir.path()).await;
11298 let mut found = 0;
11299 for k in &keys {
11300 if reopened.get_block(k).await.unwrap().is_some() {
11301 found += 1;
11302 }
11303 }
11304 assert_eq!(
11305 found,
11306 keys.len(),
11307 "entries cached after another DB closed the shared cache were lost \
11308 ({}/{} survived cache close + reopen)",
11309 found,
11310 keys.len()
11311 );
11312 }
11313
11314 mod object_store_cache {
11315 use super::*;
11316 use crate::cached_object_store::stats::{PART_ACCESS_COUNT, PART_HIT_COUNT};
11317 use crate::cached_object_store::CachedObjectStore;
11318 use object_store::ObjectStoreExt;
11319
11320 struct ObjectStoreCacheTest {
11322 db: Db,
11323 upstream: Arc<dyn ObjectStore>,
11324 cache: Option<Arc<CachedObjectStore>>,
11327 cache_root: std::path::PathBuf,
11328 db_path: String,
11329 should_compact: Option<Arc<AtomicBool>>,
11330 }
11331
11332 struct ObjectStoreCacheTestBuilder {
11335 db_path: String,
11336 object_store_cache: bool,
11337 cache_on_flush: bool,
11338 cache_on_compaction: bool,
11339 part_size: usize,
11340 l0_sst_size_bytes: usize,
11341 on_demand_compactor: bool,
11342 custom_compactor_store: bool,
11343 metrics_recorder: Option<Arc<DefaultMetricsRecorder>>,
11344 }
11345
11346 impl ObjectStoreCacheTestBuilder {
11347 fn new(db_path: &str) -> Self {
11348 Self {
11349 db_path: db_path.to_string(),
11350 object_store_cache: true,
11351 cache_on_flush: false,
11352 cache_on_compaction: false,
11353 part_size: 1024,
11354 l0_sst_size_bytes: 1024,
11355 on_demand_compactor: false,
11356 custom_compactor_store: false,
11357 metrics_recorder: None,
11358 }
11359 }
11360
11361 fn without_object_store_cache(mut self) -> Self {
11363 self.object_store_cache = false;
11364 self
11365 }
11366
11367 fn metrics_recorder(mut self, recorder: Arc<DefaultMetricsRecorder>) -> Self {
11368 self.metrics_recorder = Some(recorder);
11369 self
11370 }
11371
11372 fn cache_on_flush(mut self) -> Self {
11373 self.cache_on_flush = true;
11374 self
11375 }
11376
11377 fn cache_on_compaction(mut self) -> Self {
11378 self.cache_on_compaction = true;
11379 self
11380 }
11381
11382 fn part_size(mut self, part_size: usize) -> Self {
11383 self.part_size = part_size;
11384 self
11385 }
11386
11387 fn l0_sst_size_bytes(mut self, bytes: usize) -> Self {
11388 self.l0_sst_size_bytes = bytes;
11389 self
11390 }
11391
11392 fn on_demand_compactor(mut self) -> Self {
11395 self.on_demand_compactor = true;
11396 self
11397 }
11398
11399 fn on_demand_compactor_with_custom_store(mut self) -> Self {
11402 self.on_demand_compactor = true;
11403 self.custom_compactor_store = true;
11404 self
11405 }
11406
11407 async fn build(self) -> ObjectStoreCacheTest {
11408 let Self {
11409 db_path,
11410 object_store_cache,
11411 cache_on_flush,
11412 cache_on_compaction,
11413 part_size,
11414 l0_sst_size_bytes,
11415 on_demand_compactor,
11416 custom_compactor_store,
11417 metrics_recorder,
11418 } = self;
11419
11420 let upstream: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
11421 let temp_dir = tempfile::Builder::new()
11422 .prefix("objstore_cache_test_")
11423 .tempdir()
11424 .unwrap();
11425 let cache_root = temp_dir.keep();
11426
11427 let opts = test_db_options(0, l0_sst_size_bytes, None);
11428
11429 let cache = if object_store_cache {
11432 Some(
11433 CachedObjectStore::builder(cache_root.clone(), upstream.clone())
11434 .with_part_size_bytes(part_size)
11435 .with_cache_on_flush(cache_on_flush)
11436 .with_cache_on_compaction(cache_on_compaction)
11437 .build()
11438 .await
11439 .unwrap(),
11440 )
11441 } else {
11442 None
11443 };
11444 let main_store: Arc<dyn ObjectStore> = match &cache {
11445 Some(cache) => cache.clone(),
11446 None => upstream.clone(),
11447 };
11448
11449 let mut builder =
11450 Db::builder(db_path.as_str(), main_store.clone()).with_settings(opts);
11451 if let Some(recorder) = metrics_recorder {
11452 builder = builder.with_metrics_recorder(recorder);
11453 }
11454 let should_compact = if on_demand_compactor {
11455 let flag = Arc::new(AtomicBool::new(false));
11456 let flag_clone = flag.clone();
11457 let scheduler = Arc::new(OnDemandCompactionSchedulerSupplier::new(Arc::new(
11458 move |_state| flag_clone.swap(false, Ordering::SeqCst),
11459 )));
11460 let compactor_store: Arc<dyn ObjectStore> = if custom_compactor_store {
11465 Arc::new(GatedObjectStore::new(upstream.clone()))
11466 } else {
11467 main_store.clone()
11468 };
11469 let mut compactor_options = fast_compactor_options();
11472 if let Some(worker) = compactor_options.worker.as_mut() {
11473 worker.max_subcompactions = 1;
11474 }
11475 builder = builder.with_compactor_builder(
11476 CompactorBuilder::new(db_path.as_str(), compactor_store)
11477 .with_scheduler_supplier(scheduler)
11478 .with_options(compactor_options),
11479 );
11480 Some(flag)
11481 } else {
11482 None
11483 };
11484 let db = builder.build().await.unwrap();
11485
11486 ObjectStoreCacheTest {
11487 db,
11488 upstream,
11489 cache,
11490 cache_root,
11491 db_path,
11492 should_compact,
11493 }
11494 }
11495 }
11496
11497 impl ObjectStoreCacheTest {
11498 fn builder(db_path: &str) -> ObjectStoreCacheTestBuilder {
11499 ObjectStoreCacheTestBuilder::new(db_path)
11500 }
11501
11502 fn db(&self) -> &Db {
11503 &self.db
11504 }
11505
11506 fn sub_path(&self, suffix: &str) -> object_store::path::Path {
11508 object_store::path::Path::from(format!("{}/{}", self.db_path, suffix))
11509 }
11510
11511 fn cached_part_count(&self, path: &object_store::path::Path) -> usize {
11513 let dir = self.cache_root.join(path.to_string());
11514 let Ok(entries) = std::fs::read_dir(dir) else {
11515 return 0;
11516 };
11517 entries
11518 .filter(|e| {
11519 e.as_ref()
11520 .unwrap()
11521 .file_name()
11522 .to_string_lossy()
11523 .starts_with("_part")
11524 })
11525 .count()
11526 }
11527
11528 fn assert_cached(&self, path: &object_store::path::Path, expected_parts: usize) {
11529 assert_eq!(
11530 self.cached_part_count(path),
11531 expected_parts,
11532 "expected {path} to be cached as {expected_parts} part(s)"
11533 );
11534 }
11535
11536 fn assert_uncached(&self, suffixes: &[&str]) {
11538 for suffix in suffixes {
11539 let path = self.sub_path(suffix);
11540 assert_eq!(
11541 self.cached_part_count(&path),
11542 0,
11543 "expected {suffix} to be uncached"
11544 );
11545 }
11546 }
11547
11548 async fn compacted_locations(&self) -> Vec<object_store::path::Path> {
11550 let prefix = self.sub_path("compacted");
11551 self.upstream
11552 .list(Some(&prefix))
11553 .map(|meta| meta.unwrap().location)
11554 .collect()
11555 .await
11556 }
11557
11558 async fn object_size(&self, path: &object_store::path::Path) -> u64 {
11560 self.upstream.head(path).await.unwrap().size
11561 }
11562
11563 fn compacted_sst_path(&self, id: &SsTableId) -> object_store::path::Path {
11565 crate::paths::PathResolver::from_root(self.db_path.as_str()).sst_path(id)
11566 }
11567
11568 fn l0_ids(&self) -> Vec<SsTableId> {
11569 self.db.manifest().l0().iter().map(|v| v.sst.id).collect()
11570 }
11571
11572 async fn compact_and_wait(&self) {
11575 self.should_compact
11576 .as_ref()
11577 .expect("fixture built without on_demand_compactor")
11578 .store(true, Ordering::SeqCst);
11579 tokio::time::timeout(Duration::from_secs(30), async {
11580 loop {
11581 if !self.db.manifest().compacted().is_empty() {
11582 return;
11583 }
11584 tokio::time::sleep(Duration::from_millis(10)).await;
11585 }
11586 })
11587 .await
11588 .expect("compaction did not land within timeout");
11589 }
11590
11591 fn compaction_output_ids(&self, l0_ids: &[SsTableId]) -> Vec<SsTableId> {
11594 self.db
11595 .manifest()
11596 .compacted()
11597 .iter()
11598 .flat_map(|sr| sr.sst_views.iter())
11599 .map(|v| v.sst.id)
11600 .filter(|id| !l0_ids.contains(id))
11601 .collect()
11602 }
11603
11604 async fn close(self) {
11605 self.db.close().await.unwrap();
11606 }
11607 }
11608
11609 #[tokio::test]
11610 async fn test_get_with_object_store_cache_metrics() {
11611 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
11612 let mut opts = test_db_options(0, 1024, None);
11613 let temp_dir = tempfile::Builder::new()
11614 .prefix("objstore_cache_test_")
11615 .tempdir()
11616 .unwrap();
11617
11618 opts.object_store_cache_options.root_folder = Some(temp_dir.keep());
11619 opts.object_store_cache_options.part_size_bytes = 1024;
11620 let metrics_recorder = Arc::new(DefaultMetricsRecorder::new());
11621 let kv_store = Db::builder(
11622 "/tmp/test_kv_store_with_cache_metrics",
11623 object_store.clone(),
11624 )
11625 .with_settings(opts)
11626 .with_db_cache_disabled()
11627 .with_metrics_recorder(metrics_recorder.clone())
11628 .build()
11629 .await
11630 .unwrap();
11631
11632 let access_count0 = lookup_metric(&metrics_recorder, PART_ACCESS_COUNT).unwrap();
11633 let key = b"test_key";
11634 let value = b"test_value";
11635 kv_store.put(key, value).await.unwrap();
11636 kv_store
11637 .flush_with_options(FlushOptions {
11638 flush_type: FlushType::MemTable,
11639 })
11640 .await
11641 .unwrap();
11642
11643 let val = kv_store.get(key).await.unwrap();
11649 assert_eq!(val, Some(Bytes::from_static(value)));
11650 let access_count1 = lookup_metric(&metrics_recorder, PART_ACCESS_COUNT).unwrap();
11651 let hit_count1 = lookup_metric(&metrics_recorder, PART_HIT_COUNT).unwrap();
11652 assert_eq!(
11653 access_count1 - access_count0,
11654 3,
11655 "one point get reads the single-part SST in three sub-ranges"
11656 );
11657 assert_eq!(
11658 hit_count1, 2,
11659 "the cold read is a miss; the next two reads hit the warm cache"
11660 );
11661
11662 let got = kv_store.get(key).await.unwrap();
11664 assert_eq!(got, Some(Bytes::from_static(value)));
11665 let access_count2 = lookup_metric(&metrics_recorder, PART_ACCESS_COUNT).unwrap();
11666 let hit_count2 = lookup_metric(&metrics_recorder, PART_HIT_COUNT).unwrap();
11667 assert_eq!(
11668 access_count2 - access_count1,
11669 3,
11670 "the second get reads the same three sub-ranges"
11671 );
11672 assert_eq!(
11673 hit_count2 - hit_count1,
11674 3,
11675 "every read in the warm get is a cache hit"
11676 );
11677 }
11678
11679 #[tokio::test]
11680 async fn test_db_records_read_calls_into_cached_object_store() {
11681 let object_store: Arc<dyn ObjectStore> = Arc::new(InMemory::new());
11682 let mut opts = test_db_options(0, 1024, None);
11683 let temp_dir = tempfile::Builder::new()
11684 .prefix("objstore_metrics_test_")
11685 .tempdir()
11686 .unwrap();
11687
11688 opts.manifest_poll_interval = Duration::from_secs(3600);
11689 let metrics_recorder = Arc::new(DefaultMetricsRecorder::new());
11690 let path = "/tmp/test_db_records_read_calls_into_cached_object_store";
11691 let cached_store = CachedObjectStore::builder(temp_dir.keep(), object_store)
11692 .with_part_size_bytes(1024)
11693 .build()
11694 .await
11695 .unwrap();
11696 let kv_store = Db::builder(path, cached_store)
11700 .with_settings(opts)
11701 .with_db_cache_disabled()
11702 .with_metrics_recorder(metrics_recorder.clone())
11703 .build()
11704 .await
11705 .unwrap();
11706
11707 kv_store.put(b"test_key", b"test_value").await.unwrap();
11708 kv_store.flush().await.unwrap();
11709 kv_store
11710 .flush_with_options(FlushOptions {
11711 flush_type: FlushType::MemTable,
11712 })
11713 .await
11714 .unwrap();
11715
11716 let requests_before =
11717 lookup_object_store_op_request_count(&metrics_recorder, "db", "main", "get");
11718 let _val = kv_store.get(b"test_key").await.unwrap();
11719 let requests_after_first =
11720 lookup_object_store_op_request_count(&metrics_recorder, "db", "main", "get");
11721 let got = kv_store.get(b"test_key").await.unwrap();
11722 let requests_after_second =
11723 lookup_object_store_op_request_count(&metrics_recorder, "db", "main", "get");
11724
11725 assert_eq!(requests_after_first, requests_before + 3);
11731 assert_eq!(got, Some(Bytes::from_static(b"test_value")));
11732 assert_eq!(requests_after_second, requests_after_first + 3);
11733 assert_eq!(
11734 lookup_object_store_op_histogram_count(&metrics_recorder, "db", "main", "get"),
11735 requests_after_second as u64
11736 );
11737 kv_store.close().await.unwrap();
11738 }
11739
11740 #[tokio::test]
11744 async fn test_preload_disk_cache_from_manifest() {
11745 let fixture =
11746 ObjectStoreCacheTest::builder("/tmp/test_preload_disk_cache_from_manifest")
11747 .build()
11748 .await;
11749
11750 for (key, value) in [(b"k1", b"v1"), (b"k2", b"v2")] {
11752 fixture.db().put(key, value).await.unwrap();
11753 fixture.db().flush().await.unwrap();
11754 fixture
11755 .db()
11756 .flush_with_options(FlushOptions {
11757 flush_type: FlushType::MemTable,
11758 })
11759 .await
11760 .unwrap();
11761 }
11762
11763 let ids = fixture.l0_ids();
11764 assert_eq!(ids.len(), 2);
11765 let paths: Vec<_> = ids
11766 .iter()
11767 .map(|id| fixture.compacted_sst_path(id))
11768 .collect();
11769 for path in &paths {
11770 fixture.assert_cached(path, 0);
11771 }
11772
11773 let cache = fixture.cache.as_ref().unwrap();
11774 cache
11775 .load_files_to_cache(paths.clone(), usize::MAX)
11776 .await
11777 .unwrap();
11778
11779 for path in &paths {
11781 fixture.assert_cached(path, 1);
11782 }
11783 fixture.close().await;
11784 }
11785
11786 #[tokio::test]
11790 async fn test_object_store_cache_caches_flushed_sst_only() {
11791 let fixture = ObjectStoreCacheTest::builder("/tmp/test_object_store_cache_flush_only")
11792 .cache_on_flush()
11793 .build()
11794 .await;
11795
11796 fixture.db().put(b"test_key", b"test_value").await.unwrap();
11799 fixture.db().flush().await.unwrap();
11800 fixture
11801 .db()
11802 .flush_with_options(FlushOptions {
11803 flush_type: FlushType::MemTable,
11804 })
11805 .await
11806 .unwrap();
11807
11808 fixture.assert_uncached(&[
11809 "manifest/00000000000000000001.manifest",
11810 "manifest/00000000000000000002.manifest",
11811 "wal/00000000000000000001.sst",
11812 "wal/00000000000000000002.sst",
11813 ]);
11814
11815 let compacted = fixture.compacted_locations().await;
11818 assert_eq!(compacted.len(), 1, "expected exactly one flushed SST");
11819 fixture.assert_cached(&compacted[0], 1);
11820 fixture.close().await;
11821 }
11822
11823 #[tokio::test]
11827 async fn test_object_store_cache_caches_large_multipart_flush() {
11828 const MIB: usize = 1024 * 1024;
11829
11830 let fixture = ObjectStoreCacheTest::builder("/tmp/test_object_store_cache_large_flush")
11831 .cache_on_flush()
11832 .part_size(MIB)
11833 .l0_sst_size_bytes(64 * MIB)
11835 .build()
11836 .await;
11837
11838 for i in 0..20u32 {
11841 let key = format!("k{:04}", i);
11842 fixture
11843 .db()
11844 .put(key.as_bytes(), &vec![i as u8; MIB])
11845 .await
11846 .unwrap();
11847 }
11848 fixture
11849 .db()
11850 .flush_with_options(FlushOptions {
11851 flush_type: FlushType::MemTable,
11852 })
11853 .await
11854 .unwrap();
11855
11856 let compacted = fixture.compacted_locations().await;
11857 assert_eq!(compacted.len(), 1, "expected exactly one flushed SST");
11858 let expected_parts = (fixture.object_size(&compacted[0]).await as usize).div_ceil(MIB);
11861 assert!(
11862 expected_parts > 10,
11863 "expected a large multipart SST, got {expected_parts} part(s)"
11864 );
11865 fixture.assert_cached(&compacted[0], expected_parts);
11866 fixture.close().await;
11867 }
11868
11869 #[tokio::test]
11872 async fn test_object_store_cache_caches_compaction_output() {
11873 let t = ObjectStoreCacheTest::builder("/tmp/test_object_store_cache_compaction_output")
11874 .cache_on_compaction()
11875 .on_demand_compactor()
11876 .build()
11877 .await;
11878
11879 for i in 0..2u32 {
11880 let key = format!("key{:04}", i);
11881 t.db().put(key.as_bytes(), &[b'v'; 64]).await.unwrap();
11882 t.db()
11883 .flush_with_options(FlushOptions {
11884 flush_type: FlushType::MemTable,
11885 })
11886 .await
11887 .unwrap();
11888 }
11889 let l0_ids = t.l0_ids();
11890 assert_eq!(l0_ids.len(), 2);
11891
11892 t.compact_and_wait().await;
11893
11894 for id in &l0_ids {
11895 t.assert_cached(&t.compacted_sst_path(id), 0);
11896 }
11897
11898 let output_ids = t.compaction_output_ids(&l0_ids);
11899 assert!(!output_ids.is_empty(), "expected compaction output SSTs");
11900 for id in &output_ids {
11901 let path = t.compacted_sst_path(id);
11902 assert!(
11903 t.cached_part_count(&path) > 0,
11904 "expected compaction output {path} to be cached"
11905 );
11906 }
11907 t.close().await;
11908 }
11909
11910 #[tokio::test]
11912 async fn test_object_store_cache_caches_large_multipart_compaction_output() {
11913 const MIB: usize = 1024 * 1024;
11914
11915 let t = ObjectStoreCacheTest::builder(
11916 "/tmp/test_object_store_cache_large_compaction_output",
11917 )
11918 .cache_on_compaction()
11919 .on_demand_compactor()
11920 .part_size(MIB)
11921 .l0_sst_size_bytes(64 * MIB)
11923 .build()
11924 .await;
11925
11926 for sst in 0..2u32 {
11928 for i in 0..10u32 {
11929 let key = format!("k{:04}", sst * 10 + i);
11930 t.db()
11931 .put(key.as_bytes(), &vec![i as u8; MIB])
11932 .await
11933 .unwrap();
11934 }
11935 t.db()
11936 .flush_with_options(FlushOptions {
11937 flush_type: FlushType::MemTable,
11938 })
11939 .await
11940 .unwrap();
11941 }
11942 let l0_ids = t.l0_ids();
11943 assert_eq!(l0_ids.len(), 2);
11944
11945 t.compact_and_wait().await;
11946
11947 let output_ids = t.compaction_output_ids(&l0_ids);
11948 assert_eq!(output_ids.len(), 1, "expected one output SST");
11949 let path = t.compacted_sst_path(&output_ids[0]);
11950 let expected_parts = (t.object_size(&path).await as usize).div_ceil(MIB);
11951 assert_eq!(
11952 expected_parts, 21,
11953 "update this count if an SST encoding change shifts the size"
11954 );
11955 t.assert_cached(&path, expected_parts);
11956 t.close().await;
11957 }
11958
11959 #[tokio::test]
11962 async fn test_object_store_cache_skips_compaction_output_from_custom_store() {
11963 let t = ObjectStoreCacheTest::builder(
11964 "/tmp/test_object_store_cache_custom_compactor_store",
11965 )
11966 .cache_on_compaction()
11967 .on_demand_compactor_with_custom_store()
11968 .build()
11969 .await;
11970
11971 for i in 0..2u32 {
11972 let key = format!("key{:04}", i);
11973 t.db().put(key.as_bytes(), &[b'v'; 64]).await.unwrap();
11974 t.db()
11975 .flush_with_options(FlushOptions {
11976 flush_type: FlushType::MemTable,
11977 })
11978 .await
11979 .unwrap();
11980 }
11981 let l0_ids = t.l0_ids();
11982 assert_eq!(l0_ids.len(), 2);
11983
11984 t.compact_and_wait().await;
11985
11986 let output_ids = t.compaction_output_ids(&l0_ids);
11987 assert!(!output_ids.is_empty(), "expected compaction output SSTs");
11988 for id in &output_ids {
11989 let path = t.compacted_sst_path(id);
11990 assert_eq!(
11991 t.cached_part_count(&path),
11992 0,
11993 "expected compaction output {path} to stay uncached"
11994 );
11995 }
11996 t.close().await;
11997 }
11998
11999 #[tokio::test]
12003 async fn test_embedded_compactor_io_recorded_under_compactor_component() {
12004 for object_store_cache in [true, false] {
12005 let recorder = Arc::new(DefaultMetricsRecorder::new());
12006 let mut builder =
12007 ObjectStoreCacheTest::builder("/tmp/test_compactor_component_metrics")
12008 .cache_on_compaction()
12009 .on_demand_compactor()
12010 .metrics_recorder(recorder.clone());
12011 if !object_store_cache {
12012 builder = builder.without_object_store_cache();
12013 }
12014 let t = builder.build().await;
12015
12016 for i in 0..2u32 {
12017 let key = format!("key{:04}", i);
12018 t.db().put(key.as_bytes(), &[b'v'; 64]).await.unwrap();
12019 t.db()
12020 .flush_with_options(FlushOptions {
12021 flush_type: FlushType::MemTable,
12022 })
12023 .await
12024 .unwrap();
12025 }
12026 t.compact_and_wait().await;
12027
12028 let gets =
12029 lookup_object_store_op_request_count(&recorder, "compactor", "main", "get");
12030 let puts =
12031 lookup_object_store_op_request_count(&recorder, "compactor", "main", "put");
12032 assert!(gets > 0, "no compactor gets [cache={object_store_cache}]");
12033 assert!(puts > 0, "no compactor puts [cache={object_store_cache}]");
12034 t.close().await;
12035 }
12036 }
12037 }
12038}