Explorar o código

script/research: sled_playground added

aggstam %!s(int64=3) %!d(string=hai) anos
pai
achega
38d9eafa31

+ 2 - 0
script/research/sled_playground/.gitignore

@@ -0,0 +1,2 @@
+/target
+Cargo.lock

+ 11 - 0
script/research/sled_playground/Cargo.toml

@@ -0,0 +1,11 @@
+[package]
+name = "sled_playground"
+version = "0.4.1"
+authors = ["Dyne.org foundation <foundation@dyne.org>"]
+license = "AGPL-3.0-only"
+edition = "2021"
+
+[dependencies]
+sled = "0.34.7"
+
+[workspace]

+ 215 - 0
script/research/sled_playground/src/main.rs

@@ -0,0 +1,215 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2023 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use sled::{Config, Db, IVec};
+
+#[derive(Debug, PartialEq)]
+struct MyBullshitError;
+
+struct SledCache {
+    // A sequence to execute
+    sequence: Vec<(IVec, Option<IVec>)>
+}
+
+impl SledCache {
+    fn new() -> Self {
+        Self {sequence: vec![]}
+    }
+    
+    // Check if a key is cached
+    fn get<K: AsRef<[u8]> + Copy>(&self, key: K) -> Option<IVec> 
+    where 
+        K: Into<IVec>
+    {
+        let key = key.into();
+        // TODO: optimize this
+        for pair in &self.sequence {
+            if pair.0 == key {
+                return pair.1.clone()
+            }
+        }
+        None
+    }
+    
+    // Set a key to a new value
+    fn push<K>(&mut self, key: K, value: Option<IVec>)
+    where
+        K: Into<IVec>,
+    {
+        self.sequence.push((key.into(), value));
+    }
+    
+    // Set a key to a new value at front of sequence
+    fn insert<K>(&mut self, key: K, value: Option<IVec>)
+    where
+        K: Into<IVec>,
+    {
+        self.sequence.insert(0, (key.into(), value));
+    }
+}
+
+struct SledOverlay {
+    // Actual sled storage
+    sled: Db,    
+    // Cached writes sequence
+    // NOTE: this should be per Tree
+    writes: SledCache,
+}
+
+impl SledOverlay {
+    fn new(sled: Db) -> Self {
+        Self {sled, writes: SledCache::new()}
+    }
+    
+    // Check if a key is cached for changes, otherwise get it
+    // from sled directly
+    fn get<K: AsRef<[u8]> + Copy>(&self, key: K) -> Result<Option<IVec>, sled::Error> 
+    where 
+        K: Into<IVec>
+    {
+        if let Some(v) = self.writes.get(&key.into()) {
+            return Ok(Some(v.clone()))
+        }
+        
+        self.sled.get(key)
+    }
+    
+    // Set a key to a new value
+    fn insert<K, V>(&mut self, key: K, value: V)
+    where
+        K: Into<IVec>,
+        V: Into<IVec>,
+    {
+        self.writes.push(key, Some(value.into()));
+    }
+    
+    // Apply all writes and flush sled db
+    fn flush(&mut self) -> Result<(), sled::Error> {
+        let mut rollback = SledCache::new();
+        for (k, v_opt) in &self.writes.sequence {
+            if let Some(v) = v_opt {
+                let old = self.sled.insert(k, v)?;
+                rollback.insert(k, old);
+            } else {
+                let old = self.sled.remove(k)?;
+                rollback.insert(k, old);
+            }
+        }
+        
+        self.sled.flush()?;
+        self.writes = SledCache::new();
+        
+        Ok(())
+    }
+    
+    // Apply all writes, rollback to previous and flush sled db
+    fn flush_rollback(&mut self) -> Result<(), sled::Error> {
+        // Rollback keeps track of writes in reverse order
+        let mut rollback = SledCache::new();
+        let res = {
+            for (k, v_opt) in &self.writes.sequence {
+                if let Some(v) = v_opt {
+                    let old = self.sled.insert(k, v)?;
+                    rollback.insert(k, old);
+                } else {
+                    let old = self.sled.remove(k)?;
+                    rollback.insert(k, old);
+                }
+            }
+            
+            MyBullshitError
+        };
+        
+        // Execute rollback in case of error
+        if res == MyBullshitError {
+            for (k, v_opt) in &rollback.sequence {
+                if let Some(v) = v_opt {
+                    let _old = self.sled.insert(k, v)?;
+                } else {
+                    let _old = self.sled.remove(k)?;
+                }
+            }
+        }
+        
+        self.sled.flush()?;
+        self.writes = SledCache::new();
+        
+        Ok(())
+    }
+}
+
+// This examples showcases a serial apply of writes
+// where if one fails we can detect it and rollback.
+fn main() -> Result<(), sled::Error> {
+    // Initialize database overlay
+    let config = Config::new().temporary(true);
+    let db = config.open()?;
+    let mut overlay = SledOverlay::new(db.clone());
+    
+    // Insert some values to cache
+    overlay.insert("key_a", "val_a");
+    overlay.insert("key_b", "val_b");
+    overlay.insert("key_c", "val_c");
+    
+    assert_eq!(overlay.get("key_a")?, Some("val_a".into()));
+    assert_eq!(overlay.get("key_b")?, Some("val_b".into()));
+    assert_eq!(overlay.get("key_c")?, Some("val_c".into()));
+    
+    // Verify they are not in sled
+    assert_eq!(db.get(b"key_a")?, None);
+    assert_eq!(db.get(b"key_b")?, None);
+    assert_eq!(db.get(b"key_c")?, None);
+    
+    // Now we write them to sled
+    overlay.flush()?;
+    
+    // Verify sled contains keys
+    assert_eq!(db.get("key_a")?, Some("val_a".into()));
+    assert_eq!(db.get("key_b")?, Some("val_b".into()));
+    assert_eq!(db.get("key_c")?, Some("val_c".into()));
+    
+    // Perform the same steps, but assume an error occured
+    // during flashing, so the sled performs a rollback.
+    let config = Config::new().temporary(true);
+    let db = config.open()?;
+    let mut overlay = SledOverlay::new(db.clone());
+    
+    // Insert some values to cache
+    overlay.insert("key_a", "val_a");
+    overlay.insert("key_b", "val_b");
+    overlay.insert("key_c", "val_c");
+    
+    assert_eq!(overlay.get("key_a")?, Some("val_a".into()));
+    assert_eq!(overlay.get("key_b")?, Some("val_b".into()));
+    assert_eq!(overlay.get("key_c")?, Some("val_c".into()));
+    
+    // Verify they are not in sled
+    assert_eq!(db.get(b"key_a")?, None);
+    assert_eq!(db.get(b"key_b")?, None);
+    assert_eq!(db.get(b"key_c")?, None);
+    
+    // Now we write them to sled
+    overlay.flush_rollback()?;
+    
+    // Verify they are not in sled
+    assert_eq!(db.get(b"key_a")?, None);
+    assert_eq!(db.get(b"key_b")?, None);
+    assert_eq!(db.get(b"key_c")?, None);
+
+    Ok(())
+}