rust_rocksdb/sst_partitioner.rs
1//! Boundaries that compaction must cut SST files on.
2//!
3//! An SST partitioner is consulted for every key a compaction writes. When it
4//! reports a boundary, the compaction closes the output file it is filling and
5//! starts a new one, so the keys on either side of the boundary never share a
6//! file. Trivial moves are held to the same rule: a file only moves down a
7//! level untouched if the partitioner says its first and last key belong
8//! together.
9//!
10//! The point is to line file boundaries up with something the application
11//! cares about, usually a key prefix that identifies a tenant, a shard, or a
12//! table. Once a prefix owns whole files, work scoped to that prefix gets
13//! cheaper. [`DB::delete_file_in_range`](crate::DB::delete_file_in_range) only
14//! drops files that sit entirely inside the range, so a partitioned prefix can
15//! be deleted by unlinking files rather than by writing tombstones that later
16//! compactions have to carry. Promoting one prefix to the next level stops
17//! dragging its neighbours along, which is the write amplification argument
18//! upstream makes for the feature.
19//!
20//! Partitioning only shapes files that future compactions produce. Files
21//! already on disk keep whatever boundaries they were written with, and are
22//! only re-cut when a compaction happens to rewrite them.
23//!
24//! Upstream marks the feature experimental in `options.h`.
25
26use std::ptr::NonNull;
27
28use crate::ffi;
29
30/// A factory RocksDB asks for a partitioner at the start of every compaction.
31///
32/// Install one with
33/// [`Options::set_sst_partitioner_factory`](crate::Options::set_sst_partitioner_factory).
34/// The setter copies the underlying `shared_ptr`, so a factory can be installed
35/// on any number of `Options` and dropped as soon as the last call returns.
36pub struct SstPartitionerFactory {
37 inner: NonNull<ffi::rocksdb_sst_partitioner_factory_t>,
38}
39
40impl SstPartitionerFactory {
41 /// Cuts a new SST file whenever the first `prefix_len` bytes of the user
42 /// key change.
43 ///
44 /// Keys shorter than `prefix_len` are compared whole, so a short key only
45 /// groups with keys that share all of it. `prefix_len` of 0 makes every key
46 /// compare equal and never forces a cut, which is the same as installing no
47 /// partitioner.
48 ///
49 /// The comparison is over raw bytes of the user key and ignores the column
50 /// family comparator, so this is a fit for fixed width prefixes such as a
51 /// tenant id, not for prefixes an application defines through a
52 /// [`SliceTransform`](crate::SliceTransform).
53 ///
54 /// Wraps `NewSstPartitionerFixedPrefixFactory`.
55 #[must_use]
56 pub fn fixed_prefix(prefix_len: usize) -> Self {
57 let inner = unsafe { ffi::rocksdb_sst_partitioner_fixed_prefix_factory_create(prefix_len) };
58 Self {
59 inner: NonNull::new(inner)
60 .expect("rocksdb_sst_partitioner_fixed_prefix_factory_create returned null"),
61 }
62 }
63
64 pub(crate) fn as_ptr(&self) -> *mut ffi::rocksdb_sst_partitioner_factory_t {
65 self.inner.as_ptr()
66 }
67}
68
69impl Drop for SstPartitionerFactory {
70 fn drop(&mut self) {
71 unsafe {
72 ffi::rocksdb_sst_partitioner_factory_destroy(self.inner.as_ptr());
73 }
74 }
75}
76
77// `rocksdb_sst_partitioner_factory_t` is a `std::shared_ptr<SstPartitionerFactory>`
78// and nothing else (c.cc:501). Destroying the handle only drops that one
79// reference, and the only other operation this crate performs on it is the
80// refcount bump in `rocksdb_options_set_sst_partitioner_factory` (c.cc:6142),
81// which is atomic. So the handle carries no thread affinity, and concurrent
82// reads through `&SstPartitionerFactory` cannot race.
83unsafe impl Send for SstPartitionerFactory {}
84unsafe impl Sync for SstPartitionerFactory {}