perf(cache): cache COUNT(*) result separately to avoid redundant queries

- Add TotalPublishedPosts cache key for reusing total count across pages
- list_published_posts now checks total cache before running COUNT(*)
- Add note to get_posts_by_tag about total = posts.len() assumption
- Remove unused invalidate_total_published_posts helper
This commit is contained in:
xfy 2026-06-10 14:44:53 +08:00
parent bd9053132b
commit 311ddbe204
2 changed files with 29 additions and 9 deletions

View File

@ -20,15 +20,21 @@ pub async fn list_published_posts(
let client = get_conn().await.map_err(AppError::db_conn)?;
// Get total count
let count_row = client
.query_one(
"SELECT COUNT(*) FROM posts WHERE status = 'published' AND deleted_at IS NULL",
&[],
)
.await
.map_err(AppError::query)?;
let total: i64 = count_row.get(0);
// Get total count from cache or query
let total = if let Some(cached_total) = crate::cache::get_total_published_posts().await {
cached_total
} else {
let count_row = client
.query_one(
"SELECT COUNT(*) FROM posts WHERE status = 'published' AND deleted_at IS NULL",
&[],
)
.await
.map_err(AppError::query)?;
let total: i64 = count_row.get(0);
crate::cache::set_total_published_posts(total).await;
total
};
let offset = ((page - 1).max(0) as i64) * (per_page as i64);
let limit = per_page as i64;
@ -139,6 +145,9 @@ pub async fn get_posts_by_tag(tag_name: String) -> Result<PostListResponse, Serv
posts.push(row_to_post_list(&client, row).await);
}
// NOTE: total = posts.len() is correct because get_posts_by_tag
// currently fetches ALL matching posts (no LIMIT/OFFSET).
// If pagination is added later, switch to a proper COUNT(*) query.
let total = posts.len() as i64;
crate::cache::set_posts_by_tag(&tag_name, posts.clone(), total).await;
Ok(PostListResponse { posts, total })

View File

@ -31,6 +31,7 @@ const TTL_TAG_POSTS: Duration = Duration::from_secs(120);
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
pub enum CacheKey {
PublishedPosts { page: i32, per_page: i32 },
TotalPublishedPosts,
AllTags,
PostBySlug(String),
PostsByTag(String),
@ -109,6 +110,16 @@ pub async fn set_post_list(key: &CacheKey, posts: Vec<Post>, total: i64) {
let _ = POST_LIST_CACHE.insert(key.clone(), (posts, total)).await;
}
#[cfg(feature = "server")]
pub async fn get_total_published_posts() -> Option<i64> {
POST_LIST_CACHE.get(&CacheKey::TotalPublishedPosts).await.map(|(_, total)| total)
}
#[cfg(feature = "server")]
pub async fn set_total_published_posts(total: i64) {
let _ = POST_LIST_CACHE.insert(CacheKey::TotalPublishedPosts, (vec![], total)).await;
}
#[cfg(feature = "server")]
pub async fn get_tag_list() -> Option<Vec<Tag>> {
TAG_LIST_CACHE.get(&CacheKey::AllTags).await