#![cfg(feature = "kv-surrealkv")]
mod background_flusher;
mod cnf;
mod commit_coordinator;
use std::ops::Range;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::time::Duration;
use background_flusher::BackgroundFlusher;
use chrono::{DateTime, Utc};
pub use cnf::SurrealKvConfig;
use commit_coordinator::CommitCoordinator;
use surrealkv::{
Durability, HistoryOptions, LSMIterator, Mode, Transaction as Tx, Tree, TreeBuilder,
};
use tokio::sync::RwLock;
use super::Direction;
use super::api::{BoxFut, KeysResult, ScanResult};
use super::config::SyncMode;
use super::err::{Error, Result};
use crate::key::debug::Sprintable;
use crate::kvs::api::Transactable;
use crate::kvs::timestamp::{
BoxTimeStamp, BoxTimeStampImpl, MAX_TIMESTAMP_BYTES, TimeStamp, TimeStampImpl,
};
use crate::kvs::{Key, Val};
const TARGET: &str = "surrealdb::core::kvs::surrealkv";
pub struct Datastore {
db: Tree,
versioned: bool,
commit_coordinator: Option<Arc<CommitCoordinator>>,
background_flusher: Option<Arc<BackgroundFlusher>>,
}
pub struct Transaction {
done: AtomicBool,
write: bool,
versioned: bool,
inner: RwLock<Tx>,
commit_coordinator: Option<Arc<CommitCoordinator>>,
}
impl Transaction {
fn ensure_versioned(&self, version: Option<u64>) -> Result<()> {
if !self.versioned && version.is_some() {
return Err(Error::UnsupportedVersionedQueries);
}
Ok(())
}
}
impl Datastore {
pub(crate) async fn new(path: &str, config: SurrealKvConfig) -> Result<Datastore> {
let builder = TreeBuilder::new();
info!(target: TARGET, "Enabling value log separation: {}", config.enable_vlog);
let builder = builder.with_enable_vlog(config.enable_vlog);
info!(target: TARGET, "Setting value log max file size: {}", config.vlog_max_file_size);
let builder = builder.with_vlog_max_file_size(config.vlog_max_file_size);
info!(target: TARGET, "Setting value log threshold: {}", config.vlog_threshold);
let builder = builder.with_vlog_value_threshold(config.vlog_threshold);
let retention_ns = config.retention.as_nanos().try_into().unwrap_or(u64::MAX);
info!(target: TARGET, "Versioning enabled: {} with retention period: {}ns", config.versioned, retention_ns);
let builder = builder.with_versioning(config.versioned, retention_ns);
let versioned_index = config.versioned && config.versioned_index;
info!(target: TARGET, "Versioning with versioned_index: {}", versioned_index);
let builder = builder.with_versioned_index(versioned_index);
info!(target: TARGET, "Setting max memtable size: {}", config.max_memtable_size);
let builder = builder.with_max_memtable_size(config.max_memtable_size);
info!(target: TARGET, "Setting block cache capacity: {}", config.block_cache_capacity);
let builder = builder.with_block_cache_capacity(config.block_cache_capacity);
info!(target: TARGET, "Setting block size: {}", config.block_size);
let builder = builder.with_block_size(config.block_size);
let builder = builder.with_path(path.to_string().into());
let db = builder.build().map_err(|e| Error::Datastore(e.to_string()))?;
let (commit_coordinator, background_flusher) = match config.sync_mode {
SyncMode::Every => {
info!(target: TARGET, "Sync mode: every transaction commit");
let coordinator = Arc::new(CommitCoordinator::new(db.clone(), &config)?);
(Some(coordinator), None)
}
SyncMode::Interval(interval) => {
info!(target: TARGET, "Sync mode: background syncing on interval ({}ms)", interval.as_millis());
let flusher = Arc::new(BackgroundFlusher::new(db.clone(), interval)?);
(None, Some(flusher))
}
SyncMode::Never => {
info!(target: TARGET, "Sync mode: never (handled by the OS)");
(None, None)
}
};
Ok(Datastore {
db,
versioned: config.versioned,
commit_coordinator,
background_flusher,
})
}
pub(crate) async fn shutdown(&self) -> Result<()> {
if let Some(background_flusher) = &self.background_flusher {
background_flusher.shutdown()?;
}
if let Some(commit_coordinator) = &self.commit_coordinator {
commit_coordinator.shutdown()?;
}
if let Err(e) = self.db.flush_wal(true) {
error!(target: TARGET, "An error occurred flushing the WAL buffer to disk: {e}");
}
if let Err(e) = self.db.close().await {
error!(target: TARGET, "An error occurred closing the database: {e}");
}
Ok(())
}
pub(crate) async fn transaction(&self, write: bool, _: bool) -> Result<Box<dyn Transactable>> {
let mut txn = match write {
true => self.db.begin_with_mode(Mode::ReadWrite),
false => self.db.begin_with_mode(Mode::ReadOnly),
}?;
txn.set_durability(Durability::Eventual);
Ok(Box::new(Transaction {
done: AtomicBool::new(false),
write,
versioned: self.versioned,
inner: RwLock::new(txn),
commit_coordinator: self.commit_coordinator.clone(),
}))
}
}
impl Transactable for Transaction {
fn kind(&self) -> &'static str {
"surrealkv"
}
fn closed(&self) -> bool {
self.done.load(Ordering::Relaxed)
}
fn writeable(&self) -> bool {
self.write
}
#[instrument(level = "trace", target = "surrealdb::core::kvs::api", skip(self))]
fn cancel(&self) -> BoxFut<'_, Result<()>> {
Box::pin(async move {
if self.done.swap(true, Ordering::AcqRel) {
return Err(Error::TransactionFinished);
}
let mut inner = self.inner.write().await;
inner.rollback();
Ok(())
})
}
#[instrument(level = "trace", target = "surrealdb::core::kvs::api", skip(self))]
fn commit(&self) -> BoxFut<'_, Result<()>> {
Box::pin(async move {
if self.done.swap(true, Ordering::AcqRel) {
return Err(Error::TransactionFinished);
}
if !self.writeable() {
return Err(Error::TransactionReadonly);
}
let mut inner = self.inner.write().await;
inner.commit().await?;
if let Some(coordinator) = &self.commit_coordinator {
coordinator.wait_for_sync().await?;
}
Ok(())
})
}
#[instrument(level = "trace", target = "surrealdb::core::kvs::api", skip(self), fields(key = key.sprint()))]
fn exists(&self, key: Key, version: Option<u64>) -> BoxFut<'_, Result<bool>> {
Box::pin(async move {
self.ensure_versioned(version)?;
if self.closed() {
return Err(Error::TransactionFinished);
}
let inner = self.inner.read().await;
let res = match version {
Some(ts) => inner.get_at(&key, ts)?.is_some(),
None => inner.get(&key)?.is_some(),
};
Ok(res)
})
}
#[instrument(level = "trace", target = "surrealdb::core::kvs::api", skip(self), fields(key = key.sprint()))]
fn get(&self, key: Key, version: Option<u64>) -> BoxFut<'_, Result<Option<Val>>> {
Box::pin(async move {
self.ensure_versioned(version)?;
if self.closed() {
return Err(Error::TransactionFinished);
}
let inner = self.inner.read().await;
let res = match version {
Some(ts) => inner.get_at(&key, ts)?,
None => inner.get(&key)?,
};
Ok(res)
})
}
#[instrument(level = "trace", target = "surrealdb::core::kvs::api", skip(self), fields(key = key.sprint()))]
fn set(&self, key: Key, val: Val) -> BoxFut<'_, Result<()>> {
Box::pin(async move {
if self.closed() {
return Err(Error::TransactionFinished);
}
if !self.writeable() {
return Err(Error::TransactionReadonly);
}
let mut inner = self.inner.write().await;
inner.set(&key, &val)?;
Ok(())
})
}
#[instrument(level = "trace", target = "surrealdb::core::kvs::api", skip(self), fields(key = key.sprint()))]
fn replace(&self, key: Key, val: Val) -> BoxFut<'_, Result<()>> {
Box::pin(async move {
if self.closed() {
return Err(Error::TransactionFinished);
}
if !self.writeable() {
return Err(Error::TransactionReadonly);
}
let mut inner = self.inner.write().await;
inner.replace(&key, &val)?;
Ok(())
})
}
#[instrument(level = "trace", target = "surrealdb::core::kvs::api", skip(self), fields(key = key.sprint()))]
fn put(&self, key: Key, val: Val) -> BoxFut<'_, Result<()>> {
Box::pin(async move {
if self.closed() {
return Err(Error::TransactionFinished);
}
if !self.writeable() {
return Err(Error::TransactionReadonly);
}
let mut inner = self.inner.write().await;
match inner.get(&key)? {
None => inner.set(&key, &val)?,
_ => return Err(Error::TransactionKeyAlreadyExists),
}
Ok(())
})
}
#[instrument(level = "trace", target = "surrealdb::core::kvs::api", skip(self), fields(key = key.sprint()))]
fn putc(&self, key: Key, val: Val, chk: Option<Val>) -> BoxFut<'_, Result<()>> {
Box::pin(async move {
if self.closed() {
return Err(Error::TransactionFinished);
}
if !self.writeable() {
return Err(Error::TransactionReadonly);
}
let mut inner = self.inner.write().await;
match (inner.get(&key)?, chk) {
(Some(v), Some(w)) if v == w => inner.set(&key, &val)?,
(None, None) => inner.set(&key, &val)?,
_ => return Err(Error::TransactionConditionNotMet),
};
Ok(())
})
}
#[instrument(level = "trace", target = "surrealdb::core::kvs::api", skip(self), fields(key = key.sprint()))]
fn del(&self, key: Key) -> BoxFut<'_, Result<()>> {
Box::pin(async move {
if self.closed() {
return Err(Error::TransactionFinished);
}
if !self.writeable() {
return Err(Error::TransactionReadonly);
}
let mut inner = self.inner.write().await;
if self.versioned {
inner.soft_delete(&key)?;
} else {
inner.delete(&key)?;
}
Ok(())
})
}
#[instrument(level = "trace", target = "surrealdb::core::kvs::api", skip(self), fields(key = key.sprint()))]
fn delc(&self, key: Key, chk: Option<Val>) -> BoxFut<'_, Result<()>> {
Box::pin(async move {
if self.closed() {
return Err(Error::TransactionFinished);
}
if !self.writeable() {
return Err(Error::TransactionReadonly);
}
let mut inner = self.inner.write().await;
if self.versioned {
match (inner.get(&key)?, chk) {
(Some(v), Some(w)) if v == w => inner.soft_delete(&key)?,
(None, None) => inner.soft_delete(&key)?,
_ => return Err(Error::TransactionConditionNotMet),
};
} else {
match (inner.get(&key)?, chk) {
(Some(v), Some(w)) if v == w => inner.delete(&key)?,
(None, None) => inner.delete(&key)?,
_ => return Err(Error::TransactionConditionNotMet),
};
}
Ok(())
})
}
#[instrument(level = "trace", target = "surrealdb::core::kvs::api", skip(self), fields(key = key.sprint()))]
fn clr(&self, key: Key) -> BoxFut<'_, Result<()>> {
Box::pin(async move {
if self.closed() {
return Err(Error::TransactionFinished);
}
if !self.writeable() {
return Err(Error::TransactionReadonly);
}
let mut inner = self.inner.write().await;
inner.delete(&key)?;
Ok(())
})
}
#[instrument(level = "trace", target = "surrealdb::core::kvs::api", skip(self), fields(key = key.sprint()))]
fn clrc(&self, key: Key, chk: Option<Val>) -> BoxFut<'_, Result<()>> {
Box::pin(async move {
if self.closed() {
return Err(Error::TransactionFinished);
}
if !self.writeable() {
return Err(Error::TransactionReadonly);
}
let mut inner = self.inner.write().await;
match (inner.get(&key)?, chk) {
(Some(v), Some(w)) if v == w => inner.delete(&key)?,
(None, None) => inner.delete(&key)?,
_ => return Err(Error::TransactionConditionNotMet),
};
Ok(())
})
}
#[instrument(level = "trace", target = "surrealdb::core::kvs::api", skip(self), fields(rng = rng.sprint()))]
fn count(&self, rng: Range<Key>, version: Option<u64>) -> BoxFut<'_, Result<usize>> {
Box::pin(async move {
self.ensure_versioned(version)?;
if self.closed() {
return Err(Error::TransactionFinished);
}
let beg = rng.start;
let end = rng.end;
let inner = self.inner.read().await;
let res = affinitypool::spawn_local(move || -> Result<_> {
let mut count = 0;
match version {
Some(ts) => {
let opts = HistoryOptions::new().with_tombstones(true);
let mut iter = inner.history_with_options(beg, end, &opts)?;
iter.seek_first()?;
while iter.valid() {
let key_ref = iter.key();
if key_ref.timestamp() <= ts {
let user_key = key_ref.user_key().to_vec();
let is_tombstone = key_ref.is_tombstone();
loop {
iter.next()?;
if !iter.valid() || iter.key().user_key() != user_key {
break;
}
}
if !is_tombstone {
count += 1;
}
} else {
iter.next()?;
}
}
}
None => {
let mut iter = inner.range(beg, end)?;
iter.seek_first()?;
while iter.valid() {
count += 1;
iter.next()?;
}
}
}
Ok(count)
})
.await?;
Ok(res)
})
}
#[instrument(level = "trace", target = "surrealdb::core::kvs::api", skip(self), fields(rng = rng.sprint()))]
fn keys(
&self,
rng: Range<Key>,
limit: u32,
skip: u32,
version: Option<u64>,
) -> BoxFut<'_, Result<KeysResult>> {
Box::pin(async move {
self.ensure_versioned(version)?;
if self.closed() {
return Err(Error::TransactionFinished);
}
let beg = rng.start;
let end = rng.end;
let inner = self.inner.read().await;
let res = match version {
Some(ts) => {
let mut iter = inner.history(&beg, &end)?;
iter.seek_first()?;
let mut cursor = HistoryCursor {
inner: Box::new(iter),
dir: Direction::Forward,
ts,
};
consume_keys(&mut cursor, limit, skip)?
}
None => {
let mut iter = inner.range(&beg, &end)?;
iter.seek_first()?;
let mut cursor = RangeCursor {
inner: Box::new(iter),
dir: Direction::Forward,
};
consume_keys(&mut cursor, limit, skip)?
}
};
Ok(res)
})
}
#[instrument(level = "trace", target = "surrealdb::core::kvs::api", skip(self), fields(rng = rng.sprint()))]
fn keysr(
&self,
rng: Range<Key>,
limit: u32,
skip: u32,
version: Option<u64>,
) -> BoxFut<'_, Result<KeysResult>> {
Box::pin(async move {
self.ensure_versioned(version)?;
if self.closed() {
return Err(Error::TransactionFinished);
}
let beg = rng.start;
let end = rng.end;
let inner = self.inner.read().await;
let res = match version {
Some(ts) => {
let mut iter = inner.history(&beg, &end)?;
iter.seek_last()?;
let mut cursor = HistoryCursor {
inner: Box::new(iter),
dir: Direction::Backward,
ts,
};
consume_keys(&mut cursor, limit, skip)?
}
None => {
let mut iter = inner.range(&beg, &end)?;
iter.seek_last()?;
let mut cursor = RangeCursor {
inner: Box::new(iter),
dir: Direction::Backward,
};
consume_keys(&mut cursor, limit, skip)?
}
};
Ok(res)
})
}
#[instrument(level = "trace", target = "surrealdb::core::kvs::api", skip(self), fields(rng = rng.sprint()))]
fn scan(
&self,
rng: Range<Key>,
limit: u32,
skip: u32,
version: Option<u64>,
) -> BoxFut<'_, Result<ScanResult>> {
Box::pin(async move {
self.ensure_versioned(version)?;
if self.closed() {
return Err(Error::TransactionFinished);
}
let beg = rng.start;
let end = rng.end;
let inner = self.inner.read().await;
let res = match version {
Some(ts) => {
let mut iter = inner.history(&beg, &end)?;
iter.seek_first()?;
let mut cursor = HistoryCursor {
inner: Box::new(iter),
dir: Direction::Forward,
ts,
};
consume_vals(&mut cursor, limit, skip)?
}
None => {
let mut iter = inner.range(&beg, &end)?;
iter.seek_first()?;
let mut cursor = RangeCursor {
inner: Box::new(iter),
dir: Direction::Forward,
};
consume_vals(&mut cursor, limit, skip)?
}
};
Ok(res)
})
}
#[instrument(level = "trace", target = "surrealdb::core::kvs::api", skip(self), fields(rng = rng.sprint()))]
fn scanr(
&self,
rng: Range<Key>,
limit: u32,
skip: u32,
version: Option<u64>,
) -> BoxFut<'_, Result<ScanResult>> {
Box::pin(async move {
self.ensure_versioned(version)?;
if self.closed() {
return Err(Error::TransactionFinished);
}
let beg = rng.start;
let end = rng.end;
let inner = self.inner.read().await;
let res = match version {
Some(ts) => {
let mut iter = inner.history(&beg, &end)?;
iter.seek_last()?;
let mut cursor = HistoryCursor {
inner: Box::new(iter),
dir: Direction::Backward,
ts,
};
consume_vals(&mut cursor, limit, skip)?
}
None => {
let mut iter = inner.range(&beg, &end)?;
iter.seek_last()?;
let mut cursor = RangeCursor {
inner: Box::new(iter),
dir: Direction::Backward,
};
consume_vals(&mut cursor, limit, skip)?
}
};
Ok(res)
})
}
fn new_save_point(&self) -> BoxFut<'_, Result<()>> {
Box::pin(async move {
self.inner.write().await.set_savepoint()?;
Ok(())
})
}
fn rollback_to_save_point(&self) -> BoxFut<'_, Result<()>> {
Box::pin(async move {
self.inner.write().await.rollback_to_savepoint()?;
Ok(())
})
}
fn release_last_save_point(&self) -> BoxFut<'_, Result<()>> {
Box::pin(async move { Ok(()) })
}
fn timestamp_impl(&self) -> BoxTimeStampImpl {
Box::new(SurrealKvTimeStampImpl)
}
}
struct SurrealKvTimeStamp(u64);
impl TimeStamp for SurrealKvTimeStamp {
fn as_versionstamp(&self) -> u128 {
self.0 as u128
}
fn as_datetime(&self) -> Option<DateTime<Utc>> {
Some(DateTime::from_timestamp_nanos(self.0 as i64))
}
fn sub_checked(&self, duration: Duration) -> Option<BoxTimeStamp> {
let nanos: u64 = duration.as_nanos().try_into().ok()?;
Some(BoxTimeStamp::new(SurrealKvTimeStamp(self.0.checked_sub(nanos)?)))
}
fn encode<'a>(&self, bytes: &'a mut [u8; MAX_TIMESTAMP_BYTES]) -> &'a [u8] {
bytes[..8].copy_from_slice(&self.0.to_be_bytes());
&bytes[..8]
}
}
struct SurrealKvTimeStampImpl;
impl TimeStampImpl for SurrealKvTimeStampImpl {
fn earliest(&self) -> BoxTimeStamp {
BoxTimeStamp::new(SurrealKvTimeStamp(0))
}
fn create_from_versionstamp(&self, version: u128) -> Option<BoxTimeStamp> {
Some(BoxTimeStamp::new(SurrealKvTimeStamp(version.try_into().ok()?)))
}
fn create_from_datetime(&self, dt: DateTime<Utc>) -> Option<BoxTimeStamp> {
let nanos = dt.timestamp_nanos_opt()?;
if nanos < 0 {
return None;
}
Some(BoxTimeStamp::new(SurrealKvTimeStamp(nanos as u64)))
}
fn decode(&self, bytes: &[u8]) -> Result<BoxTimeStamp> {
let bytes = <[u8; 8]>::try_from(bytes).map_err(|_| {
Error::TimestampInvalid("encoded timestamp not a valid length".to_string())
})?;
Ok(BoxTimeStamp::new(SurrealKvTimeStamp(u64::from_be_bytes(bytes))))
}
}
trait Cursor {
fn next_key(&mut self) -> Result<Option<Key>>;
fn next_entry(&mut self) -> Result<Option<(Key, Val)>>;
}
struct RangeCursor<'a> {
inner: Box<dyn LSMIterator + 'a>,
dir: Direction,
}
impl Cursor for RangeCursor<'_> {
fn next_key(&mut self) -> Result<Option<Key>> {
if self.inner.valid() {
let key = self.inner.key().user_key().to_vec();
match self.dir {
Direction::Forward => self.inner.next()?,
Direction::Backward => self.inner.prev()?,
};
return Ok(Some(key));
}
Ok(None)
}
fn next_entry(&mut self) -> Result<Option<(Key, Val)>> {
if self.inner.valid() {
let key = self.inner.key().user_key().to_vec();
let value = self.inner.value()?;
match self.dir {
Direction::Forward => self.inner.next()?,
Direction::Backward => self.inner.prev()?,
};
return Ok(Some((key, value)));
}
Ok(None)
}
}
struct HistoryCursor<'a> {
inner: Box<dyn LSMIterator + 'a>,
dir: Direction,
ts: u64,
}
impl Cursor for HistoryCursor<'_> {
fn next_key(&mut self) -> Result<Option<Key>> {
match self.dir {
Direction::Forward => {
while self.inner.valid() {
let key_ref = self.inner.key();
if key_ref.timestamp() <= self.ts {
let user_key = key_ref.user_key().to_vec();
loop {
self.inner.next()?;
if !self.inner.valid() || self.inner.key().user_key() != user_key {
break;
}
}
return Ok(Some(user_key));
}
self.inner.next()?;
}
Ok(None)
}
Direction::Backward => {
while self.inner.valid() {
let mut matched = false;
let user_key = self.inner.key().user_key().to_vec();
while self.inner.valid() && self.inner.key().user_key() == user_key {
if self.inner.key().timestamp() <= self.ts {
matched = true;
}
self.inner.prev()?;
}
if matched {
return Ok(Some(user_key));
}
}
Ok(None)
}
}
}
fn next_entry(&mut self) -> Result<Option<(Key, Val)>> {
match self.dir {
Direction::Forward => {
while self.inner.valid() {
let key_ref = self.inner.key();
if key_ref.timestamp() <= self.ts {
let user_key = key_ref.user_key().to_vec();
let value = self.inner.value()?;
loop {
self.inner.next()?;
if !self.inner.valid() || self.inner.key().user_key() != user_key {
break;
}
}
return Ok(Some((user_key, value)));
}
self.inner.next()?;
}
Ok(None)
}
Direction::Backward => {
while self.inner.valid() {
let user_key = self.inner.key().user_key().to_vec();
let mut value: Option<Val> = None;
while self.inner.valid() && self.inner.key().user_key() == user_key {
if self.inner.key().timestamp() <= self.ts {
value = Some(self.inner.value()?);
}
self.inner.prev()?;
}
if let Some(value) = value {
return Ok(Some((user_key, value)));
}
}
Ok(None)
}
}
}
}
fn consume_keys(cursor: &mut impl Cursor, limit: u32, skip: u32) -> Result<KeysResult> {
for _ in 0..skip {
if cursor.next_key()?.is_none() {
return Ok(KeysResult::default());
}
}
let mut key_bytes = 0u64;
let mut keys = Vec::with_capacity(limit.min(4096) as usize);
while keys.len() < limit as usize {
if let Some(key) = cursor.next_key()? {
key_bytes += key.len() as u64;
keys.push(key);
} else {
break;
}
}
Ok(KeysResult {
keys,
key_bytes,
})
}
fn consume_vals(cursor: &mut impl Cursor, limit: u32, skip: u32) -> Result<ScanResult> {
for _ in 0..skip {
if cursor.next_entry()?.is_none() {
return Ok(ScanResult::default());
}
}
let mut key_bytes = 0u64;
let mut value_bytes = 0u64;
let mut values = Vec::with_capacity(limit.min(4096) as usize);
while values.len() < limit as usize {
if let Some((key, value)) = cursor.next_entry()? {
key_bytes += key.len() as u64;
value_bytes += value.len() as u64;
values.push((key, value));
} else {
break;
}
}
Ok(ScanResult {
values,
key_bytes,
value_bytes,
})
}