Implement log store append and file set management (#43)

* add log store impl

* add some test

* delete failing test

* fix: concurrent close issue

* feat: use arcswap to replace unsafe AtomicPtr

* fix: use lock to protect rolling procedure.
fix: use try_recv to replace poll_recv on appender task.

* chores: 1. use direct tmp dir instead of creating TempDir instance; 2. inline some short function; 3. rename some structs; 4. optimize namespace to arc wrapper inner struct.
This commit is contained in:
Lei, Huang
2022-06-16 19:09:09 +08:00
committed by GitHub
parent 725a261b55
commit e03ac2fc2b
17 changed files with 1550 additions and 27 deletions

View File

@@ -10,6 +10,7 @@ async-trait = "0.1"
common-error = { path = "../common/error" }
datatypes = { path = "../datatypes" }
futures = "0.3"
snafu = { version = "0.7", features = ["backtraces"] }
[dev-dependencies]
async-stream = "0.3"

View File

@@ -1,9 +1,8 @@
//! LogStore APIs.
use common_error::prelude::ErrorExt;
use entry::Offset;
use crate::logstore::entry::Entry;
use crate::logstore::entry::{Entry, Id, Offset};
use crate::logstore::entry_stream::SendableEntryStream;
use crate::logstore::namespace::Namespace;
@@ -17,23 +16,28 @@ pub trait LogStore {
type Error: ErrorExt + Send + Sync;
type Namespace: Namespace;
type Entry: Entry;
type AppendResponse: AppendResponse;
/// Append an `Entry` to WAL with given namespace
async fn append(&mut self, ns: Self::Namespace, e: Self::Entry) -> Result<Offset, Self::Error>;
async fn append(
&self,
ns: Self::Namespace,
mut e: Self::Entry,
) -> Result<Self::AppendResponse, Self::Error>;
// Append a batch of entries atomically and return the offset of first entry.
async fn append_batch(
&mut self,
&self,
ns: Self::Namespace,
e: Vec<Self::Entry>,
) -> Result<Offset, Self::Error>;
) -> Result<Id, Self::Error>;
// Create a new `EntryStream` to asynchronously generates `Entry`.
async fn read(
&self,
ns: Self::Namespace,
offset: Offset,
) -> Result<SendableEntryStream<Self::Entry>, Self::Error>;
id: Id,
) -> Result<SendableEntryStream<Self::Entry, Self::Error>, Self::Error>;
// Create a new `Namespace`.
async fn create_namespace(&mut self, ns: Self::Namespace) -> Result<(), Self::Error>;
@@ -44,3 +48,9 @@ pub trait LogStore {
// List all existing namespaces.
async fn list_namespaces(&self) -> Result<Vec<Self::Namespace>, Self::Error>;
}
pub trait AppendResponse: Send + Sync {
fn entry_id(&self) -> Id;
fn offset(&self) -> Offset;
}

View File

@@ -1,14 +1,36 @@
pub type Offset = u64;
use common_error::ext::ErrorExt;
pub type Offset = usize;
pub type Epoch = u64;
pub type Id = u64;
/// Entry is the minimal data storage unit in `LogStore`.
pub trait Entry {
pub trait Entry: Send + Sync {
type Error: ErrorExt + Send + Sync;
/// Return contained data of entry.
fn data(&self) -> &[u8];
/// Return offset of entry.
/// Return entry id that monotonically increments.
fn id(&self) -> Id;
/// Return file offset of entry.
fn offset(&self) -> Offset;
fn set_offset(&mut self, offset: Offset);
fn set_id(&mut self, id: Id);
/// Returns epoch of entry.
fn epoch(&self) -> Epoch;
fn len(&self) -> usize;
fn is_empty(&self) -> bool;
fn serialize(&self) -> Vec<u8>;
fn deserialize(b: impl AsRef<[u8]>) -> Result<Self, Self::Error>
where
Self: Sized;
}

View File

@@ -1,25 +1,28 @@
use std::pin::Pin;
use common_error::prelude::ErrorExt;
use futures::Stream;
use crate::logstore::entry::Entry;
use crate::logstore::Offset;
pub trait EntryStream: Stream<Item = Vec<Self::Entry>> {
pub trait EntryStream: Stream<Item = Result<Vec<Self::Entry>, Self::Error>> {
type Error: ErrorExt;
type Entry: Entry;
fn start_offset(&self) -> Offset;
fn start_id(&self) -> u64;
}
pub type SendableEntryStream<'a, E> = Pin<Box<dyn Stream<Item = Vec<E>> + Send + 'a>>;
pub type SendableEntryStream<'a, I, E> = Pin<Box<dyn Stream<Item = Result<Vec<I>, E>> + Send + 'a>>;
#[cfg(test)]
mod tests {
use std::any::Any;
use std::task::{Context, Poll};
use futures::StreamExt;
use super::*;
use crate::logstore::entry::Epoch;
use crate::logstore::entry::{Epoch, Id, Offset};
pub struct SimpleEntry {
/// Offset of current entry
@@ -30,18 +33,61 @@ mod tests {
data: Vec<u8>,
}
use common_error::prelude::{ErrorExt, Snafu};
use snafu::{Backtrace, ErrorCompat};
#[derive(Debug, Snafu)]
#[snafu(visibility(pub))]
pub struct Error {}
impl ErrorExt for Error {
fn backtrace_opt(&self) -> Option<&Backtrace> {
ErrorCompat::backtrace(self)
}
fn as_any(&self) -> &dyn Any {
self
}
}
impl Entry for SimpleEntry {
type Error = Error;
fn data(&self) -> &[u8] {
self.data.as_slice()
&self.data
}
fn id(&self) -> Id {
0u64
}
fn offset(&self) -> Offset {
self.offset
}
fn set_offset(&mut self, _offset: Offset) {}
fn set_id(&mut self, _id: Id) {}
fn epoch(&self) -> Epoch {
self.epoch
}
fn len(&self) -> usize {
self.data.len()
}
fn is_empty(&self) -> bool {
self.data.is_empty()
}
fn serialize(&self) -> Vec<u8> {
self.data.clone()
}
fn deserialize(_b: impl AsRef<[u8]>) -> Result<Self, Self::Error> {
unimplemented!()
}
}
impl SimpleEntry {
@@ -56,20 +102,21 @@ mod tests {
}
pub struct EntryStreamImpl<'a> {
inner: SendableEntryStream<'a, SimpleEntry>,
start_offset: Offset,
inner: SendableEntryStream<'a, SimpleEntry, Error>,
start_id: u64,
}
impl<'a> EntryStream for EntryStreamImpl<'a> {
type Error = Error;
type Entry = SimpleEntry;
fn start_offset(&self) -> Offset {
self.start_offset
fn start_id(&self) -> u64 {
self.start_id
}
}
impl Stream for EntryStreamImpl<'_> {
type Item = Vec<SimpleEntry>;
type Item = Result<Vec<SimpleEntry>, Error>;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
match Pin::new(&mut self.inner).poll_next(cx) {
@@ -83,17 +130,18 @@ mod tests {
#[tokio::test]
pub async fn test_entry_stream() {
let stream = async_stream::stream!({
yield vec![SimpleEntry::new("test_entry".as_bytes(), 0, 128)]
yield Ok(vec![SimpleEntry::new("test_entry".as_bytes(), 0, 128)])
});
let mut stream_impl = EntryStreamImpl {
inner: Box::pin(stream),
start_offset: 1234,
start_id: 1234,
};
if let Some(v) = stream_impl.next().await {
assert_eq!(1, v.len());
assert_eq!(b"test_entry", v[0].data());
let vec = v.unwrap();
assert_eq!(1, vec.len());
assert_eq!(b"test_entry", vec[0].data());
}
}
}