@@ -28,9 +28,9 @@ const logger = createLogger('ReportingUsageCache')
2828 * small against a year-long allowance while turning a per-event scan into one per window.
2929 *
3030 * A sum is held both in Redis, shared by every process, and in each process that reads it. It
31- * reflects the ledger as of the moment its sum began, so a served sum can omit usage written over
32- * the sum's own duration, plus up to this TTL and its jitter in Redis, plus up to this TTL again
33- * in the reading process.
31+ * reflects the ledger as of the moment its sum began, and its Redis expiry is anchored to that
32+ * moment, so a sum is served from Redis for at most this TTL plus its jitter after it began —
33+ * including however long the sum itself ran — plus up to this TTL again in the reading process.
3434 */
3535export const REPORTING_USAGE_CACHE_TTL_MS = 30_000
3636
@@ -95,17 +95,21 @@ function warnSharedWriteFailed(error: unknown): void {
9595}
9696
9797/**
98- * Fire-and-forget: a read never waits on, or fails because of, the shared write. The write only
99- * lands when no sum is stored (`NX`), so a slow, older sum can never replace a fresher one or
100- * extend its expiry.
98+ * Fire-and-forget: a read never waits on, or fails because of, the shared write. The expiry is
99+ * anchored to when the sum began (`PXAT`), and a sum already older than the TTL is not written, so
100+ * neither a slow sum nor a write the client resends after a reconnect can give an old total a
101+ * fresh expiry. The write only lands when no sum is stored (`NX`), so an older sum can never
102+ * replace a fresher one or extend its expiry.
101103 */
102- function writeSharedReportingUsageCost ( key : string , cost : number ) : void {
104+ function writeSharedReportingUsageCost ( key : string , cost : number , sumStartedAt : number ) : void {
103105 try {
106+ if ( Date . now ( ) >= sumStartedAt + REPORTING_USAGE_CACHE_TTL_MS ) return
104107 const redis = readyRedisClient ( )
105108 if ( ! redis ) return
106- const ttlMs = REPORTING_USAGE_CACHE_TTL_MS + randomInt ( 0 , SHARED_TTL_JITTER_MS )
109+ const expiresAt =
110+ sumStartedAt + REPORTING_USAGE_CACHE_TTL_MS + randomInt ( 0 , SHARED_TTL_JITTER_MS )
107111 redis
108- . set ( sharedReportingUsageKey ( key ) , String ( cost ) , 'PX ' , ttlMs , 'NX' )
112+ . set ( sharedReportingUsageKey ( key ) , String ( cost ) , 'PXAT ' , expiresAt , 'NX' )
109113 . catch ( warnSharedWriteFailed )
110114 } catch ( error ) {
111115 warnSharedWriteFailed ( error )
@@ -124,8 +128,9 @@ async function sumReportingUsageCost(
124128) : Promise < number > {
125129 const shared = await readSharedReportingUsageCost ( key )
126130 if ( shared !== undefined ) return shared
131+ const sumStartedAt = Date . now ( )
127132 const cost = await getBillingPeriodUsageCost ( entity , period )
128- writeSharedReportingUsageCost ( key , cost )
133+ writeSharedReportingUsageCost ( key , cost , sumStartedAt )
129134 return cost
130135}
131136
0 commit comments