data: add in-memory cache

This commit is contained in:
anna 2022-12-26 15:42:47 +01:00
parent 9eb7601f9d
commit 8ea3ebda54
Signed by: fef
GPG key ID: EC22E476DC2D3D84
18 changed files with 277 additions and 20 deletions

30
src/data/account/mem.rs Normal file
View 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
}
}

View file

@ -1,3 +1,5 @@
mod mem;
mod pg;
pub use mem::MemAccountDataSource;
pub use pg::PgAccountDataSource;

95
src/data/memcache.rs Normal file
View 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));
}
}
}

View file

@ -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
View 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
}
}

View file

@ -1,3 +1,5 @@
mod mem;
mod pg;
pub use mem::MemNoteDataSource;
pub use pg::PgNoteDataSource;

30
src/data/object/mem.rs Normal file
View 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
}
}

View file

@ -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
View 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
}
}

View file

@ -1,3 +1,5 @@
pub mod pg;
mod mem;
mod pg;
pub use mem::MemUserDataSource;
pub use pg::PgUserDataSource;

View file

@ -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>,

View file

@ -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,

View file

@ -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,

View file

@ -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,

View file

@ -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)
}
}

View file

@ -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)
}
}

View file

@ -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> {

View file

@ -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)
}
}