From eac0fe65ab7a22aa22a02abc53b0db4341221c72 Mon Sep 17 00:00:00 2001 From: Ben Date: Sun, 7 Jun 2026 22:58:00 -0400 Subject: [PATCH] Cache live user agent report off request path --- .../user-agent-report.service.ts | 46 ++++++++++++++++++- src/services/app.service.ts | 24 ++++++++++ 2 files changed, 68 insertions(+), 2 deletions(-) diff --git a/src/ORM/_views/user-agent-report/user-agent-report.service.ts b/src/ORM/_views/user-agent-report/user-agent-report.service.ts index d2eff3a..b22cf21 100644 --- a/src/ORM/_views/user-agent-report/user-agent-report.service.ts +++ b/src/ORM/_views/user-agent-report/user-agent-report.service.ts @@ -8,6 +8,8 @@ import { RedisMessagingService } from '../../../services/redis-messaging.service @Injectable() export class UserAgentReportService { + private readonly liveReportCacheKey = 'presence:user-agent-report'; + private liveRefreshPromise: Promise | null = null; constructor( @InjectRepository(UserAgentReportView) @@ -20,6 +22,37 @@ export class UserAgentReportService { } public async getReport() { + const cachedReport = await this.redisMessagingService + .getJsonCache(this.liveReportCacheKey) + .catch(error => { + console.error(`Live user-agent report cache read failed: ${error.message}`); + return null; + }); + if (cachedReport != null) { + return cachedReport; + } + + if (process.env.API_ONLY == 'true') { + return this.userAgentReport.find(); + } + + return this.refreshLiveReport(); + } + + public async refreshLiveReport() { + if (this.liveRefreshPromise != null) { + return this.liveRefreshPromise; + } + + this.liveRefreshPromise = this.buildLiveReport() + .finally(() => { + this.liveRefreshPromise = null; + }); + + return this.liveRefreshPromise; + } + + private async buildLiveReport() { const presences = await this.redisMessagingService.getAllClientPresence(); const rows = new Map right.totalHashRate - left.totalHashRate) .map(row => ({ userAgent: row.userAgent, @@ -55,11 +88,20 @@ export class UserAgentReportService { bestDifficulty: row.bestDifficulty, totalHashRate: row.totalHashRate.toString(), })); + + await this.redisMessagingService + .setJsonCache(this.liveReportCacheKey, report, 60 * 1000) + .catch(error => { + console.error(`Live user-agent report cache write failed: ${error.message}`); + }); + + return report; } public async refreshReport() { try { - return await this.userAgentReport.query(`REFRESH MATERIALIZED VIEW user_agent_report_view`); + await this.userAgentReport.query(`REFRESH MATERIALIZED VIEW user_agent_report_view`); + return await this.refreshLiveReport(); } catch (e) { console.log(e) diff --git a/src/services/app.service.ts b/src/services/app.service.ts index 49a6fe2..d0902ea 100644 --- a/src/services/app.service.ts +++ b/src/services/app.service.ts @@ -6,6 +6,7 @@ import { RpcBlockService } from '../ORM/rpc-block/rpc-block.service'; @Injectable() export class AppService implements OnModuleInit { + private refreshingLiveUserAgentReport = false; constructor( private readonly clientService: ClientService, @@ -27,6 +28,14 @@ export class AppService implements OnModuleInit { await this.rpcBlockService.deleteOldBlocks(); }, 1000 * 60 * 60 * 24); + setInterval(async () => { + await this.refreshLiveUserAgentReport(); + }, 1000 * 30); + + setTimeout(async () => { + await this.refreshLiveUserAgentReport(); + }, 1000 * 15); + setInterval(async () => { console.log('Refreshing user agent report view') await this.userAgentReportService.refreshReport(); @@ -43,4 +52,19 @@ export class AppService implements OnModuleInit { console.log(`Deleted ${deletedClients.affected} old clients`); } + + private async refreshLiveUserAgentReport() { + if (this.refreshingLiveUserAgentReport) { + return; + } + + this.refreshingLiveUserAgentReport = true; + try { + await this.userAgentReportService.refreshLiveReport(); + } catch (error) { + console.error(`Failed refreshing live user agent report: ${error.message}`); + } finally { + this.refreshingLiveUserAgentReport = false; + } + } }