data: add in-memory cache
This commit is contained in:
parent
9eb7601f9d
commit
8ea3ebda54
18 changed files with 277 additions and 20 deletions
30
src/data/account/mem.rs
Normal file
30
src/data/account/mem.rs
Normal file
|
@ -0,0 +1,30 @@
|
|||
use crate::core::*;
|
||||
|
||||
use crate::data::memcache::{GetId, MemCache};
|
||||
use crate::model::Account;
|
||||
|
||||
pub struct MemAccountDataSource {
|
||||
cache: MemCache<Id, Account>,
|
||||
}
|
||||
|
||||
impl MemAccountDataSource {
|
||||
pub fn new() -> MemAccountDataSource {
|
||||
MemAccountDataSource {
|
||||
cache: MemCache::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn by_id(&self, id: Id) -> Option<Account> {
|
||||
self.cache.get(id).await
|
||||
}
|
||||
|
||||
pub async fn store(&self, account: Account) {
|
||||
self.cache.put(account).await
|
||||
}
|
||||
}
|
||||
|
||||
impl GetId<Id> for Account {
|
||||
fn get_id(&self) -> Id {
|
||||
self.id
|
||||
}
|
||||
}
|
|
@ -1,3 +1,5 @@
|
|||
mod mem;
|
||||
mod pg;
|
||||
|
||||
pub use mem::MemAccountDataSource;
|
||||
pub use pg::PgAccountDataSource;
|
||||
|
|
95
src/data/memcache.rs
Normal file
95
src/data/memcache.rs
Normal file
|
@ -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<I: IndexableId, T: GetId<I> + Clone> {
|
||||
lines: Vec<RwLock<Option<T>>>,
|
||||
_phantom: PhantomData<I>,
|
||||
}
|
||||
|
||||
pub trait GetId<I: IndexableId> {
|
||||
fn get_id(&self) -> I;
|
||||
}
|
||||
|
||||
pub trait IndexableId: Eq {
|
||||
fn compute_index(&self, modulus: usize) -> usize;
|
||||
}
|
||||
|
||||
impl<I: IndexableId, T: GetId<I> + Clone> MemCache<I, T> {
|
||||
pub fn new() -> MemCache<I, T> {
|
||||
MemCache::with_capacity(256)
|
||||
}
|
||||
|
||||
pub fn with_capacity(capacity: usize) -> MemCache<I, T> {
|
||||
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<T> {
|
||||
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<Id> 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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,3 +1,6 @@
|
|||
/// Small support utility for in-memory cache.
|
||||
pub mod memcache;
|
||||
|
||||
pub mod account;
|
||||
pub mod follow;
|
||||
pub mod like;
|
||||
|
|
30
src/data/note/mem.rs
Normal file
30
src/data/note/mem.rs
Normal file
|
@ -0,0 +1,30 @@
|
|||
use crate::core::*;
|
||||
|
||||
use crate::data::memcache::{GetId, MemCache};
|
||||
use crate::model::Note;
|
||||
|
||||
pub struct MemNoteDataSource {
|
||||
cache: MemCache<Id, Note>,
|
||||
}
|
||||
|
||||
impl MemNoteDataSource {
|
||||
pub fn new() -> MemNoteDataSource {
|
||||
MemNoteDataSource {
|
||||
cache: MemCache::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn by_id(&self, id: Id) -> Option<Note> {
|
||||
self.cache.get(id).await
|
||||
}
|
||||
|
||||
pub async fn store(&self, note: Note) {
|
||||
self.cache.put(note).await
|
||||
}
|
||||
}
|
||||
|
||||
impl GetId<Id> for Note {
|
||||
fn get_id(&self) -> Id {
|
||||
self.id
|
||||
}
|
||||
}
|
|
@ -1,3 +1,5 @@
|
|||
mod mem;
|
||||
mod pg;
|
||||
|
||||
pub use mem::MemNoteDataSource;
|
||||
pub use pg::PgNoteDataSource;
|
||||
|
|
30
src/data/object/mem.rs
Normal file
30
src/data/object/mem.rs
Normal file
|
@ -0,0 +1,30 @@
|
|||
use uuid::Uuid;
|
||||
|
||||
use crate::data::memcache::{GetId, MemCache};
|
||||
use crate::model::Object;
|
||||
|
||||
pub struct MemObjectDataSource {
|
||||
cache: MemCache<Uuid, Object>,
|
||||
}
|
||||
|
||||
impl MemObjectDataSource {
|
||||
pub fn new() -> MemObjectDataSource {
|
||||
MemObjectDataSource {
|
||||
cache: MemCache::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn by_id(&self, id: Uuid) -> Option<Object> {
|
||||
self.cache.get(id).await
|
||||
}
|
||||
|
||||
pub async fn store(&self, obj: Object) {
|
||||
self.cache.put(obj).await
|
||||
}
|
||||
}
|
||||
|
||||
impl GetId<Uuid> for Object {
|
||||
fn get_id(&self) -> Uuid {
|
||||
self.id
|
||||
}
|
||||
}
|
|
@ -1,3 +1,5 @@
|
|||
pub mod pg;
|
||||
mod mem;
|
||||
mod pg;
|
||||
|
||||
pub use mem::MemObjectDataSource;
|
||||
pub use pg::PgObjectDataSource;
|
||||
|
|
30
src/data/user/mem.rs
Normal file
30
src/data/user/mem.rs
Normal file
|
@ -0,0 +1,30 @@
|
|||
use crate::core::*;
|
||||
|
||||
use crate::data::memcache::{GetId, MemCache};
|
||||
use crate::model::User;
|
||||
|
||||
pub struct MemUserDataSource {
|
||||
cache: MemCache<Id, User>,
|
||||
}
|
||||
|
||||
impl MemUserDataSource {
|
||||
pub fn new() -> MemUserDataSource {
|
||||
MemUserDataSource {
|
||||
cache: MemCache::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn by_id(&self, id: Id) -> Option<User> {
|
||||
self.cache.get(id).await
|
||||
}
|
||||
|
||||
pub async fn store(&self, user: User) {
|
||||
self.cache.put(user).await
|
||||
}
|
||||
}
|
||||
|
||||
impl GetId<Id> for User {
|
||||
fn get_id(&self) -> Id {
|
||||
self.id
|
||||
}
|
||||
}
|
|
@ -1,3 +1,5 @@
|
|||
pub mod pg;
|
||||
mod mem;
|
||||
mod pg;
|
||||
|
||||
pub use mem::MemUserDataSource;
|
||||
pub use pg::PgUserDataSource;
|
||||
|
|
|
@ -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<String>,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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<Id> + 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<T, E>(&self, new: T) -> Result<Account>
|
||||
|
@ -28,6 +34,8 @@ impl AccountRepo {
|
|||
E: Into<Error>,
|
||||
{
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Id> + 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<I>(&self, account_id: I, since: NaiveDateTime) -> Result<Vec<Note>>
|
||||
|
@ -36,6 +42,8 @@ impl NoteRepo {
|
|||
E: Into<Error>,
|
||||
{
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Object> {
|
||||
self.db.create(new).await
|
||||
let obj = self.db.create(new).await?;
|
||||
self.mem.store(obj.clone()).await;
|
||||
Ok(obj)
|
||||
}
|
||||
|
||||
pub async fn update<T, E>(&self, object: T) -> Result<Object>
|
||||
|
@ -35,7 +39,11 @@ impl ObjectRepo {
|
|||
where
|
||||
U: Into<Uuid>,
|
||||
{
|
||||
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<Object> {
|
||||
|
|
|
@ -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<Id> + 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<I>(&self, account_id: I) -> Result<User>
|
||||
|
@ -41,8 +47,9 @@ impl UserRepo {
|
|||
T: TryInto<Sane<NewUser>, Error = E> + Send,
|
||||
E: Into<Error>,
|
||||
{
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue