-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(db): Implement db-layer user count cache (#1567)
* Draft cache design * [autofix.ci] apply automated fixes * [autofix.ci] apply automated fixes (attempt 2/3) * Remove AnyCache, simplify Cache * Move cache * feat(db): Implement db-layer user count cache * Fix errors from resolving conflicts * Invalidate cache instead of updating * Remove Cache::update * Remove Cache::set * Add test for user cache, fix bug --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
- Loading branch information
1 parent
ebd2937
commit ebea511
Showing
4 changed files
with
81 additions
and
49 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -79,6 +79,11 @@ impl DbConn { | |
let res = res.unique_error("User already exists")?; | ||
transaction.commit().await?; | ||
|
||
self.cache.active_user_count.invalidate().await; | ||
if is_admin { | ||
self.cache.active_admin_count.invalidate().await; | ||
} | ||
|
||
Ok(res.last_insert_rowid() as i32) | ||
} | ||
|
||
|
@@ -179,10 +184,11 @@ impl DbConn { | |
.await? | ||
.rows_affected(); | ||
if changed != 1 { | ||
Err(anyhow!("user active status was not changed")) | ||
} else { | ||
Ok(()) | ||
return Err(anyhow!("user active status was not changed")); | ||
} | ||
self.cache.active_admin_count.invalidate().await; | ||
self.cache.active_user_count.invalidate().await; | ||
Ok(()) | ||
} | ||
|
||
pub async fn update_user_role(&self, id: i32, is_admin: bool) -> Result<()> { | ||
|
@@ -199,6 +205,7 @@ impl DbConn { | |
if changed != 1 { | ||
Err(anyhow!("user admin status was not changed")) | ||
} else { | ||
self.cache.active_admin_count.invalidate().await; | ||
Ok(()) | ||
} | ||
} | ||
|
@@ -214,19 +221,28 @@ impl DbConn { | |
Ok(()) | ||
} | ||
|
||
// FIXME(boxbeam): Revisit if a caching layer should be put into DbConn for this query in future. | ||
pub async fn count_active_users(&self) -> Result<usize> { | ||
let users = query_scalar!("SELECT COUNT(1) FROM users WHERE active;") | ||
.fetch_one(&self.pool) | ||
.await?; | ||
Ok(users as usize) | ||
self.cache | ||
.active_user_count | ||
.get_or_refresh(|| async { | ||
let users = query_scalar!("SELECT COUNT(1) FROM users WHERE active;") | ||
.fetch_one(&self.pool) | ||
.await?; | ||
Ok(users as usize) | ||
}) | ||
.await | ||
} | ||
|
||
pub async fn count_active_admin_users(&self) -> Result<usize> { | ||
let users = query_scalar!("SELECT COUNT(1) FROM users WHERE active and is_admin;") | ||
.fetch_one(&self.pool) | ||
.await?; | ||
Ok(users as usize) | ||
self.cache | ||
.active_admin_count | ||
.get_or_refresh(|| async { | ||
let users = query_scalar!("SELECT COUNT(1) FROM users WHERE active and is_admin;") | ||
.fetch_one(&self.pool) | ||
.await?; | ||
Ok(users as usize) | ||
}) | ||
.await | ||
} | ||
} | ||
|
||
|
@@ -528,5 +544,38 @@ mod tests { | |
) | ||
); | ||
} | ||
|
||
#[tokio::test] | ||
async fn test_caching() { | ||
let db = DbConn::new_in_memory().await.unwrap(); | ||
|
||
db.create_user("[email protected]".into(), "".into(), true) | ||
.await | ||
.unwrap(); | ||
|
||
assert_eq!(db.count_active_users().await.unwrap(), 1); | ||
assert_eq!(db.count_active_admin_users().await.unwrap(), 1); | ||
|
||
let user2_id = db | ||
.create_user("[email protected]".into(), "".into(), false) | ||
.await | ||
.unwrap(); | ||
assert_eq!(db.count_active_users().await.unwrap(), 2); | ||
assert_eq!(db.count_active_admin_users().await.unwrap(), 1); | ||
|
||
db.update_user_active(user2_id, false).await.unwrap(); | ||
assert_eq!(db.count_active_users().await.unwrap(), 1); | ||
assert_eq!(db.count_active_admin_users().await.unwrap(), 1); | ||
|
||
let user3_id = db | ||
.create_user("[email protected]".into(), "".into(), true) | ||
.await | ||
.unwrap(); | ||
assert_eq!(db.count_active_users().await.unwrap(), 2); | ||
assert_eq!(db.count_active_admin_users().await.unwrap(), 2); | ||
|
||
db.update_user_active(user3_id, false).await.unwrap(); | ||
assert_eq!(db.count_active_users().await.unwrap(), 1); | ||
assert_eq!(db.count_active_admin_users().await.unwrap(), 1); | ||
} | ||
} | ||
// FIXME(boxbeam): Revisit if a caching layer should be put into DbConn for this query in future. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters