// Copyright 2019 The Grin Developers // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //! Storage of core types using LMDB. use std::fs; use std::marker; use std::sync::Arc; use lmdb_zero as lmdb; use lmdb_zero::traits::CreateCursor; use lmdb_zero::LmdbResultExt; use crate::core::ser::{self, ProtocolVersion}; use crate::util::{RwLock, RwLockReadGuard}; /// number of bytes to grow the database by when needed pub const ALLOC_CHUNK_SIZE: usize = 134_217_728; //128 MB const RESIZE_PERCENT: f32 = 0.9; /// Want to ensure that each resize gives us at least this % /// of total space free const RESIZE_MIN_TARGET_PERCENT: f32 = 0.65; /// Main error type for this lmdb #[derive(Clone, Eq, PartialEq, Debug, Fail)] pub enum Error { /// Couldn't find what we were looking for #[fail(display = "DB Not Found Error: {}", _0)] NotFoundErr(String), /// Wraps an error originating from RocksDB (which unfortunately returns /// string errors). #[fail(display = "LMDB error")] LmdbErr(lmdb::error::Error), /// Wraps a serialization error for Writeable or Readable #[fail(display = "Serialization Error")] SerErr(String), } impl From for Error { fn from(e: lmdb::error::Error) -> Error { Error::LmdbErr(e) } } /// unwraps the inner option by converting the none case to a not found error pub fn option_to_not_found(res: Result, Error>, field_name: F) -> Result where F: Fn() -> String, { match res { Ok(None) => Err(Error::NotFoundErr(field_name())), Ok(Some(o)) => Ok(o), Err(e) => Err(e), } } const DEFAULT_DB_VERSION: ProtocolVersion = ProtocolVersion(2); /// LMDB-backed store facilitating data access and serialization. All writes /// are done through a Batch abstraction providing atomicity. pub struct Store { env: Arc, db: Arc>>>>, name: String, version: ProtocolVersion, } impl Store { /// Create a new LMDB env under the provided directory. /// By default creates an environment named "lmdb". /// Be aware of transactional semantics in lmdb /// (transactions are per environment, not per database). pub fn new( root_path: &str, env_name: Option<&str>, db_name: Option<&str>, max_readers: Option, ) -> Result { let name = match env_name { Some(n) => n.to_owned(), None => "lmdb".to_owned(), }; let db_name = match db_name { Some(n) => n.to_owned(), None => "lmdb".to_owned(), }; let full_path = [root_path.to_owned(), name.clone()].join("/"); fs::create_dir_all(&full_path) .expect("Unable to create directory 'db_root' to store chain_data"); let mut env_builder = lmdb::EnvBuilder::new().unwrap(); env_builder.set_maxdbs(8)?; if let Some(max_readers) = max_readers { env_builder.set_maxreaders(max_readers)?; } let env = unsafe { env_builder.open(&full_path, lmdb::open::NOTLS, 0o600)? }; debug!( "DB Mapsize for {} is {}", full_path, env.info().as_ref().unwrap().mapsize ); let res = Store { env: Arc::new(env), db: Arc::new(RwLock::new(None)), name: db_name, version: DEFAULT_DB_VERSION, }; { let mut w = res.db.write(); *w = Some(Arc::new(lmdb::Database::open( res.env.clone(), Some(&res.name), &lmdb::DatabaseOptions::new(lmdb::db::CREATE), )?)); } Ok(res) } /// Construct a new store using a specific protocol version. /// Permits access to the db with legacy protocol versions for db migrations. pub fn with_version(&self, version: ProtocolVersion) -> Store { Store { env: self.env.clone(), db: self.db.clone(), name: self.name.clone(), version: version, } } /// Opens the database environment pub fn open(&self) -> Result<(), Error> { let mut w = self.db.write(); *w = Some(Arc::new(lmdb::Database::open( self.env.clone(), Some(&self.name), &lmdb::DatabaseOptions::new(lmdb::db::CREATE), )?)); Ok(()) } /// Determines whether the environment needs a resize based on a simple percentage threshold pub fn needs_resize(&self) -> Result { let env_info = self.env.info()?; let stat = self.env.stat()?; let size_used = stat.psize as usize * env_info.last_pgno; trace!("DB map size: {}", env_info.mapsize); trace!("Space used: {}", size_used); trace!("Space remaining: {}", env_info.mapsize - size_used); let resize_percent = RESIZE_PERCENT; trace!( "Percent used: {:.*} Percent threshold: {:.*}", 4, size_used as f64 / env_info.mapsize as f64, 4, resize_percent ); if size_used as f32 / env_info.mapsize as f32 > resize_percent || env_info.mapsize < ALLOC_CHUNK_SIZE { trace!("Resize threshold met (percent-based)"); Ok(true) } else { trace!("Resize threshold not met (percent-based)"); Ok(false) } } /// Increments the database size by as many ALLOC_CHUNK_SIZES /// to give a minimum threshold of free space pub fn do_resize(&self) -> Result<(), Error> { let env_info = self.env.info()?; let stat = self.env.stat()?; let size_used = stat.psize as usize * env_info.last_pgno; let new_mapsize = if env_info.mapsize < ALLOC_CHUNK_SIZE { ALLOC_CHUNK_SIZE } else { let mut tot = env_info.mapsize; while size_used as f32 / tot as f32 > RESIZE_MIN_TARGET_PERCENT { tot += ALLOC_CHUNK_SIZE; } tot }; // close let mut w = self.db.write(); *w = None; unsafe { self.env.set_mapsize(new_mapsize)?; } *w = Some(Arc::new(lmdb::Database::open( self.env.clone(), Some(&self.name), &lmdb::DatabaseOptions::new(lmdb::db::CREATE), )?)); info!( "Resized database from {} to {}", env_info.mapsize, new_mapsize ); Ok(()) } /// Gets a value from the db, provided its key pub fn get(&self, key: &[u8]) -> Result>, Error> { let db = self.db.read(); let txn = lmdb::ReadTransaction::new(self.env.clone())?; let access = txn.access(); let res = access.get(&db.as_ref().unwrap(), key); res.map(|res: &[u8]| res.to_vec()) .to_opt() .map_err(From::from) } /// Gets a `Readable` value from the db, provided its key. Encapsulates /// serialization. pub fn get_ser(&self, key: &[u8]) -> Result, Error> { let db = self.db.read(); let txn = lmdb::ReadTransaction::new(self.env.clone())?; let access = txn.access(); self.get_ser_access(key, &access, db) } fn get_ser_access( &self, key: &[u8], access: &lmdb::ConstAccessor<'_>, db: RwLockReadGuard<'_, Option>>>, ) -> Result, Error> { let res: lmdb::error::Result<&[u8]> = access.get(&db.as_ref().unwrap(), key); match res.to_opt() { Ok(Some(mut res)) => match ser::deserialize(&mut res, self.version) { Ok(res) => Ok(Some(res)), Err(e) => Err(Error::SerErr(format!("{}", e))), }, Ok(None) => Ok(None), Err(e) => Err(From::from(e)), } } /// Whether the provided key exists pub fn exists(&self, key: &[u8]) -> Result { let db = self.db.read(); let txn = lmdb::ReadTransaction::new(self.env.clone())?; let access = txn.access(); let res: lmdb::error::Result<&lmdb::Ignore> = access.get(&db.as_ref().unwrap(), key); res.to_opt().map(|r| r.is_some()).map_err(From::from) } /// Produces an iterator of (key, value) pairs, where values are `Readable` types /// moving forward from the provided key. pub fn iter(&self, from: &[u8]) -> Result, Error> { let db = self.db.read(); let tx = Arc::new(lmdb::ReadTransaction::new(self.env.clone())?); let cursor = Arc::new(tx.cursor(db.as_ref().unwrap().clone()).unwrap()); Ok(SerIterator { tx, cursor, seek: false, prefix: from.to_vec(), version: self.version, _marker: marker::PhantomData, }) } /// Builds a new batch to be used with this store. pub fn batch(&self) -> Result, Error> { // check if the db needs resizing before returning the batch if self.needs_resize()? { self.do_resize()?; } let tx = lmdb::WriteTransaction::new(self.env.clone())?; Ok(Batch { store: self, tx }) } } /// Batch to write multiple Writeables to db in an atomic manner. pub struct Batch<'a> { store: &'a Store, tx: lmdb::WriteTransaction<'a>, } impl<'a> Batch<'a> { /// Writes a single key/value pair to the db pub fn put(&self, key: &[u8], value: &[u8]) -> Result<(), Error> { let db = self.store.db.read(); self.tx .access() .put(&db.as_ref().unwrap(), key, value, lmdb::put::Flags::empty())?; Ok(()) } /// Writes a single key and its `Writeable` value to the db. /// Encapsulates serialization using the (default) version configured on the store instance. pub fn put_ser(&self, key: &[u8], value: &W) -> Result<(), Error> { self.put_ser_with_version(key, value, self.store.version) } /// Writes a single key and its `Writeable` value to the db. /// Encapsulates serialization using the specified protocol version. pub fn put_ser_with_version( &self, key: &[u8], value: &W, version: ProtocolVersion, ) -> Result<(), Error> { let ser_value = ser::ser_vec(value, version); match ser_value { Ok(data) => self.put(key, &data), Err(err) => Err(Error::SerErr(format!("{}", err))), } } /// gets a value from the db, provided its key pub fn get(&self, key: &[u8]) -> Result>, Error> { self.store.get(key) } /// Whether the provided key exists pub fn exists(&self, key: &[u8]) -> Result { self.store.exists(key) } /// Produces an iterator of `Readable` types moving forward from the /// provided key. pub fn iter(&self, from: &[u8]) -> Result, Error> { self.store.iter(from) } /// Gets a `Readable` value from the db, provided its key, taking the /// content of the current batch into account. pub fn get_ser(&self, key: &[u8]) -> Result, Error> { let access = self.tx.access(); let db = self.store.db.read(); self.store.get_ser_access(key, &access, db) } /// Deletes a key/value pair from the db pub fn delete(&self, key: &[u8]) -> Result<(), Error> { let db = self.store.db.read(); self.tx.access().del_key(&db.as_ref().unwrap(), key)?; Ok(()) } /// Writes the batch to db pub fn commit(self) -> Result<(), Error> { self.tx.commit()?; Ok(()) } /// Creates a child of this batch. It will be merged with its parent on /// commit, abandoned otherwise. pub fn child(&mut self) -> Result, Error> { Ok(Batch { store: self.store, tx: self.tx.child_tx()?, }) } } /// An iterator that produces Readable instances back. Wraps the lower level /// DBIterator and deserializes the returned values. pub struct SerIterator where T: ser::Readable, { tx: Arc>, cursor: Arc>, seek: bool, prefix: Vec, version: ProtocolVersion, _marker: marker::PhantomData, } impl Iterator for SerIterator where T: ser::Readable, { type Item = (Vec, T); fn next(&mut self) -> Option<(Vec, T)> { let access = self.tx.access(); let kv = if self.seek { Arc::get_mut(&mut self.cursor).unwrap().next(&access) } else { self.seek = true; Arc::get_mut(&mut self.cursor) .unwrap() .seek_range_k(&access, &self.prefix[..]) }; match kv { Ok((k, v)) => self.deser_if_prefix_match(k, v), Err(_) => None, } } } impl SerIterator where T: ser::Readable, { fn deser_if_prefix_match(&self, key: &[u8], value: &[u8]) -> Option<(Vec, T)> { let plen = self.prefix.len(); if plen == 0 || (key.len() >= plen && key[0..plen] == self.prefix[..]) { if let Ok(value) = ser::deserialize(&mut &value[..], self.version) { Some((key.to_vec(), value)) } else { None } } else { None } } }