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 static_sorted_file_builder::{StaticSortedFileBuilderMeta, write_static_stored_file},
29};
30
31pub(crate) struct NewFile {
34 pub(crate) seq: u32,
35 pub(crate) file: File,
36 pub(crate) size: u64,
38}
39
40struct ThreadLocalState<K: StoreKey + Send, const FAMILIES: usize> {
46 collectors: [Option<Collector<K, THREAD_LOCAL_SIZE_SHIFT>>; FAMILIES],
48 new_blob_files: Vec<NewFile>,
50}
51
52const COLLECTOR_SHARDS: usize = 4;
53const COLLECTOR_SHARD_SHIFT: usize =
54 u64::BITS as usize - COLLECTOR_SHARDS.trailing_zeros() as usize;
55
56pub(crate) struct FinishResult {
58 pub(crate) sequence_number: u32,
59 pub(crate) new_meta_files: Vec<NewFile>,
60 pub(crate) new_sst_files: Vec<NewFile>,
61 pub(crate) new_blob_files: Vec<NewFile>,
62 pub(crate) keys_written: u64,
64}
65
66enum GlobalCollectorState<K: StoreKey + Send> {
67 Unsharded(Collector<K>),
69 Sharded([Collector<K>; COLLECTOR_SHARDS]),
72}
73
74pub struct WriteBatch<'db, K: StoreKey + Send, S: ParallelScheduler, const FAMILIES: usize> {
76 _write_guard: WriteOperationGuard<'db>,
78 parallel_scheduler: S,
80 db_path: PathBuf,
82 #[cfg_attr(not(feature = "verify_sst_content"), allow(dead_code))]
84 family_configs: [FamilyConfig; FAMILIES],
85 current_sequence_number: AtomicU32,
87 thread_locals: ThreadLocal<SyncUnsafeCell<ThreadLocalState<K, FAMILIES>>>,
89 collectors: [Mutex<GlobalCollectorState<K>>; FAMILIES],
91 meta_collectors: [Mutex<Vec<(u32, StaticSortedFileBuilderMeta<'static>)>>; FAMILIES],
93 new_sst_files: Mutex<Vec<NewFile>>,
95}
96
97impl<'db, K: StoreKey + Send + Sync, S: ParallelScheduler, const FAMILIES: usize>
98 WriteBatch<'db, K, S, FAMILIES>
99{
100 pub(crate) fn new(
102 write_guard: WriteOperationGuard<'db>,
103 path: PathBuf,
104 current: u32,
105 parallel_scheduler: S,
106 family_configs: [FamilyConfig; FAMILIES],
107 ) -> Self {
108 const {
109 assert!(FAMILIES <= usize_from_u32(u32::MAX));
110 };
111 Self {
112 _write_guard: write_guard,
113 parallel_scheduler,
114 db_path: path,
115 family_configs,
116 current_sequence_number: AtomicU32::new(current),
117 thread_locals: ThreadLocal::new(),
118 collectors: [(); FAMILIES]
119 .map(|_| Mutex::new(GlobalCollectorState::Unsharded(Collector::new()))),
120 meta_collectors: [(); FAMILIES].map(|_| Mutex::new(Vec::new())),
121 new_sst_files: Mutex::new(Vec::new()),
122 }
123 }
124
125 pub(crate) fn mark_succeeded(&mut self) {
130 self._write_guard.success();
131 }
132
133 #[allow(clippy::mut_from_ref)]
135 fn thread_local_state(&self) -> &mut ThreadLocalState<K, FAMILIES> {
136 let cell = self.thread_locals.get_or(|| {
137 SyncUnsafeCell::new(ThreadLocalState {
138 collectors: [const { None }; FAMILIES],
139 new_blob_files: Vec::new(),
140 })
141 });
142 unsafe { &mut *cell.get() }
144 }
145
146 fn thread_local_collector_mut<'l>(
148 &self,
149 state: &'l mut ThreadLocalState<K, FAMILIES>,
150 family: u32,
151 ) -> Result<&'l mut Collector<K, THREAD_LOCAL_SIZE_SHIFT>> {
152 debug_assert!(usize_from_u32(family) < FAMILIES);
153 let collector =
154 state.collectors[usize_from_u32(family)].get_or_insert_with(|| Collector::new());
155 if collector.is_full() {
156 self.flush_thread_local_collector(family, collector)?;
157 }
158 Ok(collector)
159 }
160
161 #[tracing::instrument(level = "trace", skip(self, collector), fields(family_name = self.family_configs[usize_from_u32(family)].name))]
162 fn flush_thread_local_collector(
163 &self,
164 family: u32,
165 collector: &mut Collector<K, THREAD_LOCAL_SIZE_SHIFT>,
166 ) -> Result<()> {
167 let mut full_collectors = SmallVec::<[_; 2]>::new();
168 {
169 let mut global_collector_state = self.collectors[usize_from_u32(family)].lock();
170 for entry in collector.drain() {
171 match &mut *global_collector_state {
172 GlobalCollectorState::Unsharded(collector) => {
173 collector.add_entry(entry);
174 if collector.is_full() {
175 let mut shards: [Collector<K>; 4] =
177 [(); COLLECTOR_SHARDS].map(|_| Collector::new());
178 for entry in collector.drain() {
179 let shard = (entry.key.hash >> COLLECTOR_SHARD_SHIFT) as usize;
180 shards[shard].add_entry(entry);
181 }
182 for collector in shards.iter_mut() {
185 if collector.is_full() {
186 full_collectors
187 .push(replace(&mut *collector, Collector::new()));
188 }
189 }
190 *global_collector_state = GlobalCollectorState::Sharded(shards);
191 }
192 }
193 GlobalCollectorState::Sharded(shards) => {
194 let shard = (entry.key.hash >> COLLECTOR_SHARD_SHIFT) as usize;
195 let collector = &mut shards[shard];
196 collector.add_entry(entry);
197 if collector.is_full() {
198 full_collectors.push(replace(&mut *collector, Collector::new()));
199 }
200 }
201 }
202 }
203 }
204 for mut global_collector in full_collectors {
223 let sst = self.create_sst_file(
225 family,
226 global_collector.sorted(self.family_configs[usize_from_u32(family)].kind),
227 )?;
228 self.new_sst_files.lock().push(sst);
229 drop(global_collector);
230 }
231 Ok(())
232 }
233
234 pub fn put(&self, family: u32, key: K, value: ValueBuffer<'_>) -> Result<()> {
236 let state = self.thread_local_state();
237 let collector = self.thread_local_collector_mut(state, family)?;
238 if value.len() <= MAX_MEDIUM_VALUE_SIZE {
239 collector.put(key, value);
240 } else {
241 let blob = self.create_blob(family, &value)?;
242 collector.put_blob(key, blob.seq);
243 state.new_blob_files.push(blob);
244 }
245 Ok(())
246 }
247
248 pub fn delete(&self, family: u32, key: K) -> Result<()> {
254 let state = self.thread_local_state();
255 let collector = self.thread_local_collector_mut(state, family)?;
256 collector.delete(key);
257 Ok(())
258 }
259
260 pub fn delete_value(&self, family: u32, key: K, value: ValueBuffer<'_>) -> Result<()> {
273 let family_config = &self.family_configs[usize_from_u32(family)];
274 if family_config.kind != FamilyKind::MultiValue {
275 bail!(
276 "delete_value is only valid for MultiValue families, but family {} is SingleValue",
277 family_config.name
278 );
279 }
280 if value.len() > MAX_INLINE_VALUE_SIZE {
281 bail!(
282 "delete_value only supports values of at most {MAX_INLINE_VALUE_SIZE} bytes, got \
283 {} bytes",
284 value.len()
285 );
286 }
287 let state = self.thread_local_state();
288 let collector = self.thread_local_collector_mut(state, family)?;
289 collector.delete_value(key, &value);
290 Ok(())
291 }
292
293 #[tracing::instrument(level = "trace", skip(self), fields(family_name = self.family_configs[usize_from_u32(family)].name))]
301 pub unsafe fn flush(&self, family: u32) -> Result<()> {
302 let mut collectors = Vec::new();
304 for cell in self.thread_locals.iter() {
305 let state = unsafe { &mut *cell.get() };
306 if let Some(collector) = state.collectors[usize_from_u32(family)].take()
307 && !collector.is_empty()
308 {
309 collectors.push(collector);
310 }
311 }
312
313 self.parallel_scheduler
314 .try_parallel_for_each_owned(collectors, |mut collector| {
315 self.flush_thread_local_collector(family, &mut collector)?;
316 drop(collector);
317 anyhow::Ok(())
318 })?;
319
320 let family_usize = usize_from_u32(family);
322 let mut collector_state = self.collectors[family_usize].lock();
323 let family_config = self.family_configs[family_usize];
324 match &mut *collector_state {
325 GlobalCollectorState::Unsharded(collector) => {
326 if !collector.is_empty() {
327 let sst = self.create_sst_file(family, collector.sorted(family_config.kind))?;
328 collector.clear();
329 self.new_sst_files.lock().push(sst);
330 }
331 }
332 GlobalCollectorState::Sharded(_) => {
333 let GlobalCollectorState::Sharded(mut shards) = replace(
334 &mut *collector_state,
335 GlobalCollectorState::Unsharded(Collector::new()),
336 ) else {
337 unreachable!();
338 };
339 self.parallel_scheduler
340 .try_parallel_for_each_mut(&mut shards, |collector| {
341 if !collector.is_empty() {
342 let sst =
343 self.create_sst_file(family, collector.sorted(family_config.kind))?;
344 collector.clear();
345 self.new_sst_files.lock().push(sst);
346 collector.drop_contents();
347 }
348 anyhow::Ok(())
349 })?;
350 }
351 }
352
353 Ok(())
354 }
355
356 #[tracing::instrument(level = "trace", skip_all)]
359 pub(crate) fn finish(
360 &mut self,
361 get_accessed_key_hashes: impl Fn(u32) -> qfilter::Filter + Send + Sync,
362 ) -> Result<FinishResult> {
363 let mut new_blob_files = Vec::new();
364
365 {
367 let _span = tracing::trace_span!("flush thread local collectors").entered();
368 let mut collectors = [const { Vec::new() }; FAMILIES];
369 for cell in self.thread_locals.iter_mut() {
370 let state = cell.get_mut();
371 new_blob_files.append(&mut state.new_blob_files);
372 for (family, thread_local_collector) in state.collectors.iter_mut().enumerate() {
373 if let Some(collector) = thread_local_collector.take()
374 && !collector.is_empty()
375 {
376 collectors[family].push(collector);
377 }
378 }
379 }
380 let to_flush = collectors
381 .into_iter()
382 .enumerate()
383 .flat_map(|(family, collector)| {
384 collector
385 .into_iter()
386 .map(move |collector| (family as u32, collector))
387 })
388 .collect::<Vec<_>>();
389 self.parallel_scheduler.try_parallel_for_each_owned(
390 to_flush,
391 |(family, mut collector)| {
392 self.flush_thread_local_collector(family, &mut collector)?;
393 drop(collector);
394 anyhow::Ok(())
395 },
396 )?;
397 }
398
399 let _span = tracing::trace_span!("flush collectors").entered();
400
401 let mut new_sst_files = take(self.new_sst_files.get_mut());
403 let shared_new_sst_files = Mutex::new(&mut new_sst_files);
404
405 let new_collectors =
406 [(); FAMILIES].map(|_| Mutex::new(GlobalCollectorState::Unsharded(Collector::new())));
407 let collectors = replace(&mut self.collectors, new_collectors);
408 let collectors = collectors
409 .into_iter()
410 .enumerate()
411 .flat_map(|(family, state)| {
412 let collector = state.into_inner();
413 match collector {
414 GlobalCollectorState::Unsharded(collector) => {
415 Either::Left([(family, collector)].into_iter())
416 }
417 GlobalCollectorState::Sharded(shards) => {
418 Either::Right(shards.into_iter().map(move |collector| (family, collector)))
419 }
420 }
421 })
422 .collect::<Vec<_>>();
423 self.parallel_scheduler.try_parallel_for_each_owned(
424 collectors,
425 |(family, mut collector)| {
426 let family = family as u32;
427 if !collector.is_empty() {
428 let sst = self.create_sst_file(
429 family,
430 collector.sorted(self.family_configs[usize_from_u32(family)].kind),
431 )?;
432 collector.clear();
433 drop(collector);
434 shared_new_sst_files.lock().push(sst);
435 }
436 anyhow::Ok(())
437 },
438 )?;
439
440 let new_meta_collectors = [(); FAMILIES].map(|_| Mutex::new(Vec::new()));
442 let meta_collectors = replace(&mut self.meta_collectors, new_meta_collectors);
443 let keys_written = AtomicU64::new(0);
444 let file_to_write = meta_collectors
445 .into_iter()
446 .map(|mutex| mutex.into_inner())
447 .enumerate()
448 .filter(|(_, sst_files)| !sst_files.is_empty())
449 .collect::<Vec<_>>();
450 let new_meta_files = self
451 .parallel_scheduler
452 .parallel_map_collect_owned::<_, _, Result<Vec<_>>>(
453 file_to_write,
454 |(family, sst_files)| {
455 let family = family as u32;
456 let mut entries = 0;
457 let mut builder = MetaFileBuilder::new(
458 family,
459 self.family_configs[usize_from_u32(family)].compression,
460 );
461 for (seq, sst) in sst_files {
462 entries += sst.entries;
463 builder.add(seq, sst);
464 }
465 keys_written.fetch_add(entries, Ordering::Relaxed);
466 let accessed_key_hashes = get_accessed_key_hashes(family);
467 builder.set_used_key_hashes_amqf(accessed_key_hashes);
468 let seq = self.current_sequence_number.fetch_add(1, Ordering::SeqCst) + 1;
469 let (file, size) = builder.write(&self.db_path, seq)?;
470 Ok(NewFile { seq, file, size })
471 },
472 )?;
473
474 let seq = self.current_sequence_number.load(Ordering::SeqCst);
476 Ok(FinishResult {
477 sequence_number: seq,
478 new_meta_files,
479 new_sst_files,
480 new_blob_files,
481 keys_written: keys_written.into_inner(),
482 })
483 }
484
485 #[tracing::instrument(level = "trace", skip(self, value), fields(value_len = value.len()))]
487 fn create_blob(&self, family: u32, value: &[u8]) -> Result<NewFile> {
488 let seq = self.current_sequence_number.fetch_add(1, Ordering::SeqCst) + 1;
489 let mut compressed = Vec::new();
490 let compression = self.family_configs[usize_from_u32(family)].compression;
491 Compressor::new(compression)?
492 .compress_into_buffer(value, &mut compressed)
493 .context("Compression of value for blob file failed")?;
494
495 let mut buffer = Vec::with_capacity(8 + compressed.len());
496 buffer.write_u32::<BE>(value.len() as u32)?;
497 buffer.write_u32::<BE>(checksum_block(&compressed))?;
498 buffer.extend_from_slice(&compressed);
499
500 let size = buffer.len() as u64;
501 let file = self.db_path.join(format!("{seq:08}.blob"));
502 let mut file = File::create(&file)?;
503 file.write_all(&buffer)?;
504 file.flush()?;
505 Ok(NewFile { seq, file, size })
506 }
507
508 #[tracing::instrument(level = "trace", skip(self, collector_data), fields(family_name = self.family_configs[usize_from_u32(family)].name))]
510 fn create_sst_file(
511 &self,
512 family: u32,
513 collector_data: (&[CollectorEntry<K>], usize),
514 ) -> Result<NewFile> {
515 let (entries, _total_key_size) = collector_data;
516 let seq = self.current_sequence_number.fetch_add(1, Ordering::SeqCst) + 1;
517
518 let path = self.db_path.join(format!("{seq:08}.sst"));
519 let (meta, file) = self
520 .parallel_scheduler
521 .block_in_place(|| {
522 write_static_stored_file(
523 entries,
524 &path,
525 MetaEntryFlags::FRESH,
526 self.family_configs[usize_from_u32(family)].compression,
527 )
528 })
529 .with_context(|| format!("Unable to write SST file {seq:08}.sst"))?;
530
531 #[cfg(feature = "verify_sst_content")]
532 {
533 use core::panic;
534
535 use crate::{
536 collector_entry::CollectorEntryValue,
537 key::hash_key,
538 lookup_entry::LookupValue,
539 static_sorted_file::{
540 BlockCache, SstLookupResult, StaticSortedFile, StaticSortedFileMetaData,
541 },
542 static_sorted_file_builder::Entry,
543 };
544
545 file.sync_all()?;
546 let sst = StaticSortedFile::open(
547 &self.db_path,
548 StaticSortedFileMetaData {
549 sequence_number: seq,
550 block_count: meta.block_count,
551 },
552 self.family_configs[usize_from_u32(family)].compression,
553 crate::mmap_access_mode(),
554 )?;
555 let cache2 = BlockCache::with(
556 10,
557 u64::MAX,
558 Default::default(),
559 Default::default(),
560 Default::default(),
561 );
562 let cache3 = BlockCache::with(
563 10,
564 u64::MAX,
565 Default::default(),
566 Default::default(),
567 Default::default(),
568 );
569 let mut key_buf = Vec::new();
570 let family_config = self.family_configs[usize_from_u32(family)].kind;
571 for entry in entries {
572 entry.write_key_to(&mut key_buf);
573 let result = sst
574 .lookup::<_, true>(hash_key(&key_buf), &key_buf, &cache2, &cache3)
575 .expect("key found");
576 key_buf.clear();
577 match result {
578 SstLookupResult::Found(values) => {
579 if values.len() > 1 {
580 use crate::FamilyKind;
581
582 assert!(
583 values.len() == 1 || family_config == FamilyKind::MultiValue,
584 "only multi-value tables can have more than one value, got {} \
585 values",
586 values.len()
587 )
588 }
589 match &entry.value {
590 CollectorEntryValue::Large { blob } => {
591 assert!(
592 values.contains(&LookupValue::Blob {
593 sequence_number: *blob
594 }),
595 "we wrote a blob but did not read it"
596 );
597 }
598 CollectorEntryValue::KeyDeleted => assert!(
601 values.last() == Some(&LookupValue::KeyDeleted),
602 "we wrote a key tombstone but it was not last in results"
603 ),
604 CollectorEntryValue::KeyValueDeleted { value, len } => {
605 let expected = &value[..*len as usize];
606 assert!(
607 values.iter().any(|lv| matches!(
608 lv,
609 LookupValue::KeyValueDeleted { value } if &**value == expected
610 )),
611 "we wrote a key-value tombstone but did not read it back"
612 )
613 }
614 v => {
615 assert!(
616 values.into_iter().any(|lv| {
617 if let LookupValue::Slice { value } = lv {
618 &*value == v.as_bytes().unwrap()
619 } else {
620 false
621 }
622 }),
623 "we wrote a slice of bytes but did not read it"
624 )
625 }
626 }
627 }
628 SstLookupResult::NotFound => panic!("All keys must exist"),
629 }
630 }
631 }
632
633 let size = meta.size;
634 self.meta_collectors[usize_from_u32(family)]
635 .lock()
636 .push((seq, meta));
637
638 Ok(NewFile { seq, file, size })
639 }
640}
641
642#[inline(always)]
643const fn usize_from_u32(value: u32) -> usize {
644 const {
647 assert!(u32::BITS <= usize::BITS);
648 };
649 value as usize
650}