perf: batch recent play counts for gallery lists
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
use crate::*;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
const PUBLIC_WORK_PLAY_DAY_MICROS: i64 = 86_400_000_000;
|
||||
const PUBLIC_WORK_RECENT_PLAY_WINDOW_DAYS: i64 = 7;
|
||||
@@ -1299,25 +1300,94 @@ pub(crate) fn count_recent_public_work_plays(
|
||||
profile_id: &str,
|
||||
now_micros: i64,
|
||||
) -> u32 {
|
||||
count_recent_public_work_plays_for_profiles(
|
||||
ctx,
|
||||
source_type,
|
||||
&[profile_id.to_string()],
|
||||
now_micros,
|
||||
)
|
||||
.remove(profile_id.trim())
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
pub(crate) fn count_recent_public_work_plays_for_profiles(
|
||||
ctx: &ReducerContext,
|
||||
source_type: &str,
|
||||
profile_ids: &[String],
|
||||
now_micros: i64,
|
||||
) -> HashMap<String, u32> {
|
||||
let source_type = source_type.trim();
|
||||
let profile_id = profile_id.trim();
|
||||
if source_type.is_empty() || profile_id.is_empty() {
|
||||
return 0;
|
||||
if source_type.is_empty() || profile_ids.is_empty() {
|
||||
return HashMap::new();
|
||||
}
|
||||
|
||||
let current_day = public_work_play_day_from_micros(now_micros);
|
||||
let first_day = current_day.saturating_sub(PUBLIC_WORK_RECENT_PLAY_WINDOW_DAYS - 1);
|
||||
|
||||
ctx.db
|
||||
.public_work_play_daily_stat()
|
||||
let requested_profile_ids = profile_ids
|
||||
.iter()
|
||||
.filter(|row| {
|
||||
row.source_type == source_type
|
||||
&& row.profile_id == profile_id
|
||||
&& row.played_day >= first_day
|
||||
&& row.played_day <= current_day
|
||||
})
|
||||
.fold(0u32, |total, row| total.saturating_add(row.play_count))
|
||||
.map(|profile_id| profile_id.trim())
|
||||
.filter(|profile_id| !profile_id.is_empty())
|
||||
.collect::<HashSet<_>>();
|
||||
let mut counts = HashMap::new();
|
||||
|
||||
for profile_id in requested_profile_ids {
|
||||
let mut total = 0u32;
|
||||
for played_day in first_day..=current_day {
|
||||
let day_total = ctx
|
||||
.db
|
||||
.public_work_play_daily_stat()
|
||||
.by_public_work_play_daily_stat_work_day()
|
||||
.filter((source_type, profile_id, played_day))
|
||||
.fold(0u32, |sum, row| sum.saturating_add(row.play_count));
|
||||
total = total.saturating_add(day_total);
|
||||
}
|
||||
if total > 0 {
|
||||
counts.insert(profile_id.to_string(), total);
|
||||
}
|
||||
}
|
||||
|
||||
counts
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn build_recent_public_work_play_counts(
|
||||
rows: impl IntoIterator<Item = PublicWorkPlayDailyStat>,
|
||||
source_type: &str,
|
||||
profile_ids: &[String],
|
||||
now_micros: i64,
|
||||
) -> HashMap<String, u32> {
|
||||
let source_type = source_type.trim();
|
||||
if source_type.is_empty() || profile_ids.is_empty() {
|
||||
return HashMap::new();
|
||||
}
|
||||
|
||||
let requested_profile_ids = profile_ids
|
||||
.iter()
|
||||
.map(|profile_id| profile_id.trim())
|
||||
.filter(|profile_id| !profile_id.is_empty())
|
||||
.collect::<HashSet<_>>();
|
||||
if requested_profile_ids.is_empty() {
|
||||
return HashMap::new();
|
||||
}
|
||||
|
||||
let current_day = public_work_play_day_from_micros(now_micros);
|
||||
let first_day = current_day.saturating_sub(PUBLIC_WORK_RECENT_PLAY_WINDOW_DAYS - 1);
|
||||
let mut counts = HashMap::new();
|
||||
|
||||
for row in rows {
|
||||
if row.source_type != source_type
|
||||
|| !requested_profile_ids.contains(row.profile_id.as_str())
|
||||
|| row.played_day < first_day
|
||||
|| row.played_day > current_day
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let entry = counts.entry(row.profile_id.clone()).or_insert(0u32);
|
||||
*entry = entry.saturating_add(row.play_count);
|
||||
}
|
||||
|
||||
counts
|
||||
}
|
||||
|
||||
fn public_work_play_day_from_micros(value: i64) -> i64 {
|
||||
@@ -1336,6 +1406,75 @@ fn build_public_work_like_id(source_type: &str, profile_id: &str, user_id: &str)
|
||||
format!("{source_type}:{profile_id}:{user_id}")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn recent_public_work_play_counts_group_requested_profiles_in_window() {
|
||||
let now_micros = PUBLIC_WORK_PLAY_DAY_MICROS * 10;
|
||||
let updated_at = Timestamp::from_micros_since_unix_epoch(now_micros);
|
||||
let rows = vec![
|
||||
PublicWorkPlayDailyStat {
|
||||
stat_id: "puzzle:profile-a:10".to_string(),
|
||||
source_type: "puzzle".to_string(),
|
||||
owner_user_id: "user-a".to_string(),
|
||||
profile_id: "profile-a".to_string(),
|
||||
played_day: 10,
|
||||
play_count: 3,
|
||||
updated_at,
|
||||
},
|
||||
PublicWorkPlayDailyStat {
|
||||
stat_id: "puzzle:profile-a:4".to_string(),
|
||||
source_type: "puzzle".to_string(),
|
||||
owner_user_id: "user-a".to_string(),
|
||||
profile_id: "profile-a".to_string(),
|
||||
played_day: 4,
|
||||
play_count: 5,
|
||||
updated_at,
|
||||
},
|
||||
PublicWorkPlayDailyStat {
|
||||
stat_id: "puzzle:profile-a:3".to_string(),
|
||||
source_type: "puzzle".to_string(),
|
||||
owner_user_id: "user-a".to_string(),
|
||||
profile_id: "profile-a".to_string(),
|
||||
played_day: 3,
|
||||
play_count: 99,
|
||||
updated_at,
|
||||
},
|
||||
PublicWorkPlayDailyStat {
|
||||
stat_id: "custom-world:profile-a:10".to_string(),
|
||||
source_type: "custom-world".to_string(),
|
||||
owner_user_id: "user-a".to_string(),
|
||||
profile_id: "profile-a".to_string(),
|
||||
played_day: 10,
|
||||
play_count: 7,
|
||||
updated_at,
|
||||
},
|
||||
PublicWorkPlayDailyStat {
|
||||
stat_id: "puzzle:profile-b:9".to_string(),
|
||||
source_type: "puzzle".to_string(),
|
||||
owner_user_id: "user-b".to_string(),
|
||||
profile_id: "profile-b".to_string(),
|
||||
played_day: 9,
|
||||
play_count: 11,
|
||||
updated_at,
|
||||
},
|
||||
];
|
||||
|
||||
let counts = build_recent_public_work_play_counts(
|
||||
rows,
|
||||
"puzzle",
|
||||
&["profile-a".to_string(), "profile-b".to_string()],
|
||||
now_micros,
|
||||
);
|
||||
|
||||
assert_eq!(counts.get("profile-a"), Some(&8));
|
||||
assert_eq!(counts.get("profile-b"), Some(&11));
|
||||
assert_eq!(counts.get("profile-c"), None);
|
||||
}
|
||||
}
|
||||
|
||||
fn ensure_profile_dashboard_state(ctx: &ReducerContext, user_id: &str, updated_at: Timestamp) {
|
||||
if ctx
|
||||
.db
|
||||
|
||||
Reference in New Issue
Block a user