fix(stats): honor traffic ledger history

This commit is contained in:
saltbo
2026-07-22 12:24:45 -04:00
parent 44198fabcb
commit ea26ff0c83
6 changed files with 188 additions and 8 deletions
+79 -4
View File
@@ -21,6 +21,7 @@ const MAX_BACKFILL_HOURS = 100_000
const STATISTICS_OPENING_SOURCE_ID = 'v3-authoritative-sources'
const STATISTICS_OPENING_EVENT_ID = `audit:statistics_source_initialized:${STATISTICS_OPENING_SOURCE_ID}`
const STATISTICS_OPENING_OPTION_KEY = 'stats_integrity_exact_from_v3'
const TRAFFIC_LEDGER_OPENING_EVENT_ID = 'traffic_ledger_opening_v1'
const statisticsExactFromMsSql = `COALESCE(
(SELECT unixepoch(value) * 1000 FROM system_options WHERE key = '${STATISTICS_OPENING_OPTION_KEY}'),
@@ -77,6 +78,7 @@ interface ValidationSummary {
rawMissingByteEvents: number
rollupMissingByteEvents: number
invalidAuditEvents: number
invalidIssuedTrafficReports: number
missingUserRegistrationEvents: number
invalidDownloadTaskEvents: number
orphanRollupBuckets: number
@@ -90,6 +92,9 @@ interface ValidationSummary {
signupExpectedBuckets: number
signupCompletedBuckets: number
signupMissingBuckets: number
trafficExpectedBuckets: number
trafficCompletedBuckets: number
trafficMissingBuckets: number
userSignupProviderMismatchGroups: number
openCounterMarkers: number
}
@@ -272,11 +277,19 @@ function buildHourlyBackfillSql(now: Date): string {
const currentHour = Math.floor(now.getTime() / 3_600_000) * 3_600_000
const fromMs = `MAX(${MIN_VALID_TIMESTAMP_MS}, COALESCE(${statisticsFirstFullHourMsSql}, ${MIN_VALID_TIMESTAMP_MS}))`
const signupFromMs = userSignupHistoryStartSql(currentHour)
const trafficFromMs = trafficHistoryStartSql(currentHour)
return [
...buildAdminStatsCounterRollupInsertSqlStatements({
fromMs,
toMs: currentHour,
metrics: ADMIN_STATS_FACT_COUNTER_METRICS.filter((metric) => metric !== M.userSignup),
metrics: ADMIN_STATS_FACT_COUNTER_METRICS.filter(
(metric) => metric !== M.userSignup && metric !== M.transferDownloadIssued,
),
}),
...buildAdminStatsCounterRollupInsertSqlStatements({
fromMs: trafficFromMs,
toMs: currentHour,
metrics: [M.transferDownloadIssued],
}),
...buildAdminStatsCounterRollupInsertSqlStatements({
fromMs: signupFromMs,
@@ -284,10 +297,22 @@ function buildHourlyBackfillSql(now: Date): string {
metrics: [M.userSignup],
}),
rollupMarkerBackfillSql(now),
metricRollupMarkerBackfillSql(now, M.transferDownloadIssued, trafficFromMs),
metricRollupMarkerBackfillSql(now, M.userSignup, signupFromMs),
].join(';\n\n')
}
function trafficHistoryStartSql(currentHour: number): string {
return `MAX(
${MIN_VALID_TIMESTAMP_MS},
COALESCE((
SELECT CAST((created_at + 3599999) / 3600000 AS INTEGER) * 3600000
FROM cloud_traffic_reports
WHERE event_id = '${TRAFFIC_LEDGER_OPENING_EVENT_ID}'
), ${currentHour})
)`
}
function userSignupHistoryStartSql(currentHour: number): string {
return `MAX(
${MIN_VALID_TIMESTAMP_MS},
@@ -628,12 +653,12 @@ export function buildValidationSql(now = new Date()): string {
'rawDownloadEvents', (
SELECT COUNT(*) FROM cloud_traffic_reports
WHERE issued_at IS NOT NULL AND status <> 'reversed'
AND issued_at >= MAX(${MIN_VALID_TIMESTAMP_MS}, COALESCE(${statisticsFirstFullHourMsSql}, ${MIN_VALID_TIMESTAMP_MS})) AND issued_at < ${currentHour}
AND issued_at >= ${trafficHistoryStartSql(currentHour)} AND issued_at < ${currentHour}
),
'rawDownloadBytes', (
SELECT COALESCE(SUM(bytes), 0) FROM cloud_traffic_reports
WHERE issued_at IS NOT NULL AND status <> 'reversed'
AND issued_at >= MAX(${MIN_VALID_TIMESTAMP_MS}, COALESCE(${statisticsFirstFullHourMsSql}, ${MIN_VALID_TIMESTAMP_MS})) AND issued_at < ${currentHour}
AND issued_at >= ${trafficHistoryStartSql(currentHour)} AND issued_at < ${currentHour}
),
'statisticsExactFrom', ${statisticsExactFromMsSql}
) AS summary;`
@@ -758,6 +783,22 @@ export function buildValidationSql(now = new Date()): string {
))
)
),
'invalidIssuedTrafficReports', (
SELECT COUNT(*)
FROM cloud_traffic_reports ctr
WHERE ctr.issued_at >= ${trafficHistoryStartSql(currentHour)}
AND ctr.issued_at < ${currentHour}
AND (
ctr.status = 'reversed'
OR ctr.bytes < 0
OR length(ctr.org_id) = 0
OR length(ctr.source_id) = 0
OR ctr.source NOT IN (
'object_download', 'direct_share', 'landing_share',
'image_hosting', 'custom_domain_image', 'webdav_download'
)
)
),
'missingUserRegistrationEvents', (
SELECT COUNT(*)
FROM user registered_user
@@ -1102,12 +1143,26 @@ signup_markers AS MATERIALIZED (
AND json_extract(metadata, '$.quality') = 'exact'
ELSE 0 END = 1
),
traffic_markers AS MATERIALIZED (
SELECT bucket_start
FROM stats_rollups_hourly
WHERE metric_key = 'stats.rollup_run' AND org_id = ''
AND dimension_key = 'metric_key' AND dimension_value = '${M.transferDownloadIssued}'
AND CASE WHEN json_valid(metadata) = 1 THEN
json_extract(metadata, '$.version') = 3
AND json_extract(metadata, '$.scope') IN ('counters', 'full')
AND json_extract(metadata, '$.quality') = 'exact'
ELSE 0 END = 1
),
coverage AS MATERIALIZED (
SELECT ${statsHistoryStartSql()} AS start_at, ${latestClosedHour} AS end_at
),
signup_coverage AS MATERIALIZED (
SELECT ${userSignupHistoryStartSql(currentHour)} AS start_at, ${latestClosedHour} AS end_at
),
traffic_coverage AS MATERIALIZED (
SELECT ${trafficHistoryStartSql(currentHour)} AS start_at, ${latestClosedHour} AS end_at
),
coverage_counts AS MATERIALIZED (
SELECT
CASE WHEN start_at IS NULL OR start_at > end_at THEN 0
@@ -1123,6 +1178,14 @@ signup_coverage_counts AS MATERIALIZED (
CASE WHEN start_at IS NULL OR start_at > end_at THEN 0
ELSE (SELECT COUNT(*) FROM signup_markers WHERE bucket_start BETWEEN start_at AND end_at) END AS completed
FROM signup_coverage
),
traffic_coverage_counts AS MATERIALIZED (
SELECT
CASE WHEN start_at IS NULL OR start_at > end_at THEN 0
ELSE CAST((end_at - start_at) / 3600000 AS INTEGER) + 1 END AS expected,
CASE WHEN start_at IS NULL OR start_at > end_at THEN 0
ELSE (SELECT COUNT(*) FROM traffic_markers WHERE bucket_start BETWEEN start_at AND end_at) END AS completed
FROM traffic_coverage
)
SELECT json_object(
'hourlyRollups', (SELECT COUNT(*) FROM counter_markers),
@@ -1132,9 +1195,13 @@ SELECT json_object(
'signupExpectedBuckets', (SELECT expected FROM signup_coverage_counts),
'signupCompletedBuckets', (SELECT completed FROM signup_coverage_counts),
'signupMissingBuckets', (SELECT expected - completed FROM signup_coverage_counts),
'trafficExpectedBuckets', (SELECT expected FROM traffic_coverage_counts),
'trafficCompletedBuckets', (SELECT completed FROM traffic_coverage_counts),
'trafficMissingBuckets', (SELECT expected - completed FROM traffic_coverage_counts),
'openCounterMarkers', (
(SELECT COUNT(*) FROM counter_markers WHERE bucket_start >= ${currentHour})
+ (SELECT COUNT(*) FROM signup_markers WHERE bucket_start >= ${currentHour})
+ (SELECT COUNT(*) FROM traffic_markers WHERE bucket_start >= ${currentHour})
)
) AS summary;
`
@@ -1334,6 +1401,7 @@ export function assertBackfillValidation(summary: ValidationSummary): void {
if (
mismatches.length > 0 ||
summary.invalidAuditEvents > 0 ||
summary.invalidIssuedTrafficReports > 0 ||
summary.missingUserRegistrationEvents > 0 ||
summary.invalidDownloadTaskEvents > 0 ||
summary.orphanRollupBuckets > 0 ||
@@ -1344,12 +1412,14 @@ export function assertBackfillValidation(summary: ValidationSummary): void {
summary.incompatibleUserSnapshotRows > 0 ||
summary.counterMissingBuckets > 0 ||
summary.signupMissingBuckets > 0 ||
summary.trafficMissingBuckets > 0 ||
summary.openCounterMarkers > 0
) {
throw new Error(
`admin_stats_validation_failed:${JSON.stringify({
mismatches,
invalidAuditEvents: summary.invalidAuditEvents,
invalidIssuedTrafficReports: summary.invalidIssuedTrafficReports,
missingUserRegistrationEvents: summary.missingUserRegistrationEvents,
invalidDownloadTaskEvents: summary.invalidDownloadTaskEvents,
orphanRollupBuckets: summary.orphanRollupBuckets,
@@ -1360,6 +1430,7 @@ export function assertBackfillValidation(summary: ValidationSummary): void {
incompatibleUserSnapshotRows: summary.incompatibleUserSnapshotRows,
counterMissingBuckets: summary.counterMissingBuckets,
signupMissingBuckets: summary.signupMissingBuckets,
trafficMissingBuckets: summary.trafficMissingBuckets,
openCounterMarkers: summary.openCounterMarkers,
})}`,
)
@@ -1374,7 +1445,11 @@ function main(): void {
const plan = querySummary<BackfillPlan>(options.target, BACKFILL_PLAN_SQL)
console.log(JSON.stringify({ mode: options.apply ? 'apply' : 'dry-run', before, plan }, null, 2))
if (!options.apply) return
const maxExpectedBuckets = Math.max(before.counterExpectedBuckets, before.signupExpectedBuckets)
const maxExpectedBuckets = Math.max(
before.counterExpectedBuckets,
before.signupExpectedBuckets,
before.trafficExpectedBuckets,
)
if (maxExpectedBuckets > MAX_BACKFILL_HOURS) {
throw new Error(`admin_stats_backfill_range_too_large:${maxExpectedBuckets}`)
}
@@ -89,6 +89,26 @@ describe('admin stats source integrity', () => {
expect(rows).toEqual([{ events: 0, issuedAt: issuedAt.getTime() }])
})
it('validates issued traffic from the earlier traffic-ledger boundary', async () => {
const { db } = await createTestApp()
const reports = createCloudTrafficReportRepo(db)
await reports.ensureLedgerOpening(new Date('2026-07-21T10:05:00.000Z'))
const opening = await ensureAdminStatsIntegrityOpening(db, new Date('2026-07-21T12:00:00.000Z'))
const issuedAt = Date.parse('2026-07-21T11:30:00.000Z')
await db.run(sql`
INSERT INTO cloud_traffic_reports (
id, org_id, period, source, source_id, event_id, bytes, status, issued_at, created_at, updated_at
) VALUES (
'traffic-before-global-invalid', 'org-1', '2026-07', 'unknown', 'matter-1',
'traffic-before-global-invalid', 10, 'not_required', ${issuedAt}, ${issuedAt}, ${issuedAt}
)
`)
const integrity = await inspectAdminStatsSourceIntegrity(db, opening)
expect(integrity.invalidIssuedTrafficReports).toBe(1)
})
it('rejects missing traffic reports without creating a duplicate event', async () => {
const { db } = await createTestApp()
const reports = createCloudTrafficReportRepo(db)
@@ -2,6 +2,7 @@ import { eq, sql } from 'drizzle-orm'
import { auditEvents, systemOptions } from '../../db/schema'
import { validDownloadTaskEventPredicate } from '../../domain/download-task-events'
import type { Database } from '../../platform/interface'
import { createCloudTrafficReportRepo, trafficLedgerExactFrom } from './cloud-traffic-report'
const OPENING_SOURCE_ID = 'v3-authoritative-sources'
const OPENING_EVENT_ID = `audit:statistics_source_initialized:${OPENING_SOURCE_ID}`
@@ -56,6 +57,8 @@ export async function inspectAdminStatsSourceIntegrity(
if (!exactFrom) throw new Error('admin_stats_integrity_opening_missing')
const exactFromMs = exactFrom.getTime()
const exactFromSec = Math.floor(exactFromMs / 1000)
const trafficOpening = await createCloudTrafficReportRepo(db).getLedgerOpening()
const trafficExactFromMs = trafficOpening ? trafficLedgerExactFrom(trafficOpening).getTime() : exactFromMs
const validTaskEvent = sql.raw(validDownloadTaskEventPredicate('task_event.value'))
const rows = await db.all<Omit<AdminStatsSourceIntegrity, 'exactFrom'>>(sql`
WITH billable_storage AS (
@@ -144,7 +147,7 @@ export async function inspectAdminStatsSourceIntegrity(
(SELECT count FROM invalid_task_events) AS invalidDownloadTaskEvents,
(SELECT COUNT(*)
FROM cloud_traffic_reports ctr
WHERE ctr.issued_at >= ${exactFromMs}
WHERE ctr.issued_at >= ${trafficExactFromMs}
AND (
ctr.status = 'reversed'
OR ctr.bytes < 0
+6 -2
View File
@@ -274,6 +274,7 @@ async function getDashboardOverviewStats(
trafficLedgerAvailable,
previousTrafficLedgerAvailable,
counterDays,
downloadCounterDays,
signupDays,
signupCoverage,
previousSignupCoverage,
@@ -297,6 +298,7 @@ async function getDashboardOverviewStats(
trafficLedgerAvailableInRange(db, effective),
trafficLedgerAvailableInRange(db, previous),
reader.completeDayKeys('counters'),
reader.completeDayKeys('counters', ADMIN_STATS_METRICS.transferDownloadIssued),
reader.completeDayKeys('counters', ADMIN_STATS_METRICS.userSignup),
reader.coverage('counters', ADMIN_STATS_METRICS.userSignup),
previousReader.coverage('counters', ADMIN_STATS_METRICS.userSignup),
@@ -320,7 +322,7 @@ async function getDashboardOverviewStats(
storageUsedBytes: storageUsedByDay.get(date) ?? null,
uploadBytes: counterDays.has(date) && !missingBytes?.upload ? (uploadByDay.get(date) ?? 0) : null,
downloadBytes:
counterDays.has(date) &&
downloadCounterDays.has(date) &&
trafficFirstExactDay !== null &&
date >= trafficFirstExactDay &&
!missingBytes?.download
@@ -696,6 +698,7 @@ async function getDashboardTrafficStats(
trafficLedgerAvailable,
previousTrafficLedgerAvailable,
counterDays,
downloadCounterDays,
trafficFirstExactDay,
] = await Promise.all([
getTrafficTotals(reader),
@@ -719,6 +722,7 @@ async function getDashboardTrafficStats(
trafficLedgerAvailableInRange(db, effective),
trafficLedgerAvailableInRange(db, previous),
reader.completeDayKeys('counters'),
reader.completeDayKeys('counters', ADMIN_STATS_METRICS.transferDownloadIssued),
trafficLedgerFirstExactDay(db),
])
const sourceRows = new Map<string, { name: string; bytes: number; requests: number }>()
@@ -748,7 +752,7 @@ async function getDashboardTrafficStats(
date,
uploadBytes: counterDays.has(date) && !missingBytesByDay.get(date)?.upload ? (uploadByDay.get(date) ?? 0) : null,
downloadBytes:
counterDays.has(date) &&
downloadCounterDays.has(date) &&
trafficFirstExactDay !== null &&
date >= trafficFirstExactDay &&
!missingBytesByDay.get(date)?.download
@@ -327,6 +327,62 @@ describe('site stats routes', () => {
expect(sharing.typeBreakdown).toEqual([])
})
it('uses the traffic ledger boundary instead of the later global statistics boundary', async () => {
const { app, db } = await createTestApp()
const headers = await adminHeaders(app)
await seedProLicense(db)
const firstHour = Date.UTC(2026, 6, 1, 23)
const secondHour = firstHour + 3_600_000
const issuedAt = firstHour + 10 * 60_000
await db.run(sql`
INSERT INTO cloud_traffic_reports (
id, org_id, period, source, source_id, event_id, bytes, status, issued_at, created_at, updated_at
) VALUES
('traffic_ledger_opening_v1', '', '2026-07', 'object_download', 'traffic_ledger_opening_v1',
'traffic_ledger_opening_v1', 0, 'ledger_opening', NULL, ${firstHour - 3_600_000}, ${firstHour - 3_600_000}),
('traffic-before-global-opening', 'org-traffic', '2026-07', 'object_download', 'file-traffic',
'traffic-before-global-opening', 1024, 'not_required', ${issuedAt}, ${issuedAt}, ${issuedAt})
`)
await db.run(sql`
INSERT INTO stats_rollups_hourly (
id, bucket_start, org_id, metric_key, dimension_key, dimension_value,
count, bytes, unique_count, metadata, updated_at
) VALUES
('traffic-marker-first', ${firstHour}, '', 'stats.rollup_run', 'metric_key', 'transfer.download_issued',
1, 0, 0, '{"version":3,"scope":"counters","quality":"exact"}', ${firstHour + 3_600_000}),
('traffic-marker-second', ${secondHour}, '', 'stats.rollup_run', 'metric_key', 'transfer.download_issued',
1, 0, 0, '{"version":3,"scope":"counters","quality":"exact"}', ${secondHour + 3_600_000}),
('global-marker-second', ${secondHour}, '', 'stats.rollup_run', '', '',
1, 0, 0, '{"version":3,"scope":"counters","quality":"exact"}', ${secondHour + 3_600_000}),
('traffic-result-first', ${firstHour}, 'org-traffic', 'transfer.download_issued', '', '',
1, 1024, 0, '{"version":3,"scope":"counters","quality":"exact"}', ${firstHour + 3_600_000}),
('traffic-source-first', ${firstHour}, 'org-traffic', 'transfer.download_issued', 'source', 'object_download',
1, 1024, 0, '{"version":3,"scope":"counters","quality":"exact"}', ${firstHour + 3_600_000})
`)
const res = await app.request(
'/api/site/stats/traffic?from=2026-07-01T23%3A00%3A00.000Z&to=2026-07-02T00%3A59%3A59.999Z&timeZone=UTC',
{ headers },
)
const body = (await res.json()) as {
summary: { totalBytes: { value: number | null }; issuedDownloads: number | null }
trafficTrend: Array<{
date: string
uploadBytes: number | null
downloadBytes: number | null
requests: number | null
}>
}
expect(res.status).toBe(200)
expect(body.summary.totalBytes.value).toBe(1024)
expect(body.summary.issuedDownloads).toBe(1)
expect(body.trafficTrend).toEqual([
{ date: '2026-07-01', uploadBytes: null, downloadBytes: 1024, requests: null },
{ date: '2026-07-02', uploadBytes: 0, downloadBytes: 0, requests: 0 },
])
})
it('publishes exact current-hour snapshots without treating open counters as complete', async () => {
const { db } = await createTestApp()
const bucketStart = Date.UTC(2026, 6, 22, 10)
+23 -1
View File
@@ -58,6 +58,8 @@ describe('admin stats backfill', () => {
const now = new Date('2026-07-10T12:00:00.000Z')
const historyStartMs = Date.parse('2026-04-01T00:10:00.000Z')
const preExactSignupMs = Date.parse('2026-03-31T22:10:00.000Z')
const trafficOpeningMs = Date.parse('2026-03-31T20:10:00.000Z')
const trafficFirstHour = Math.ceil(trafficOpeningMs / 3_600_000) * 3_600_000
const eventMs = Date.parse('2026-07-10T09:10:00.000Z')
const eventHourMs = Date.parse('2026-07-10T09:00:00.000Z')
const sessionCreatedMs = Date.parse('2026-07-10T08:00:00.000Z')
@@ -73,6 +75,7 @@ describe('admin stats backfill', () => {
const expectedBuckets = (latestClosedHour - firstExactHour) / 3_600_000 + 1
const signupFirstHour = Math.floor(preExactSignupMs / 3_600_000) * 3_600_000
const expectedSignupBuckets = (latestClosedHour - signupFirstHour) / 3_600_000 + 1
const expectedTrafficBuckets = (latestClosedHour - trafficFirstHour) / 3_600_000 + 1
db.exec(`
CREATE TABLE user (id TEXT PRIMARY KEY, created_at INTEGER NOT NULL DEFAULT 0, last_active_at INTEGER);
CREATE TABLE account (id TEXT PRIMARY KEY, user_id TEXT NOT NULL, provider_id TEXT NOT NULL, created_at INTEGER NOT NULL);
@@ -183,6 +186,11 @@ describe('admin stats backfill', () => {
id, org_id, period, source, source_id, event_id, bytes, storage_id, unit_bytes, credits_per_unit,
status, error, attempt_count, next_retry_at, issued_at, created_at, updated_at
) VALUES
('traffic_ledger_opening_v1', '', '2026-03', 'object_download', 'traffic_ledger_opening_v1',
'traffic_ledger_opening_v1', 0, NULL, NULL, NULL, 'ledger_opening', NULL, 0, NULL, NULL,
${trafficOpeningMs}, ${trafficOpeningMs}),
('r0', 'o1', '2026-03', 'object_download', 'f1', 'traffic-0', 64, NULL, NULL, NULL,
'not_required', NULL, 0, NULL, ${preExactSignupMs}, ${preExactSignupMs}, ${preExactSignupMs}),
('r1', 'o1', '2026-07', 'direct_share', 's1', 'traffic-1', 512, NULL, NULL, NULL, 'reported', NULL, 0, NULL, NULL, ${eventMs}, ${eventMs}),
('r2', 'o1', '2026-07', 'image_hosting', 'img1', 'traffic-2', 128, NULL, NULL, NULL, 'reported', NULL, 0, NULL, NULL, ${eventMs}, ${eventMs}),
('r3', 'o1', '2026-07', 'object_download', 'f1', 'traffic-3', 512, NULL, NULL, NULL, 'blocked', 'quota_exceeded', 0, NULL, NULL, ${eventMs}, ${eventMs});
@@ -255,7 +263,14 @@ describe('admin stats backfill', () => {
...buildAdminStatsCounterRowsSqlStatements({
fromMs: firstExactHour,
toMs: currentHourMs,
metrics: ADMIN_STATS_FACT_COUNTER_METRICS.filter((metric) => metric !== 'user.signup'),
metrics: ADMIN_STATS_FACT_COUNTER_METRICS.filter(
(metric) => metric !== 'user.signup' && metric !== 'transfer.download_issued',
),
}).flatMap((statement) => db.prepare(statement).all()),
...buildAdminStatsCounterRowsSqlStatements({
fromMs: trafficFirstHour,
toMs: currentHourMs,
metrics: ['transfer.download_issued'],
}).flatMap((statement) => db.prepare(statement).all()),
...buildAdminStatsCounterRowsSqlStatements({
fromMs: signupFirstHour,
@@ -307,6 +322,9 @@ describe('admin stats backfill', () => {
signupExpectedBuckets: expectedSignupBuckets,
signupCompletedBuckets: expectedSignupBuckets,
signupMissingBuckets: 0,
trafficExpectedBuckets: expectedTrafficBuckets,
trafficCompletedBuckets: expectedTrafficBuckets,
trafficMissingBuckets: 0,
openCounterMarkers: 0,
requiredDimensionMismatchGroups: 0,
userSignupProviderMismatchGroups: 0,
@@ -314,6 +332,10 @@ describe('admin stats backfill', () => {
lowerBoundRollups: 0,
rawUploadAttempts: 1,
rollupUploadAttempts: 1,
rawDownloadEvents: 2,
rollupDownloadEvents: 2,
rawDownloadBytes: 576,
rollupDownloadBytes: 576,
rawUserSignups: 3,
rollupUserSignups: 3,
rawSharesCreated: 1,