Skip to main content

rod/adapters/
sled_storage.rs

1use std::collections::{BTreeMap, HashSet};
2use std::sync::atomic::{AtomicUsize, Ordering};
3
4use crate::actor::{Actor, ActorContext};
5use crate::message::{Get, Message, Put};
6use crate::types::*;
7use crate::Config;
8
9use async_trait::async_trait;
10use log::{debug, error, info};
11use serde::{Deserialize, Serialize};
12use sled;
13use tokio::time::{sleep, Duration};
14
15static COUNTER: AtomicUsize = AtomicUsize::new(0);
16
17macro_rules! unwrap_or_return {
18    ( $e:expr ) => {
19        match $e {
20            Ok(x) => x,
21            Err(e) => {
22                error!("{:?}", e);
23                return;
24            }
25        }
26    };
27}
28
29#[derive(Serialize, Deserialize, Debug)]
30struct NodesByPriorityEntry {
31    pub node_id: String,
32    pub child_key: String,
33    pub size: u64,
34}
35
36// can be extended later with defaults https://serde.rs/attr-default.html
37#[derive(Serialize, Deserialize, Debug)]
38struct SledNodeData {
39    pub id: u64,
40    pub node_id: String,
41    pub child_key: String,
42    pub data: NodeData,
43    pub size: u64,
44    pub priority: i32,
45    pub last_opened: u64,
46    pub times_opened: u32,
47}
48
49impl SledNodeData {
50    pub fn new(node_id: String, child_key: String, data: NodeData, store: &SledStorage) -> Self {
51        let mut s = SledNodeData {
52            id: COUNTER.fetch_add(1, Ordering::Relaxed) as u64,
53            node_id,
54            child_key,
55            data,
56            size: 0,
57            priority: 0,
58            last_opened: 0,
59            times_opened: 0,
60        };
61        s.update_size();
62        s.update_priority(store, true);
63        s
64    }
65
66    fn update_size(&mut self) {
67        let size = (self.node_id.len() + self.child_key.len() + self.data.value.size()) as u64;
68        self.size = size;
69    }
70
71    pub fn update_priority(&mut self, storage: &SledStorage, force_index: bool) {
72        // TODO update prio only once per sec? or once per 10 times_opened?
73        let old_prio = self.priority;
74        let mut changed = false;
75        if let Some(first_letter) = self.node_id.chars().next() {
76            if (first_letter == '~') || (self.node_id == "#") {
77                // signed or content addressed data
78                let base_prio = match storage.config.my_pub {
79                    Some(ref my_pub) => {
80                        match self.node_id.find(my_pub) {
81                            Some(1) => 110, // prioritize our own stuff. TODO: prioritize our follows also
82                            _ => 100,
83                        }
84                    }
85                    _ => 100,
86                };
87                self.priority = base_prio
88                    + ((1000000.0 / (self.size as f64)).log10().max(0.1)
89                        * ((self.times_opened + 10) as f64).log10()) as i32;
90                debug!(
91                    "{}.{} new priority {}",
92                    self.node_id, self.child_key, self.priority
93                );
94                changed = true;
95            }
96        }
97        if !changed {
98            self.priority = ((1000000.0 / (self.size as f64)).log10().max(0.1)
99                * ((self.times_opened + 10) as f64).log10())
100            .min(99.0) as i32;
101            debug!(
102                "{}.{} new priority {}",
103                self.node_id, self.child_key, self.priority
104            );
105        }
106        if force_index || (self.priority != old_prio) {
107            // update index
108            let index = storage.get_index();
109            let old_index_key = format!("{:0>9}{}", old_prio, self.id);
110            index
111                .remove(&old_index_key)
112                .expect("failed to remove old index key");
113            let new_index_key = format!("{:0>9}{}", self.priority, self.id);
114            debug!("old {} new {}", old_index_key, new_index_key);
115            let entry = NodesByPriorityEntry {
116                node_id: self.node_id.clone(),
117                child_key: self.child_key.clone(),
118                size: self.size,
119            };
120            index
121                .insert(new_index_key, bincode::serialize(&entry).unwrap())
122                .expect("failed to write into node priority index");
123        }
124    }
125}
126
127#[derive(Clone)]
128pub struct SledStorage {
129    store: sled::Db,
130    meta: sled::Tree,
131    max_size: Option<u64>,
132    pub config: Config,
133}
134
135impl SledStorage {
136    pub fn new() -> Self {
137        Self::new_with_config(
138            Config::default(),
139            sled::Config::default().path("sled_db"),
140            None,
141        )
142    }
143
144    pub fn new_with_config(
145        config: Config,
146        sled_config: sled::Config,
147        max_size: Option<u64>,
148    ) -> Self {
149        let store = sled_config.open().unwrap();
150        let meta = store.open_tree("_meta").unwrap();
151        meta.set_merge_operator(Self::merge_meta);
152        let s = SledStorage {
153            config,
154            store,
155            meta,
156            max_size,
157        };
158        s.change_size(0);
159        s
160    }
161
162    fn merge_meta(key: &[u8], old_value: Option<&[u8]>, new_value: &[u8]) -> Option<Vec<u8>> {
163        let key = std::str::from_utf8(key).unwrap();
164        if key == "size" {
165            let new_size: u64;
166            let new_value = bincode::deserialize::<i64>(new_value).unwrap();
167            if let Some(old_value) = old_value {
168                let old_value = bincode::deserialize::<u64>(old_value).unwrap();
169                new_size = ((old_value as i64) + new_value).max(0) as u64;
170            } else {
171                new_size = new_value.max(0) as u64;
172            }
173            return Some(bincode::serialize(&new_size).unwrap());
174        }
175        Some(new_value.to_vec())
176    }
177
178    pub fn get_size(&self) -> Result<u64, &'static str> {
179        match self.meta.get(b"size") {
180            Ok(size) => {
181                let size = size.ok_or("no size found")?;
182                bincode::deserialize::<u64>(&size).or(Err("could not deserialize db size"))
183            }
184            _ => Err("sled read failed"),
185        }
186    }
187
188    fn change_size(&self, change: i64) {
189        self.meta
190            .merge(b"size", bincode::serialize(&change).unwrap())
191            .expect("sled db size update failed");
192    }
193
194    fn open_child(
195        &self,
196        node_id: &String,
197        child_key: &String,
198        bytes: &[u8],
199        update_times_opened: bool,
200    ) -> Result<SledNodeData, ()> {
201        let mut res = match bincode::deserialize::<SledNodeData>(bytes) {
202            Ok(v) => Ok(v),
203            _ => {
204                // I can see future migrations becoming a problem
205                match bincode::deserialize::<NodeData>(bytes) {
206                    Ok(v) => Ok(SledNodeData::new(
207                        node_id.clone(),
208                        child_key.clone(),
209                        v,
210                        &self,
211                    )), // migrate,
212                    _ => {
213                        error!("could not deserialize data from sled");
214                        Err(())
215                    }
216                }
217            }
218        };
219        if update_times_opened {
220            match res {
221                Ok(ref mut node) => {
222                    node.times_opened += 1;
223                    node.update_priority(&self, false);
224                }
225                _ => {}
226            }
227        }
228        res
229    }
230
231    fn get_index(&self) -> sled::Tree {
232        // sqlite could do the indexing automatically
233        self.store.open_tree("_nodes_by_priority").unwrap()
234    }
235
236    fn handle_get(&self, get: Get, ctx: &ActorContext) {
237        let res = unwrap_or_return!(self.store.get(&get.node_id.clone()));
238        if res.is_none() {
239            debug!("have not {}", get.node_id);
240            return;
241        }
242        let children = res.unwrap();
243        debug!("have {}: {:?}", get.node_id, children);
244        let children = unwrap_or_return!(self.store.open_tree(&get.node_id));
245        let reply_with_children = match &get.child_key {
246            Some(child_key) => {
247                // reply with specific child if it's found
248                let child_val = unwrap_or_return!(children.get(child_key));
249                if child_val.is_none() {
250                    debug!("requested child {} not found", child_key);
251                    return;
252                }
253                debug!("requested child {} was found", child_key);
254                let child_val = unwrap_or_return!(self.open_child(
255                    &get.node_id,
256                    child_key,
257                    &child_val.unwrap(),
258                    true
259                ));
260                let _ =
261                    children.insert(get.node_id.clone(), bincode::serialize(&child_val).unwrap());
262                let mut r = BTreeMap::new();
263                r.insert(child_key.clone(), child_val.data);
264                r
265            }
266            None => {
267                // reply with all children of this node
268                let mut r = BTreeMap::new();
269                for child in children.iter() {
270                    let (k, v) = unwrap_or_return!(child);
271                    let k = unwrap_or_return!(std::str::from_utf8(&k)).to_string();
272                    let v: SledNodeData =
273                        unwrap_or_return!(self.open_child(&get.node_id, &k, &v, true));
274                    let _ = children.insert(get.node_id.clone(), bincode::serialize(&v).unwrap());
275                    r.insert(k, v.data);
276                }
277                r
278            }
279        };
280        let mut reply_with_nodes = BTreeMap::new();
281        reply_with_nodes.insert(get.node_id.clone(), reply_with_children);
282        let mut recipients = HashSet::new();
283        recipients.insert(get.from.clone());
284        debug!("direct replying to {}", get.from);
285        let my_addr = ctx.addr.clone();
286        let mut put = Put::new(reply_with_nodes, Some(get.id.clone()), my_addr);
287        put.to_string();
288        if put.checksum != get.checksum {
289            let _ = get.from.send(Message::Put(put));
290        } else {
291            debug!("put.checksum == get.checksum, not replying with the same data");
292        }
293    }
294
295    fn handle_put(&self, put: Put) {
296        for (node_id, update_data) in put.updated_nodes.into_iter().rev() {
297            if (node_id.len() > 0) && node_id.chars().next().unwrap() == '_' {
298                continue; // _ paths reserved for our metadata. we can add escaping if needed.
299            }
300            debug!("saving k-v {}: {:?}", node_id, update_data);
301            let res = unwrap_or_return!(self.store.get(&node_id));
302            if let Some(_children) = res {
303                let children = unwrap_or_return!(self.store.open_tree(&node_id));
304                for (child_id, child_data) in update_data {
305                    if let Some(existing) = unwrap_or_return!(children.get(&child_id)) {
306                        let mut existing: SledNodeData = unwrap_or_return!(
307                            self.open_child(&node_id, &child_id, &existing, false)
308                        );
309                        if child_data.updated_at >= existing.data.updated_at {
310                            existing.data = child_data;
311                            let old_size = existing.size as i64;
312                            existing.update_size();
313                            existing.update_priority(&self, false);
314                            let _ = children
315                                .insert(child_id.clone(), bincode::serialize(&existing).unwrap());
316                            self.change_size((existing.size as i64) - old_size);
317                        }
318                    } else {
319                        let child =
320                            SledNodeData::new(node_id.clone(), child_id.clone(), child_data, &self);
321                        let _ = children.insert(child_id, bincode::serialize(&child).unwrap());
322                        self.change_size(child.size as i64);
323                    }
324                }
325            } else {
326                let _ = self.store.insert(node_id.clone(), vec![1]);
327                let children = unwrap_or_return!(self.store.open_tree(&node_id));
328                for (child_id, child_data) in update_data {
329                    let child =
330                        SledNodeData::new(node_id.clone(), child_id.clone(), child_data, &self);
331                    let _ = children.insert(child_id, bincode::serialize(&child).unwrap());
332                    self.change_size(child.size as i64);
333                }
334            }
335        }
336    }
337
338    fn evict(&self, amount: u64) {
339        let mut evicted = 0;
340        let index = self.store.open_tree("_nodes_by_priority").unwrap();
341        while evicted < amount {
342            match index.pop_min() {
343                // TODO transaction? don't pop unless the actual data was removed
344                Ok(opt) => {
345                    if let Some((_key, entry)) = opt {
346                        let entry = bincode::deserialize::<NodesByPriorityEntry>(&entry).unwrap();
347                        let node = unwrap_or_return!(self.store.open_tree(&entry.node_id));
348                        node.remove(&entry.child_key).expect("evict failed");
349                        self.change_size(0 - (entry.size as i64));
350                        evicted += entry.size;
351                    }
352                }
353                _ => break,
354            }
355        }
356        debug!("evict done");
357    }
358}
359
360#[async_trait]
361impl Actor for SledStorage {
362    async fn handle(&mut self, message: Message, ctx: &ActorContext) {
363        debug!("SledStorage incoming message {:?}", message);
364        match message {
365            Message::Get(get) => self.handle_get(get, ctx),
366            Message::Put(put) => self.handle_put(put),
367            _ => {}
368        }
369    }
370
371    async fn pre_start(&mut self, ctx: &ActorContext) {
372        info!("SledStorage adapter starting");
373        if let Some(limit) = self.max_size {
374            let storage = self.clone();
375            ctx.child_task(async move {
376                loop {
377                    sleep(Duration::from_millis(2000)).await;
378                    match storage.get_size() {
379                        Ok(size) => {
380                            if size >= limit {
381                                let excess = size - limit;
382                                info!(
383                                    "sled db too big ({} bytes), evicting lowest priority data",
384                                    size
385                                );
386                                storage.evict(excess); // TODO: size_on_disk() not shrinking (enough)
387                                sleep(Duration::from_millis(1000)).await;
388                                let new_size = storage.get_size().unwrap();
389                                info!("size after eviction {} (was {} bytes)", new_size, size);
390                            }
391                        }
392                        _ => {}
393                    }
394                }
395            });
396        }
397    }
398
399    async fn stopping(&mut self, _context: &ActorContext) {
400        info!("SledStorage adapter stopping");
401    }
402}
403
404#[cfg(test)]
405mod tests {
406    use crate::actor::{ActorContext, Addr};
407    use crate::adapters::sled_storage::SledStorage;
408    use crate::message::{Get, Message, Put};
409    use crate::types::{Children, NodeData, Value};
410    use crate::Config;
411    use tokio::sync::mpsc::unbounded_channel;
412    use tokio::time::{sleep, Duration};
413
414    #[tokio::test]
415    async fn should_not_reply_with_put_when_checksum_same_as_in_get() {
416        let path = std::path::Path::new("./sled_checksum_test_db");
417        let (sender, mut receiver) = unbounded_channel::<Message>();
418        let return_addr = Addr::new(sender);
419        let sled_storage = SledStorage::new_with_config(
420            Config::default(),
421            sled::Config::default().path(path),
422            None,
423        );
424        let mut ctx = ActorContext::new("peer_id".to_string());
425        let sled_addr = ctx.start_actor(Box::new(sled_storage));
426
427        let mut children = Children::default();
428        children.insert(
429            "about".to_string(),
430            NodeData {
431                value: Value::Text("Jesus Built My Hot Rod".to_string()),
432                updated_at: 0 as f64,
433            },
434        );
435        let mut put = Put::new_from_kv("profile".to_string(), children, return_addr.clone());
436        put.to_string();
437        let checksum = put.checksum.clone().unwrap();
438        sled_addr.send(Message::Put(put)).ok();
439        sleep(Duration::from_millis(10)).await;
440        let mut get = Get::new(
441            "profile".to_string(),
442            Some("about".to_string()),
443            return_addr.clone(),
444        );
445
446        get.checksum = Some(checksum);
447        sled_addr.send(Message::Get(get.clone())).ok();
448        sleep(Duration::from_millis(10)).await;
449        assert!(receiver.try_recv().is_err());
450
451        get.checksum = None;
452        sled_addr.send(Message::Get(get)).ok();
453        sleep(Duration::from_millis(10)).await;
454        assert!(receiver.try_recv().is_ok());
455
456        std::fs::remove_dir_all(path).ok();
457
458        ctx.stop();
459    }
460}