Skip to main content

llkv_btree/bplus_tree/
shared_bplus_tree.rs

1use std::sync::{Mutex as StdMutex, RwLock};
2
3use crossbeam_channel as xchan;
4
5use crate::bplus_tree::BPlusTree;
6use crate::codecs::{IdCodec, KeyCodec};
7use crate::errors::Error;
8use crate::iter::{BPlusTreeIter, ScanOpts};
9use crate::node_cache::NodeCache;
10use crate::pager::Pager;
11use crate::traits::BTree;
12use std::sync::Arc;
13
14/// A façade that provides a serialized writer and snapshot readers,
15/// while implementing the same `BTree` trait.
16pub struct SharedBPlusTree<P, KC, IC>
17where
18    P: Pager + Clone,
19    P::Page: Send + Sync + 'static,
20    P::Id: Send + Sync + 'static,
21    KC: KeyCodec,
22    IC: IdCodec<Id = P::Id>,
23{
24    pager: P,
25    writer: StdMutex<BPlusTree<P, KC, IC>>, // single-threaded writer
26    latest_root: RwLock<P::Id>,             // published root for readers
27    shared_node_cache: Arc<RwLock<NodeCache<P>>>,
28    // Cache page size so readers do not lock the writer.
29    page_size: usize,
30}
31
32impl<P, KC, IC> SharedBPlusTree<P, KC, IC>
33where
34    P: Pager + Clone,
35    P::Page: Send + Sync + 'static,
36    P::Id: Send + Sync + 'static,
37    KC: KeyCodec,
38    IC: IdCodec<Id = P::Id>,
39{
40    /// Create an empty tree + shared writer.
41    pub fn create_empty(
42        pager: P,
43        opt_shared_node_cache: Option<Arc<RwLock<NodeCache<P>>>>,
44    ) -> Result<Self, Error> {
45        let shared_node_cache = BPlusTree::<P, KC, IC>::init_node_cache(opt_shared_node_cache);
46
47        let writer = BPlusTree::<P, KC, IC>::create_empty(
48            pager.clone(),
49            Some(Arc::clone(&shared_node_cache)),
50        )?;
51        let root = writer.root_id();
52        let page_size = writer.page_size();
53
54        Ok(Self {
55            pager,
56            writer: StdMutex::new(writer),
57            latest_root: RwLock::new(root),
58            shared_node_cache,
59            page_size,
60        })
61    }
62
63    /// Open a shared B+Tree backed by an existing root.
64    pub fn open(
65        pager: P,
66        root: P::Id,
67        opt_shared_node_cache: Option<Arc<RwLock<NodeCache<P>>>>,
68    ) -> Self {
69        let shared_node_cache = BPlusTree::<P, KC, IC>::init_node_cache(opt_shared_node_cache);
70
71        let writer = BPlusTree::<P, KC, IC>::new(
72            pager.clone(),
73            root.clone(),
74            Some(Arc::clone(&shared_node_cache)),
75        );
76        let page_size = writer.page_size();
77
78        Self {
79            pager,
80            writer: StdMutex::new(writer),
81            latest_root: RwLock::new(root),
82            shared_node_cache,
83            page_size,
84        }
85    }
86
87    /// Produce a **snapshot** tree at the current published root.
88    /// This is a normal `BPlusTree` you can iterate with `ScanOpts`.
89    pub fn snapshot(&self) -> BPlusTree<P, KC, IC> {
90        let root = { self.latest_root.read().unwrap().clone() };
91        let shared_node_cache = Arc::clone(&self.shared_node_cache);
92
93        BPlusTree::<P, KC, IC>::with_readonly_pager_state(
94            self.pager.clone(),
95            root,
96            shared_node_cache,
97            self.page_size,
98        )
99    }
100
101    // TODO: Consider putting this behind a feature flag and only using it for
102    // local testing purposes. This is good for some integration and benchmark
103    // testing, but callers wil likely benefit from manually snapshotting on the
104    // thread they choose to run it on.
105    //
106    /// Start a streaming scan at the current root, using any `ScanOpts`.
107    /// Returns a crossbeam MPMC receiver; read it from as many threads as you
108    /// like. Keys/values are owned (no borrows across threads).
109    pub fn start_stream_with_opts(
110        &self,
111        opts: ScanOpts<'static, KC>,
112    ) -> xchan::Receiver<(KC::Key, Vec<u8>)>
113    where
114        // required because we move these into a rayon worker thread
115        P: Send + 'static,
116        P::Id: Send + 'static,
117        KC: KeyCodec + 'static,
118        KC::Key: Send + Sync + 'static,
119        IC: IdCodec<Id = P::Id> + 'static,
120    {
121        let root = { self.latest_root.read().unwrap().clone() };
122        let pager = self.pager.clone();
123
124        // TODO: Make the capacity configurable
125        let (tx, rx) = xchan::bounded(1);
126
127        let shared_node_cache = Arc::clone(&self.shared_node_cache);
128        let page_size = self.page_size;
129
130        rayon::spawn(move || {
131            let tree = BPlusTree::<P, KC, IC>::with_readonly_pager_state(
132                pager,
133                root,
134                shared_node_cache,
135                page_size,
136            );
137            if let Ok(it) = BPlusTreeIter::with_opts(&tree, opts) {
138                for (kref, vref) in it {
139                    let key = KC::decode_from(kref.as_ref()).expect("decode key");
140                    let val = vref.as_ref().to_vec();
141                    if tx.send((key, val)).is_err() {
142                        break;
143                    }
144                    // std::thread::yield_now(); // encourages interleaving
145                }
146            }
147            // drop(tx) ends the stream
148        });
149
150        rx
151    }
152
153    // Optionally expose a publish step (useful if you batch).
154    // pub fn flush(&self) -> Result<(), Error> {
155    //     let mut w = self.writer.lock().unwrap();
156    //     w.flush()?;
157    //     *self.latest_root.write().unwrap() = w.root_id();
158    //     Ok(())
159    // }
160}
161
162// --- Implement the public BTree trait on SharedBPlusTree ---
163
164impl<'a, P, KC, IC> BTree<'a, KC::Key> for SharedBPlusTree<P, KC, IC>
165where
166    P: Pager + Clone + 'a,
167    P::Page: Send + Sync + 'static,
168    P::Id: Send + Sync + 'static,
169    KC: KeyCodec + 'a,
170    IC: IdCodec<Id = P::Id> + 'a,
171{
172    type Value = <BPlusTree<P, KC, IC> as BTree<'a, KC::Key>>::Value;
173
174    fn get_many(&'a self, keys: &[KC::Key]) -> Result<Vec<Option<Self::Value>>, Error> {
175        self.snapshot().get_many(keys)
176    }
177
178    fn contains_key(&'a self, key: &KC::Key) -> Result<bool, Error> {
179        self.snapshot().contains_key(key)
180    }
181
182    fn insert_many(&self, items: &[(KC::Key, &[u8])]) -> Result<(), Error>
183    where
184        KC::Key: Clone,
185    {
186        // Perform write work under the writer lock, then release it
187        // before publishing the new root to avoid cross-lock waits.
188        let new_root = {
189            let w = self.writer.lock().unwrap();
190            w.insert_many(items)?;
191            w.flush()?;
192            w.root_id()
193        };
194        *self.latest_root.write().unwrap() = new_root;
195        Ok(())
196    }
197
198    fn delete_many(&self, keys: &[KC::Key]) -> Result<(), Error>
199    where
200        KC::Key: Clone,
201    {
202        let new_root = {
203            let w = self.writer.lock().unwrap();
204            w.delete_many(keys)?;
205            w.flush()?;
206            w.root_id()
207        };
208        *self.latest_root.write().unwrap() = new_root;
209        Ok(())
210    }
211}