1use std::{
2 cell::SyncUnsafeCell,
3 io::Write,
4 mem::{replace, take},
5 path::PathBuf,
6 sync::atomic::{AtomicU32, AtomicU64, Ordering},
7};
8
9use anyhow::{Context, Result, bail};
10use byteorder::{BE, WriteBytesExt};
11use either::Either;
12use fs_err::File;
13use parking_lot::Mutex;
14use smallvec::SmallVec;
15use thread_local::ThreadLocal;
16
17use crate::{
18 FamilyConfig, FamilyKind, ValueBuffer,
19 collector::Collector,
20 collector_entry::CollectorEntry,
21 compression::{Compressor, checksum_block},
22 constants::{MAX_INLINE_VALUE_SIZE, MAX_MEDIUM_VALUE_SIZE, THREAD_LOCAL_SIZE_SHIFT},
23 db::WriteOperationGuard,
24 key::StoreKey,
25 meta_file::MetaEntryFlags,
26 meta_file_builder::MetaFileBuilder,
27 parallel_scheduler::ParallelScheduler,
28 shard::ShardBits,
29 static_sorted_file_builder::{StaticSortedFileBuilderMeta, write_static_stored_file},
30};
31
32pub(crate) struct NewFile {
35 pub(crate) seq: u32,
36 pub(crate) file: File,
37 pub(crate) size: u64,
39}
40
41struct ThreadLocalState<K: StoreKey + Send, const FAMILIES: usize> {
47 collectors: [Option<Collector<K, THREAD_LOCAL_SIZE_SHIFT>>; FAMILIES],
49 new_blob_files: Vec<NewFile>,
51}
52
53pub(crate) struct FinishResult {
55 pub(crate) sequence_number: u32,
56 pub(crate) new_meta_files: Vec<NewFile>,
57 pub(crate) new_sst_files: Vec<NewFile>,
58 pub(crate) new_blob_files: Vec<NewFile>,
59 pub(crate) keys_written: u64,
61}
62
63const MAX_COLLECTOR_SHARD_BITS: ShardBits = ShardBits::new(2);
66
67enum GlobalCollectorState<K: StoreKey + Send> {
68 Unsharded(Collector<K>),
71 Sharded(ShardBits, Box<[Collector<K>]>),
74}
75
76pub struct WriteBatch<'db, K: StoreKey + Send, S: ParallelScheduler, const FAMILIES: usize> {
78 _write_guard: WriteOperationGuard<'db>,
80 parallel_scheduler: S,
82 db_path: PathBuf,
84 #[cfg_attr(not(feature = "verify_sst_content"), allow(dead_code))]
86 family_configs: [FamilyConfig; FAMILIES],
87 shard_bits: [ShardBits; FAMILIES],
89 current_sequence_number: AtomicU32,
91 thread_locals: ThreadLocal<SyncUnsafeCell<ThreadLocalState<K, FAMILIES>>>,
93 collectors: [Mutex<GlobalCollectorState<K>>; FAMILIES],
95 meta_collectors: [Mutex<Vec<(u32, StaticSortedFileBuilderMeta<'static>)>>; FAMILIES],
97 new_sst_files: Mutex<Vec<NewFile>>,
99}
100
101impl<'db, K: StoreKey + Send + Sync, S: ParallelScheduler, const FAMILIES: usize>
102 WriteBatch<'db, K, S, FAMILIES>
103{
104 pub(crate) fn new(
106 write_guard: WriteOperationGuard<'db>,
107 path: PathBuf,
108 current: u32,
109 parallel_scheduler: S,
110 family_configs: [FamilyConfig; FAMILIES],
111 shard_bits: [ShardBits; FAMILIES],
112 ) -> Self {
113 const {
114 assert!(FAMILIES <= usize_from_u32(u32::MAX));
115 };
116 Self {
117 _write_guard: write_guard,
118 parallel_scheduler,
119 db_path: path,
120 family_configs,
121 shard_bits,
122 current_sequence_number: AtomicU32::new(current),
123 thread_locals: ThreadLocal::new(),
124 collectors: [(); FAMILIES]
125 .map(|_| Mutex::new(GlobalCollectorState::Unsharded(Collector::new()))),
126 meta_collectors: [(); FAMILIES].map(|_| Mutex::new(Vec::new())),
127 new_sst_files: Mutex::new(Vec::new()),
128 }
129 }
130
131 pub(crate) fn mark_succeeded(&mut self) {
136 self._write_guard.success();
137 }
138
139 #[allow(clippy::mut_from_ref)]
141 fn thread_local_state(&self) -> &mut ThreadLocalState<K, FAMILIES> {
142 let cell = self.thread_locals.get_or(|| {
143 SyncUnsafeCell::new(ThreadLocalState {
144 collectors: [const { None }; FAMILIES],
145 new_blob_files: Vec::new(),
146 })
147 });
148 unsafe { &mut *cell.get() }
150 }
151
152 fn thread_local_collector_mut<'l>(
154 &self,
155 state: &'l mut ThreadLocalState<K, FAMILIES>,
156 family: u32,
157 ) -> Result<&'l mut Collector<K, THREAD_LOCAL_SIZE_SHIFT>> {
158 debug_assert!(usize_from_u32(family) < FAMILIES);
159 let collector =
160 state.collectors[usize_from_u32(family)].get_or_insert_with(|| Collector::new());
161 if collector.is_full() {
162 self.flush_thread_local_collector(family, collector)?;
163 }
164 Ok(collector)
165 }
166
167 #[tracing::instrument(level = "trace", skip(self, collector), fields(family_name = self.family_configs[usize_from_u32(family)].name))]
168 fn flush_thread_local_collector(
169 &self,
170 family: u32,
171 collector: &mut Collector<K, THREAD_LOCAL_SIZE_SHIFT>,
172 ) -> Result<()> {
173 let mut full_collectors = SmallVec::<[_; 2]>::new();
174 {
175 let mut global_collector_state = self.collectors[usize_from_u32(family)].lock();
176 for entry in collector.drain() {
177 match &mut *global_collector_state {
178 GlobalCollectorState::Unsharded(collector) => {
179 collector.add_entry(entry);
180 if collector.is_full() {
181 let collector_bits = self.collector_bits(family);
182 if collector_bits.count() == 1 {
183 full_collectors.push(replace(collector, Collector::new()));
184 } else {
185 let mut shards = (0..collector_bits.count())
187 .map(|_| Collector::new())
188 .collect::<Box<[_]>>();
189 for entry in collector.drain() {
190 let shard = collector_bits.shard_of(entry.key.hash) as usize;
191 shards[shard].add_entry(entry);
192 }
193 for collector in shards.iter_mut() {
197 if collector.is_full() {
198 full_collectors
199 .push(replace(&mut *collector, Collector::new()));
200 }
201 }
202 *global_collector_state =
203 GlobalCollectorState::Sharded(collector_bits, shards);
204 }
205 }
206 }
207 GlobalCollectorState::Sharded(collector_bits, shards) => {
208 let shard = collector_bits.shard_of(entry.key.hash) as usize;
209 let collector = &mut shards[shard];
210 collector.add_entry(entry);
211 if collector.is_full() {
212 full_collectors.push(replace(&mut *collector, Collector::new()));
213 }
214 }
215 }
216 }
217 }
218 for mut global_collector in full_collectors {
237 let ssts = self.create_sst_files(
239 family,
240 global_collector.sorted(self.family_configs[usize_from_u32(family)].kind),
241 )?;
242 self.new_sst_files.lock().extend(ssts);
243 drop(global_collector);
244 }
245 Ok(())
246 }
247
248 pub fn put(&self, family: u32, key: K, value: ValueBuffer<'_>) -> Result<()> {
250 let state = self.thread_local_state();
251 let collector = self.thread_local_collector_mut(state, family)?;
252 if value.len() <= MAX_MEDIUM_VALUE_SIZE {
253 collector.put(key, value);
254 } else {
255 let blob = self.create_blob(family, &value)?;
256 collector.put_blob(key, blob.seq);
257 state.new_blob_files.push(blob);
258 }
259 Ok(())
260 }
261
262 pub fn delete(&self, family: u32, key: K) -> Result<()> {
268 let state = self.thread_local_state();
269 let collector = self.thread_local_collector_mut(state, family)?;
270 collector.delete(key);
271 Ok(())
272 }
273
274 pub fn delete_value(&self, family: u32, key: K, value: ValueBuffer<'_>) -> Result<()> {
287 let family_config = &self.family_configs[usize_from_u32(family)];
288 if family_config.kind != FamilyKind::MultiValue {
289 bail!(
290 "delete_value is only valid for MultiValue families, but family {} is SingleValue",
291 family_config.name
292 );
293 }
294 if value.len() > MAX_INLINE_VALUE_SIZE {
295 bail!(
296 "delete_value only supports values of at most {MAX_INLINE_VALUE_SIZE} bytes, got \
297 {} bytes",
298 value.len()
299 );
300 }
301 let state = self.thread_local_state();
302 let collector = self.thread_local_collector_mut(state, family)?;
303 collector.delete_value(key, &value);
304 Ok(())
305 }
306
307 #[tracing::instrument(level = "trace", skip(self), fields(family_name = self.family_configs[usize_from_u32(family)].name))]
315 pub unsafe fn flush(&self, family: u32) -> Result<()> {
316 let mut collectors = Vec::new();
318 for cell in self.thread_locals.iter() {
319 let state = unsafe { &mut *cell.get() };
320 if let Some(collector) = state.collectors[usize_from_u32(family)].take()
321 && !collector.is_empty()
322 {
323 collectors.push(collector);
324 }
325 }
326
327 self.parallel_scheduler
328 .try_parallel_for_each_owned(collectors, |mut collector| {
329 self.flush_thread_local_collector(family, &mut collector)?;
330 drop(collector);
331 anyhow::Ok(())
332 })?;
333
334 let family_usize = usize_from_u32(family);
336 let mut collector_state = self.collectors[family_usize].lock();
337 let family_config = self.family_configs[family_usize];
338 match &mut *collector_state {
339 GlobalCollectorState::Unsharded(collector) => {
340 if !collector.is_empty() {
341 let ssts =
344 self.create_sst_files(family, collector.sorted(family_config.kind))?;
345 collector.clear();
346 self.new_sst_files.lock().extend(ssts);
347 }
348 }
349 GlobalCollectorState::Sharded(..) => {
350 let GlobalCollectorState::Sharded(_, mut shards) = replace(
351 &mut *collector_state,
352 GlobalCollectorState::Unsharded(Collector::new()),
353 ) else {
354 unreachable!();
355 };
356 self.parallel_scheduler
357 .try_parallel_for_each_mut(&mut shards, |collector| {
358 if !collector.is_empty() {
359 let ssts = self
360 .create_sst_files(family, collector.sorted(family_config.kind))?;
361 collector.clear_and_drop_capacity();
362 self.new_sst_files.lock().extend(ssts);
363 }
364 anyhow::Ok(())
365 })?;
366 }
367 }
368
369 Ok(())
370 }
371
372 #[tracing::instrument(level = "trace", skip_all)]
375 pub(crate) fn finish(
376 &mut self,
377 get_accessed_key_hashes: impl Fn(u32) -> qfilter::Filter + Send + Sync,
378 ) -> Result<FinishResult> {
379 let mut new_blob_files = Vec::new();
380
381 {
383 let _span = tracing::trace_span!("flush thread local collectors").entered();
384 let mut collectors = [const { Vec::new() }; FAMILIES];
385 for cell in self.thread_locals.iter_mut() {
386 let state = cell.get_mut();
387 new_blob_files.append(&mut state.new_blob_files);
388 for (family, thread_local_collector) in state.collectors.iter_mut().enumerate() {
389 if let Some(collector) = thread_local_collector.take()
390 && !collector.is_empty()
391 {
392 collectors[family].push(collector);
393 }
394 }
395 }
396 let to_flush = collectors
397 .into_iter()
398 .enumerate()
399 .flat_map(|(family, collector)| {
400 collector
401 .into_iter()
402 .map(move |collector| (family as u32, collector))
403 })
404 .collect::<Vec<_>>();
405 self.parallel_scheduler.try_parallel_for_each_owned(
406 to_flush,
407 |(family, mut collector)| {
408 self.flush_thread_local_collector(family, &mut collector)?;
409 drop(collector);
410 anyhow::Ok(())
411 },
412 )?;
413 }
414
415 let _span = tracing::trace_span!("flush collectors").entered();
416
417 let mut new_sst_files = take(self.new_sst_files.get_mut());
419 let shared_new_sst_files = Mutex::new(&mut new_sst_files);
420
421 let new_collectors =
422 [(); FAMILIES].map(|_| Mutex::new(GlobalCollectorState::Unsharded(Collector::new())));
423 let collectors = replace(&mut self.collectors, new_collectors);
424 let collectors = collectors
425 .into_iter()
426 .enumerate()
427 .flat_map(|(family, state)| {
428 let collector = state.into_inner();
429 match collector {
430 GlobalCollectorState::Unsharded(collector) => {
431 Either::Left([(family, collector)].into_iter())
432 }
433 GlobalCollectorState::Sharded(_, shards) => {
434 Either::Right(shards.into_iter().map(move |collector| (family, collector)))
435 }
436 }
437 })
438 .collect::<Vec<_>>();
439 self.parallel_scheduler.try_parallel_for_each_owned(
440 collectors,
441 |(family, mut collector)| {
442 let family = family as u32;
443 if !collector.is_empty() {
444 let ssts = self.create_sst_files(
445 family,
446 collector.sorted(self.family_configs[usize_from_u32(family)].kind),
447 )?;
448 collector.clear();
449 drop(collector);
450 shared_new_sst_files.lock().extend(ssts);
451 }
452 anyhow::Ok(())
453 },
454 )?;
455
456 let new_meta_collectors = [(); FAMILIES].map(|_| Mutex::new(Vec::new()));
458 let meta_collectors = replace(&mut self.meta_collectors, new_meta_collectors);
459 let keys_written = AtomicU64::new(0);
460 let file_to_write = meta_collectors
461 .into_iter()
462 .map(|mutex| mutex.into_inner())
463 .enumerate()
464 .filter(|(_, sst_files)| !sst_files.is_empty())
465 .collect::<Vec<_>>();
466 let new_meta_files = self
467 .parallel_scheduler
468 .parallel_map_collect_owned::<_, _, Result<Vec<_>>>(
469 file_to_write,
470 |(family, sst_files)| {
471 let family = family as u32;
472 let mut entries = 0;
473 let mut builder = MetaFileBuilder::new(
474 family,
475 self.family_configs[usize_from_u32(family)].compression,
476 self.shard_bits[usize_from_u32(family)],
477 );
478 for (seq, sst) in sst_files {
479 entries += sst.entries;
480 builder.add(seq, sst);
481 }
482 keys_written.fetch_add(entries, Ordering::Relaxed);
483 let accessed_key_hashes = get_accessed_key_hashes(family);
484 builder.set_used_key_hashes_amqf(accessed_key_hashes);
485 let seq = self.current_sequence_number.fetch_add(1, Ordering::SeqCst) + 1;
486 let (file, size) = builder.write(&self.db_path, seq)?;
487 Ok(NewFile { seq, file, size })
488 },
489 )?;
490
491 let seq = self.current_sequence_number.load(Ordering::SeqCst);
493 Ok(FinishResult {
494 sequence_number: seq,
495 new_meta_files,
496 new_sst_files,
497 new_blob_files,
498 keys_written: keys_written.into_inner(),
499 })
500 }
501
502 #[tracing::instrument(level = "trace", skip(self, value), fields(value_len = value.len()))]
504 fn create_blob(&self, family: u32, value: &[u8]) -> Result<NewFile> {
505 let seq = self.current_sequence_number.fetch_add(1, Ordering::SeqCst) + 1;
506 let mut compressed = Vec::new();
507 let compression = self.family_configs[usize_from_u32(family)].compression;
508 Compressor::new(compression)?
509 .compress_into_buffer(value, &mut compressed)
510 .context("Compression of value for blob file failed")?;
511
512 let mut buffer = Vec::with_capacity(8 + compressed.len());
513 buffer.write_u32::<BE>(value.len() as u32)?;
514 buffer.write_u32::<BE>(checksum_block(&compressed))?;
515 buffer.extend_from_slice(&compressed);
516
517 let size = buffer.len() as u64;
518 let file = self.db_path.join(format!("{seq:08}.blob"));
519 let mut file = File::create(&file)?;
520 file.write_all(&buffer)?;
521 file.flush()?;
522 Ok(NewFile { seq, file, size })
523 }
524
525 fn collector_bits(&self, family: u32) -> ShardBits {
528 self.shard_bits[usize_from_u32(family)].min(MAX_COLLECTOR_SHARD_BITS)
529 }
530
531 #[tracing::instrument(level = "trace", skip(self, entries), fields(family_name = self.family_configs[usize_from_u32(family)].name))]
532 fn create_sst_files(
534 &self,
535 family: u32,
536 entries: &[CollectorEntry<K>],
537 ) -> Result<SmallVec<[NewFile; 1]>> {
538 let shard_bits = self.shard_bits[usize_from_u32(family)];
539 let mut files = SmallVec::new();
540 let mut rest = entries;
541 while let Some(first) = rest.first() {
542 let shard = shard_bits.shard_of(first.key.hash);
544 let len = rest.partition_point(|e| shard_bits.shard_of(e.key.hash) == shard);
545 let (chunk, remaining) = rest.split_at(len);
546 files.push(self.create_sst_file(family, chunk)?);
547 rest = remaining;
548 }
549 Ok(files)
550 }
551
552 fn create_sst_file(&self, family: u32, entries: &[CollectorEntry<K>]) -> Result<NewFile> {
554 let seq = self.current_sequence_number.fetch_add(1, Ordering::SeqCst) + 1;
555
556 let path = self.db_path.join(format!("{seq:08}.sst"));
557 let (meta, file) = self
558 .parallel_scheduler
559 .block_in_place(|| {
560 write_static_stored_file(
561 entries,
562 &path,
563 MetaEntryFlags::FRESH,
564 self.family_configs[usize_from_u32(family)].compression,
565 )
566 })
567 .with_context(|| format!("Unable to write SST file {seq:08}.sst"))?;
568
569 #[cfg(feature = "verify_sst_content")]
570 {
571 use core::panic;
572
573 use crate::{
574 collector_entry::CollectorEntryValue,
575 key::hash_key,
576 lookup_entry::LookupValue,
577 static_sorted_file::{
578 BlockCache, SstLookupResult, StaticSortedFile, StaticSortedFileMetaData,
579 },
580 static_sorted_file_builder::Entry,
581 };
582
583 file.sync_all()?;
584 let sst = StaticSortedFile::open(
585 &self.db_path,
586 StaticSortedFileMetaData {
587 sequence_number: seq,
588 block_count: meta.block_count,
589 },
590 self.family_configs[usize_from_u32(family)].compression,
591 crate::mmap_access_mode(),
592 )?;
593 let cache2 = BlockCache::with(
594 10,
595 u64::MAX,
596 Default::default(),
597 Default::default(),
598 Default::default(),
599 );
600 let cache3 = BlockCache::with(
601 10,
602 u64::MAX,
603 Default::default(),
604 Default::default(),
605 Default::default(),
606 );
607 let mut key_buf = Vec::new();
608 let family_config = self.family_configs[usize_from_u32(family)].kind;
609 for entry in entries {
610 entry.write_key_to(&mut key_buf);
611 let result = sst
612 .lookup::<_, true>(hash_key(&key_buf), &key_buf, &cache2, &cache3)
613 .expect("key found");
614 key_buf.clear();
615 match result {
616 SstLookupResult::Found(values) => {
617 if values.len() > 1 {
618 use crate::FamilyKind;
619
620 assert!(
621 values.len() == 1 || family_config == FamilyKind::MultiValue,
622 "only multi-value tables can have more than one value, got {} \
623 values",
624 values.len()
625 )
626 }
627 match &entry.value {
628 CollectorEntryValue::Large { blob } => {
629 assert!(
630 values.contains(&LookupValue::Blob {
631 sequence_number: *blob
632 }),
633 "we wrote a blob but did not read it"
634 );
635 }
636 CollectorEntryValue::KeyDeleted => assert!(
639 values.last() == Some(&LookupValue::KeyDeleted),
640 "we wrote a key tombstone but it was not last in results"
641 ),
642 CollectorEntryValue::KeyValueDeleted { value, len } => {
643 let expected = &value[..*len as usize];
644 assert!(
645 values.iter().any(|lv| matches!(
646 lv,
647 LookupValue::KeyValueDeleted { value } if &**value == expected
648 )),
649 "we wrote a key-value tombstone but did not read it back"
650 )
651 }
652 v => {
653 assert!(
654 values.into_iter().any(|lv| {
655 if let LookupValue::Slice { value } = lv {
656 &*value == v.as_bytes().unwrap()
657 } else {
658 false
659 }
660 }),
661 "we wrote a slice of bytes but did not read it"
662 )
663 }
664 }
665 }
666 SstLookupResult::NotFound => panic!("All keys must exist"),
667 }
668 }
669 }
670
671 let size = meta.size;
672 self.meta_collectors[usize_from_u32(family)]
673 .lock()
674 .push((seq, meta));
675
676 Ok(NewFile { seq, file, size })
677 }
678}
679
680#[inline(always)]
681const fn usize_from_u32(value: u32) -> usize {
682 const {
685 assert!(u32::BITS <= usize::BITS);
686 };
687 value as usize
688}