improve activity tab performance by adding indexes

This commit is contained in:
pa
2026-03-20 17:46:41 +09:00
parent 4570f254ea
commit ad5b9ab48d
7 changed files with 89 additions and 199 deletions

View File

@@ -1,14 +1,7 @@
import { defineStore } from 'pinia';
import { toast } from 'vue-sonner';
import { useI18n } from 'vue-i18n';
import { database } from '../services/database';
import {
buildSessionsFromGamelog,
ONLINE_SESSION_MERGE_GAP_MS
} from '../shared/utils/overlapCalculator';
const ACTIVITY_CACHE_TTL_MS = 7 * 24 * 60 * 60 * 1000;
import { ONLINE_SESSION_MERGE_GAP_MS } from '../shared/utils/overlapCalculator';
const refreshJobs = new Map();
function buildSessionsAndPendingFromEvents(events, initialStart = null) {
@@ -35,74 +28,25 @@ function buildSessionsAndPendingFromEvents(events, initialStart = null) {
};
}
function mergeWithLastSession(lastSession, newSessions) {
if (!lastSession || newSessions.length === 0) {
return { replaceLastSession: null, sessions: newSessions };
}
const firstSession = newSessions[0];
if (firstSession.start > lastSession.end + ONLINE_SESSION_MERGE_GAP_MS) {
return { replaceLastSession: null, sessions: newSessions };
}
const mergedFirst = {
start: Math.min(lastSession.start, firstSession.start),
end: Math.max(lastSession.end, firstSession.end)
};
return {
replaceLastSession: lastSession,
sessions: [mergedFirst, ...newSessions.slice(1)]
};
}
export const useActivityStore = defineStore('Activity', () => {
const { t } = useI18n();
function getCache(userId) {
return database.getActivityCache(userId);
}
function isExpired(cacheEntry) {
if (!cacheEntry?.updatedAt) {
return true;
}
const updatedAtMs = Date.parse(cacheEntry.updatedAt);
if (Number.isNaN(updatedAtMs)) {
return true;
}
return Date.now() - updatedAtMs >= ACTIVITY_CACHE_TTL_MS;
}
function isRefreshing(userId) {
return refreshJobs.has(userId);
}
async function fullRefresh(userId, isSelf) {
if (isSelf) {
const rows = await database.getCurrentUserOnlineSessions();
const sessions = buildSessionsFromGamelog(rows);
const sourceLastCreatedAt =
rows.length > 0 ? rows[rows.length - 1].created_at : '';
const entry = {
userId,
updatedAt: new Date().toISOString(),
isSelf,
sourceLastCreatedAt,
pendingSessionStartAt: null,
sessions
};
await database.replaceActivityCache(entry);
return database.getActivityCache(userId);
}
async function fullRefresh(userId) {
const events = await database.getOnlineOfflineSessions(userId);
const { sessions, pendingSessionStartAt } = buildSessionsAndPendingFromEvents(events);
const { sessions, pendingSessionStartAt } =
buildSessionsAndPendingFromEvents(events);
const sourceLastCreatedAt =
events.length > 0 ? events[events.length - 1].created_at : '';
const entry = {
userId,
updatedAt: new Date().toISOString(),
isSelf,
isSelf: false,
sourceLastCreatedAt,
pendingSessionStartAt,
sessions
@@ -114,40 +58,8 @@ export const useActivityStore = defineStore('Activity', () => {
async function incrementalRefresh(meta) {
const updatedAt = new Date().toISOString();
if (meta.isSelf) {
if (!meta.sourceLastCreatedAt) {
return fullRefresh(meta.userId, true);
}
const rows = await database.getCurrentUserOnlineSessionsAfter(
meta.sourceLastCreatedAt
);
if (rows.length === 0) {
await database.touchActivityCacheMeta({
...meta,
updatedAt
});
return database.getActivityCache(meta.userId);
}
const sourceLastCreatedAt = rows[rows.length - 1].created_at;
const newSessionsRaw = buildSessionsFromGamelog(rows);
const lastSession = await database.getLastActivityCacheSession(meta.userId);
const merged = mergeWithLastSession(lastSession, newSessionsRaw);
await database.appendActivityCache({
...meta,
updatedAt,
sourceLastCreatedAt,
pendingSessionStartAt: null,
sessions: merged.sessions,
replaceLastSession: merged.replaceLastSession
});
return database.getActivityCache(meta.userId);
}
if (!meta.sourceLastCreatedAt) {
return fullRefresh(meta.userId, false);
return fullRefresh(meta.userId);
}
const events = await database.getOnlineOfflineSessionsAfter(
@@ -162,10 +74,11 @@ export const useActivityStore = defineStore('Activity', () => {
return database.getActivityCache(meta.userId);
}
const { sessions, pendingSessionStartAt } = buildSessionsAndPendingFromEvents(
events,
meta.pendingSessionStartAt
);
const { sessions, pendingSessionStartAt } =
buildSessionsAndPendingFromEvents(
events,
meta.pendingSessionStartAt
);
const sourceLastCreatedAt = events[events.length - 1].created_at;
await database.appendActivityCache({
@@ -178,32 +91,18 @@ export const useActivityStore = defineStore('Activity', () => {
return database.getActivityCache(meta.userId);
}
function refreshActivityCache(userId, isSelf, options = {}) {
const { notifyStart = false, notifyComplete = false } = options;
function refreshActivityCache(userId) {
const existing = refreshJobs.get(userId);
if (existing) {
return existing;
}
if (notifyStart) {
toast.info(t('dialog.user.activity.refresh_started'), {
position: 'bottom-center'
});
}
const job = (async () => {
const meta = await database.getActivityCacheMeta(userId);
const entry =
meta && meta.isSelf === isSelf
? await incrementalRefresh(meta)
: await fullRefresh(userId, isSelf);
if (notifyComplete) {
toast.success(t('dialog.user.activity.refresh_complete'), {
position: 'bottom-center'
});
if (!meta || meta.isSelf) {
return fullRefresh(userId);
}
return entry;
return incrementalRefresh(meta);
})().finally(() => {
refreshJobs.delete(userId);
});
@@ -214,9 +113,7 @@ export const useActivityStore = defineStore('Activity', () => {
return {
getCache,
isExpired,
isRefreshing,
refreshActivityCache,
ttlMs: ACTIVITY_CACHE_TTL_MS
refreshActivityCache
};
});