perf(api): remove legacy rolling user metric (#2233)

This commit is contained in:
RainbowBird
2026-08-06 18:31:29 +08:00
committed by GitHub
parent 6aba0dcb03
commit dd4658bd41
7 changed files with 238 additions and 1742 deletions
+235 -1666
View File
File diff suppressed because it is too large Load Diff
@@ -36,37 +36,28 @@ describe('registerUserMetricsSnapshotGauges', () => {
const totalUsers = createGaugeProbe()
const activeSessions = createGaugeProbe()
const distinctActiveUsers = createGaugeProbe()
const rollingActiveUsers = createGaugeProbe()
const recorder = registerUserMetricsSnapshotGauges({
totalUsers: totalUsers.gauge,
activeSessions: activeSessions.gauge,
distinctActiveUsers: distinctActiveUsers.gauge,
rollingActiveUsers: rollingActiveUsers.gauge,
})
await Promise.all([
totalUsers.collect(),
activeSessions.collect(),
distinctActiveUsers.collect(),
rollingActiveUsers.collect(),
])
expect(totalUsers.observe).not.toHaveBeenCalled()
expect(activeSessions.observe).not.toHaveBeenCalled()
expect(distinctActiveUsers.observe).not.toHaveBeenCalled()
expect(rollingActiveUsers.observe).not.toHaveBeenCalled()
recorder.record(
{
totalUsers: 42,
activeSessions: 7,
distinctActiveUsers: 5,
rollingActiveUsers: {
'24h': 9,
'7d': 18,
'30d': 30,
},
},
refreshedAt,
)
@@ -75,15 +66,11 @@ describe('registerUserMetricsSnapshotGauges', () => {
totalUsers.collect(),
activeSessions.collect(),
distinctActiveUsers.collect(),
rollingActiveUsers.collect(),
])
expect(totalUsers.observe).toHaveBeenLastCalledWith(42)
expect(activeSessions.observe).toHaveBeenLastCalledWith(7)
expect(distinctActiveUsers.observe).toHaveBeenLastCalledWith(5)
expect(rollingActiveUsers.observe).toHaveBeenNthCalledWith(1, 9, { window: '24h' })
expect(rollingActiveUsers.observe).toHaveBeenNthCalledWith(2, 18, { window: '7d' })
expect(rollingActiveUsers.observe).toHaveBeenNthCalledWith(3, 30, { window: '30d' })
})
it('stops observing a snapshot after its bounded freshness interval', async () => {
@@ -92,13 +79,11 @@ describe('registerUserMetricsSnapshotGauges', () => {
const totalUsers = createGaugeProbe()
const activeSessions = createGaugeProbe()
const distinctActiveUsers = createGaugeProbe()
const rollingActiveUsers = createGaugeProbe()
const recorder = registerUserMetricsSnapshotGauges({
totalUsers: totalUsers.gauge,
activeSessions: activeSessions.gauge,
distinctActiveUsers: distinctActiveUsers.gauge,
rollingActiveUsers: rollingActiveUsers.gauge,
})
recorder.record(
@@ -106,7 +91,6 @@ describe('registerUserMetricsSnapshotGauges', () => {
totalUsers: 42,
activeSessions: 7,
distinctActiveUsers: 5,
rollingActiveUsers: { '24h': 9, '7d': 18, '30d': 30 },
},
refreshedAt,
)
@@ -116,12 +100,10 @@ describe('registerUserMetricsSnapshotGauges', () => {
totalUsers.collect(),
activeSessions.collect(),
distinctActiveUsers.collect(),
rollingActiveUsers.collect(),
])
expect(totalUsers.observe).not.toHaveBeenCalled()
expect(activeSessions.observe).not.toHaveBeenCalled()
expect(distinctActiveUsers.observe).not.toHaveBeenCalled()
expect(rollingActiveUsers.observe).not.toHaveBeenCalled()
})
})
@@ -1,19 +1,14 @@
import type { ObservableGauge } from '@opentelemetry/api'
const ROLLING_WINDOWS = ['24h', '7d', '30d'] as const
// The admin endpoint caches DB aggregates for 60 seconds. Two minutes keeps a
// continuously refreshed dashboard stable across cache rollover and several
// 15-second OTel collections, while still bounding stale replica dominance.
export const USER_METRICS_SNAPSHOT_MAX_AGE_MS = 2 * 60_000
type RollingWindow = (typeof ROLLING_WINDOWS)[number]
export interface UserMetricsSnapshot {
totalUsers: number
activeSessions: number
distinctActiveUsers: number
rollingActiveUsers: Record<RollingWindow, number>
}
export interface UserMetricsSnapshotRecorder {
@@ -26,7 +21,6 @@ export interface UserMetricsSnapshotGauges {
totalUsers: ObservableGaugeRegistration
activeSessions: ObservableGaugeRegistration
distinctActiveUsers: ObservableGaugeRegistration
rollingActiveUsers: ObservableGaugeRegistration
}
/**
@@ -70,15 +64,6 @@ export function registerUserMetricsSnapshotGauges(
result.observe(snapshot.distinctActiveUsers)
})
gauges.rollingActiveUsers.addCallback((result) => {
const snapshot = readFreshSnapshot()
if (!snapshot)
return
for (const window of ROLLING_WINDOWS)
result.observe(snapshot.rollingActiveUsers[window], { window })
})
return {
record(snapshot, refreshedAt) {
latest = { snapshot, refreshedAt }
-24
View File
@@ -56,7 +56,6 @@ import {
METRIC_STRIPE_EVENTS,
METRIC_STRIPE_PAYMENT_FAILED,
METRIC_STRIPE_SUBSCRIPTION_EVENT,
METRIC_USER_ACTIVE_ROLLING,
METRIC_USER_ACTIVE_SESSIONS,
METRIC_USER_DISTINCT_ACTIVE,
METRIC_USER_LOGIN,
@@ -125,26 +124,6 @@ export interface AuthMetrics {
* `avg()`/`max()`, not `sum()`.
*/
distinctActiveUsers: ObservableGauge
/**
* Gauge for the latest requested rolling-window active users (DAU / WAU /
* MAU).
*
* Use when:
* - Reporting "how many users were active in the last 24h / 7d / 30d" —
* the standard product-engagement funnel, distinct from
* {@link AuthMetrics.distinctActiveUsers} which only counts users with a
* currently-live session.
*
* Expects:
* - Refreshed by `/api/admin/metrics` using `COUNT(*) FILTER` over the
* `user` table. `last_seen_at` is touched on sign-in and on every
* OIDC access-token refresh (~hourly), so it is a per-user last-activity
* timestamp (see the `user.lastSeenAt` schema note).
* - Observed once per window with a `window` attribute (`24h` / `7d` /
* `30d`). Periodic collection reads memory only; dashboards MUST
* aggregate with `max()`/`avg()`, not `sum()`.
*/
rollingActiveUsers: ObservableGauge
}
export interface EngagementMetrics {
@@ -413,9 +392,6 @@ export function initOtel(env: Env): OtelInstance | null {
distinctActiveUsers: meter.createObservableGauge(METRIC_USER_DISTINCT_ACTIVE, {
description: 'Fresh admin-requested distinct active-user snapshot, immune to per-row session inflation and omitted when stale (dashboard must use max(), not sum())',
}),
rollingActiveUsers: meter.createObservableGauge(METRIC_USER_ACTIVE_ROLLING, {
description: 'Fresh admin-requested DAU/WAU/MAU snapshot from user.last_seen_at, omitted when stale and labelled by window=24h|7d|30d (dashboard must use max(), not sum())',
}),
}
// Engagement metrics
@@ -54,14 +54,15 @@ describe('admin metrics', () => {
const firstResponse = await app.request('/api/admin/metrics')
expect(firstResponse.status).toBe(200)
expect(await firstResponse.json()).toMatchObject({
const firstMetrics = await firstResponse.json()
expect(firstMetrics).toMatchObject({
totalUsers: 1,
verifiedUsers: 1,
adminSeats: 1,
activeSessions: 0,
distinctActiveUsers: 0,
rollingActiveUsers: { '24h': 0, '7d': 0, '30d': 0 },
})
expect(firstMetrics).not.toHaveProperty('rollingActiveUsers')
expect(recordUserMetrics).toHaveBeenLastCalledWith(expect.objectContaining({
totalUsers: 1,
activeSessions: 0,
-11
View File
@@ -93,9 +93,6 @@ function createAdminMetricsReader(db: Database) {
inFlight = (async () => {
const currentTime = Date.now()
const yesterday = new Date(currentTime - 24 * 60 * 60 * 1000)
const weekAgo = new Date(currentTime - 7 * 24 * 60 * 60 * 1000)
const monthAgo = new Date(currentTime - 30 * 24 * 60 * 60 * 1000)
const [
users,
sessions,
@@ -107,9 +104,6 @@ function createAdminMetricsReader(db: Database) {
totalUsers: count(),
verifiedUsers: sql<number>`count(*) filter (where ${userTable.emailVerified} = true)`,
adminSeats: sql<number>`count(*) filter (where 'admin' = any(regexp_split_to_array(coalesce(${userTable.role}, ''), '\\s*,\\s*')))`,
active24h: sql<number>`count(*) filter (where ${userTable.lastSeenAt} > ${yesterday})`,
active7d: sql<number>`count(*) filter (where ${userTable.lastSeenAt} > ${weekAgo})`,
active30d: sql<number>`count(*) filter (where ${userTable.lastSeenAt} > ${monthAgo})`,
}).from(userTable),
db
.select({
@@ -134,11 +128,6 @@ function createAdminMetricsReader(db: Database) {
verifiedUsers: Number(users[0]?.verifiedUsers ?? 0),
activeSessions: Number(sessions[0]?.activeSessions ?? 0),
distinctActiveUsers: Number(sessions[0]?.distinctActiveUsers ?? 0),
rollingActiveUsers: {
'24h': Number(users[0]?.active24h ?? 0),
'7d': Number(users[0]?.active7d ?? 0),
'30d': Number(users[0]?.active30d ?? 0),
},
currentFlux: Number(currentFlux[0]?.total ?? 0),
issuedFlux: Number(issuedFlux[0]?.total ?? 0),
llmRequests24h: Number(llmUsage24h[0]?.count ?? 0),
@@ -54,12 +54,6 @@ export const METRIC_USER_ACTIVE_SESSIONS = 'user.active_sessions'
// creates a new row per sign-in / per OIDC token refresh and never GCs)
// vs real user growth.
export const METRIC_USER_DISTINCT_ACTIVE = 'user.distinct_active'
// Rolling-window distinct active users (DAU / WAU / MAU), sourced from
// `user.last_seen_at` (touched on sign-in and every OIDC token refresh).
// Single gauge, observed once per window with a `window="24h"|"7d"|"30d"`
// attribute. Unlike USER_DISTINCT_ACTIVE (live-session count) this measures
// activity over a trailing time window, not "currently signed in".
export const METRIC_USER_ACTIVE_ROLLING = 'user.active_rolling'
// Engagement (AIRI custom)
export const METRIC_CHAT_MESSAGES = 'chat.messages'