diff --git a/src/data/account/mem.rs b/src/data/account/mem.rs new file mode 100644 index 0000000..0313d13 --- /dev/null +++ b/src/data/account/mem.rs @@ -0,0 +1,30 @@ +use crate::core::*; + +use crate::data::memcache::{GetId, MemCache}; +use crate::model::Account; + +pub struct MemAccountDataSource { + cache: MemCache, +} + +impl MemAccountDataSource { + pub fn new() -> MemAccountDataSource { + MemAccountDataSource { + cache: MemCache::new(), + } + } + + pub async fn by_id(&self, id: Id) -> Option { + self.cache.get(id).await + } + + pub async fn store(&self, account: Account) { + self.cache.put(account).await + } +} + +impl GetId for Account { + fn get_id(&self) -> Id { + self.id + } +} diff --git a/src/data/account/mod.rs b/src/data/account/mod.rs index 1b4d041..d751623 100644 --- a/src/data/account/mod.rs +++ b/src/data/account/mod.rs @@ -1,3 +1,5 @@ +mod mem; mod pg; +pub use mem::MemAccountDataSource; pub use pg::PgAccountDataSource; diff --git a/src/data/memcache.rs b/src/data/memcache.rs new file mode 100644 index 0000000..ed9b173 --- /dev/null +++ b/src/data/memcache.rs @@ -0,0 +1,95 @@ +use std::marker::PhantomData; +use tokio::sync::RwLock; +use uuid::Uuid; + +use crate::core::*; + +/// Shared in-memory LRU with multiple cache lines. +pub struct MemCache + Clone> { + lines: Vec>>, + _phantom: PhantomData, +} + +pub trait GetId { + fn get_id(&self) -> I; +} + +pub trait IndexableId: Eq { + fn compute_index(&self, modulus: usize) -> usize; +} + +impl + Clone> MemCache { + pub fn new() -> MemCache { + MemCache::with_capacity(256) + } + + pub fn with_capacity(capacity: usize) -> MemCache { + assert!(capacity > 0); + let mut lines = Vec::with_capacity(capacity); + for _ in 0..capacity { + lines.push(RwLock::new(None)); + } + MemCache { lines, _phantom: PhantomData } + } + + pub async fn get(&self, id: I) -> Option { + let index = id.compute_index(self.lines.len()); + let guard = self.lines[index].read().await; + let entry = guard.as_ref()?; + if entry.get_id() == id { + Some(entry.clone()) + } else { + None + } + } + + pub async fn put(&self, entry: T) { + let index = entry.get_id().compute_index(self.lines.len()); + let mut guard = self.lines[index].write().await; + *guard = Some(entry); + } +} + +impl IndexableId for Id { + fn compute_index(&self, modulus: usize) -> usize { + *self as usize % modulus + } +} + +impl IndexableId for Uuid { + fn compute_index(&self, modulus: usize) -> usize { + self.as_u128() as usize % modulus + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[derive(Clone, Debug, PartialEq)] + struct TestEntry { + id: Id, + } + + impl GetId for TestEntry { + fn get_id(&self) -> Id { + self.id + } + } + + #[actix_web::test] + async fn store_stuff() { + let cache = MemCache::with_capacity(256); + for id in 0..256 { + let entry = TestEntry { id }; + cache.put(entry.clone()).await; + assert_eq!(cache.get(id).await, Some(entry)); + } + for id in 256..512 { + let entry = TestEntry { id }; + cache.put(entry.clone()).await; + assert_eq!(cache.get(id - 256).await, None); + assert_eq!(cache.get(id).await, Some(entry)); + } + } +} diff --git a/src/data/mod.rs b/src/data/mod.rs index 4e68b81..a2e1603 100644 --- a/src/data/mod.rs +++ b/src/data/mod.rs @@ -1,3 +1,6 @@ +/// Small support utility for in-memory cache. +pub mod memcache; + pub mod account; pub mod follow; pub mod like; diff --git a/src/data/note/mem.rs b/src/data/note/mem.rs new file mode 100644 index 0000000..592b2c1 --- /dev/null +++ b/src/data/note/mem.rs @@ -0,0 +1,30 @@ +use crate::core::*; + +use crate::data::memcache::{GetId, MemCache}; +use crate::model::Note; + +pub struct MemNoteDataSource { + cache: MemCache, +} + +impl MemNoteDataSource { + pub fn new() -> MemNoteDataSource { + MemNoteDataSource { + cache: MemCache::new(), + } + } + + pub async fn by_id(&self, id: Id) -> Option { + self.cache.get(id).await + } + + pub async fn store(&self, note: Note) { + self.cache.put(note).await + } +} + +impl GetId for Note { + fn get_id(&self) -> Id { + self.id + } +} diff --git a/src/data/note/mod.rs b/src/data/note/mod.rs index 74388aa..f496350 100644 --- a/src/data/note/mod.rs +++ b/src/data/note/mod.rs @@ -1,3 +1,5 @@ +mod mem; mod pg; +pub use mem::MemNoteDataSource; pub use pg::PgNoteDataSource; diff --git a/src/data/object/mem.rs b/src/data/object/mem.rs new file mode 100644 index 0000000..de1f51a --- /dev/null +++ b/src/data/object/mem.rs @@ -0,0 +1,30 @@ +use uuid::Uuid; + +use crate::data::memcache::{GetId, MemCache}; +use crate::model::Object; + +pub struct MemObjectDataSource { + cache: MemCache, +} + +impl MemObjectDataSource { + pub fn new() -> MemObjectDataSource { + MemObjectDataSource { + cache: MemCache::new(), + } + } + + pub async fn by_id(&self, id: Uuid) -> Option { + self.cache.get(id).await + } + + pub async fn store(&self, obj: Object) { + self.cache.put(obj).await + } +} + +impl GetId for Object { + fn get_id(&self) -> Uuid { + self.id + } +} diff --git a/src/data/object/mod.rs b/src/data/object/mod.rs index 568fa16..e1d08ee 100644 --- a/src/data/object/mod.rs +++ b/src/data/object/mod.rs @@ -1,3 +1,5 @@ -pub mod pg; +mod mem; +mod pg; +pub use mem::MemObjectDataSource; pub use pg::PgObjectDataSource; diff --git a/src/data/user/mem.rs b/src/data/user/mem.rs new file mode 100644 index 0000000..81ce8f1 --- /dev/null +++ b/src/data/user/mem.rs @@ -0,0 +1,30 @@ +use crate::core::*; + +use crate::data::memcache::{GetId, MemCache}; +use crate::model::User; + +pub struct MemUserDataSource { + cache: MemCache, +} + +impl MemUserDataSource { + pub fn new() -> MemUserDataSource { + MemUserDataSource { + cache: MemCache::new(), + } + } + + pub async fn by_id(&self, id: Id) -> Option { + self.cache.get(id).await + } + + pub async fn store(&self, user: User) { + self.cache.put(user).await + } +} + +impl GetId for User { + fn get_id(&self) -> Id { + self.id + } +} diff --git a/src/data/user/mod.rs b/src/data/user/mod.rs index 05bd283..c2eb77a 100644 --- a/src/data/user/mod.rs +++ b/src/data/user/mod.rs @@ -1,3 +1,5 @@ -pub mod pg; +mod mem; +mod pg; +pub use mem::MemUserDataSource; pub use pg::PgUserDataSource; diff --git a/src/model/account.rs b/src/model/account.rs index 3ef5f4a..c615f56 100644 --- a/src/model/account.rs +++ b/src/model/account.rs @@ -5,7 +5,7 @@ use sqlx::FromRow; use crate::core::Id; use crate::util::validate::{ResultBuilder, Validate}; -#[derive(Deserialize, Serialize, FromRow)] +#[derive(Clone, Deserialize, Serialize, FromRow)] pub struct Account { pub id: Id, pub iri: Option, diff --git a/src/model/note.rs b/src/model/note.rs index b463198..5cc7b7f 100644 --- a/src/model/note.rs +++ b/src/model/note.rs @@ -4,7 +4,7 @@ use sqlx::FromRow; use crate::core::*; -#[derive(Deserialize, Serialize, FromRow)] +#[derive(Clone, Deserialize, Serialize, FromRow)] pub struct Note { pub id: Id, pub account_id: Id, diff --git a/src/model/object.rs b/src/model/object.rs index b30784c..4d1a872 100644 --- a/src/model/object.rs +++ b/src/model/object.rs @@ -2,7 +2,7 @@ use chrono::NaiveDateTime; use sqlx::types::Uuid; use sqlx::FromRow; -#[derive(FromRow)] +#[derive(Clone, FromRow)] pub struct Object { pub id: Uuid, pub iri: String, diff --git a/src/model/user.rs b/src/model/user.rs index b88a8ad..f782fa2 100644 --- a/src/model/user.rs +++ b/src/model/user.rs @@ -4,7 +4,7 @@ use sqlx::FromRow; use crate::core::*; use crate::util::validate::{ResultBuilder, Validate}; -#[derive(Deserialize, Serialize, FromRow)] +#[derive(Clone, Deserialize, Serialize, FromRow)] pub struct User { pub id: Id, pub account_id: Id, diff --git a/src/repo/account.rs b/src/repo/account.rs index 9291fae..aa451be 100644 --- a/src/repo/account.rs +++ b/src/repo/account.rs @@ -1,17 +1,19 @@ use sqlx::PgPool; use crate::core::*; -use crate::data::account::PgAccountDataSource; +use crate::data::account::{MemAccountDataSource, PgAccountDataSource}; use crate::model::account::{Account, NewAccount}; pub struct AccountRepo { db: PgAccountDataSource, + mem: MemAccountDataSource, } impl AccountRepo { pub fn new(db_pool: PgPool) -> AccountRepo { AccountRepo { db: PgAccountDataSource::new(db_pool), + mem: MemAccountDataSource::new(), } } @@ -19,7 +21,11 @@ impl AccountRepo { where I: Into + Send, { - self.db.by_id(id.into()).await + let id = id.into(); + match self.mem.by_id(id).await { + Some(account) => Ok(account), + None => self.db.by_id(id).await, + } } pub async fn create(&self, new: T) -> Result @@ -28,6 +34,8 @@ impl AccountRepo { E: Into, { let new = new.try_into().map_err(|e| e.into())?.inner(); - self.db.create(new).await + let account = self.db.create(new).await?; + self.mem.store(account.clone()).await; + Ok(account) } } diff --git a/src/repo/note.rs b/src/repo/note.rs index 974c0c5..384323b 100644 --- a/src/repo/note.rs +++ b/src/repo/note.rs @@ -2,17 +2,19 @@ use chrono::NaiveDateTime; use sqlx::PgPool; use crate::core::*; -use crate::data::PgNoteDataSource; +use crate::data::{MemNoteDataSource, PgNoteDataSource}; use crate::model::{NewNote, Note}; pub struct NoteRepo { db: PgNoteDataSource, + mem: MemNoteDataSource, } impl NoteRepo { pub fn new(db_pool: PgPool) -> NoteRepo { NoteRepo { db: PgNoteDataSource::new(db_pool), + mem: MemNoteDataSource::new(), } } @@ -20,7 +22,11 @@ impl NoteRepo { where I: Into + Send, { - self.db.by_id(id.into()).await + let id = id.into(); + match self.mem.by_id(id).await { + Some(note) => Ok(note), + None => self.db.by_id(id).await, + } } pub async fn by_account(&self, account_id: I, since: NaiveDateTime) -> Result> @@ -36,6 +42,8 @@ impl NoteRepo { E: Into, { let new = new.try_into().map_err(|e| e.into())?.inner(); - self.db.create(new).await + let note = self.db.create(new).await?; + self.mem.store(note.clone()).await; + Ok(note) } } diff --git a/src/repo/object.rs b/src/repo/object.rs index c82f79e..a445730 100644 --- a/src/repo/object.rs +++ b/src/repo/object.rs @@ -2,22 +2,26 @@ use sqlx::types::Uuid; use sqlx::PgPool; use crate::core::*; -use crate::data::object::PgObjectDataSource; +use crate::data::object::{MemObjectDataSource, PgObjectDataSource}; use crate::model::{NewObject, Object}; pub struct ObjectRepo { db: PgObjectDataSource, + mem: MemObjectDataSource, } impl ObjectRepo { pub fn new(db_pool: PgPool) -> ObjectRepo { ObjectRepo { db: PgObjectDataSource::new(db_pool), + mem: MemObjectDataSource::new(), } } pub async fn create(&self, new: NewObject) -> Result { - self.db.create(new).await + let obj = self.db.create(new).await?; + self.mem.store(obj.clone()).await; + Ok(obj) } pub async fn update(&self, object: T) -> Result @@ -35,7 +39,11 @@ impl ObjectRepo { where U: Into, { - self.db.by_id(id.into()).await + let id = id.into(); + match self.mem.by_id(id).await { + Some(obj) => Ok(obj), + None => self.db.by_id(id).await, + } } pub async fn by_iri(&self, iri: &str) -> Result { diff --git a/src/repo/user.rs b/src/repo/user.rs index fd1820f..495e4fb 100644 --- a/src/repo/user.rs +++ b/src/repo/user.rs @@ -1,17 +1,19 @@ use sqlx::PgPool; use crate::core::*; -use crate::data::user::PgUserDataSource; +use crate::data::user::{MemUserDataSource, PgUserDataSource}; use crate::model::user::{NewUser, User}; pub struct UserRepo { db: PgUserDataSource, + mem: MemUserDataSource, } impl UserRepo { pub fn new(db_pool: PgPool) -> UserRepo { UserRepo { db: PgUserDataSource::new(db_pool), + mem: MemUserDataSource::new(), } } @@ -19,7 +21,11 @@ impl UserRepo { where I: Into + Send, { - self.db.by_id(id.into()).await + let id = id.into(); + match self.mem.by_id(id).await { + Some(user) => Ok(user), + None => self.db.by_id(id).await, + } } pub async fn by_account(&self, account_id: I) -> Result @@ -41,8 +47,9 @@ impl UserRepo { T: TryInto, Error = E> + Send, E: Into, { - self.db - .create(new.try_into().map_err(|e| e.into())?.inner()) - .await + let new = new.try_into().map_err(|e| e.into())?.inner(); + let user = self.db.create(new).await?; + self.mem.store(user.clone()).await; + Ok(user) } }