Skip to content

fix(relay): attribute storage metrics per user, not per community#1885

Draft
wpfleger96 wants to merge 2 commits into
mainfrom
duncan/per-user-storage-metrics
Draft

fix(relay): attribute storage metrics per user, not per community#1885
wpfleger96 wants to merge 2 commits into
mainfrom
duncan/per-user-storage-metrics

Conversation

@wpfleger96

@wpfleger96 wpfleger96 commented Jul 15, 2026

Copy link
Copy Markdown
Member

Summary

Add per-user logical-storage observability to the relay so Block can monitor how much media storage each uploader consumes per community, informing the upcoming free-plan storage limits.

These metrics are audit-derived logical upload attributions, not authoritative physical S3 usage and not a source for quota enforcement: audit-log delivery can fail independently of the upload, content may be physically deduplicated on disk, and deletion/GC of stored objects is not represented here.

  • Migration (0019_audit_log_media_index.sql): partial index on audit_log(community_id, actor_pubkey, object_id) WHERE action = 'media_uploaded', built CONCURRENTLY outside a transaction so it doesn't lock writers on a populated relay. object_id is included because it's the dedup key the query relies on; the planner uses this index for an Index Scan, followed by an Incremental Sort + Unique to resolve DISTINCT ON's max-size tiebreaker (not covered by the index).
  • Usage query (buzz-db/src/usage.rs): storage_byte_counts() returns per-user (community_id, actor_pubkey) totals. A subquery first dedupes to one row per (community_id, actor_pubkey, object_id) — a logical object requires a non-null actor and non-null object_id — so a user re-uploading the same blob is charged once, not once per audit-log row. detail->>'size' is historical, untyped JSONB; malformed, negative, or out-of-range values fall back to 0 bytes instead of aborting the whole poller query. The per-user SUM accumulates in numeric (arbitrary precision) and is clamped to [0, i64::MAX] before the final cast to bigint, so an extreme aggregate degrades to a saturated reading instead of erroring the whole query.
  • Poller gauges (buzz-relay/src/main.rs): buzz_user_storage_bytes{community,pubkey} / buzz_user_storage_objects{community,pubkey} per uploader, rolled up into buzz_community_storage_bytes{community} / buzz_community_storage_objects{community} and buzz_total_storage_bytes / buzz_total_storage_objects fleet totals via saturating addition (same overflow-safety rationale as the SQL aggregate). Named *_objects rather than *_uploads to stay distinct from the real-time buzz_media_upload_bytes_total event counter, which is unchanged. Per-user cardinality (two custom series per community,pubkey pair) is intentional — BUZZ_USAGE_METRICS_PER_COMMUNITY=off remains the emergency kill switch.

Add logical-attribution storage observability: a partial index on
audit_log for media_uploaded actions, per-community byte/upload gauges
in the usage poller, and a real-time upload-bytes counter in the media
handler.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
@wpfleger96 wpfleger96 requested a review from a team as a code owner July 15, 2026 01:27
@wpfleger96 wpfleger96 marked this pull request as draft July 15, 2026 01:30
Group storage bytes/objects by (community_id, actor_pubkey), matching
the PR's stated goal instead of only rolling up per community. Dedupe
by (community_id, actor_pubkey, object_id) in a subquery so idempotent
re-uploads of the same blob are charged once, not once per audit row.

Extend the audit_log partial index to (community_id, actor_pubkey,
object_id) — the new dedup key — and fail safe on malformed historical
detail->>'size' values (non-numeric, negative, out-of-range) by
attributing 0 bytes instead of aborting the whole poller query. Cast
the aggregate SUM to bigint explicitly so Postgres returns an i64-
compatible value instead of numeric.

Rename polled *_uploads gauges/fields to *_objects to keep them
distinct from the real-time buzz_media_upload_bytes_total event
counter, which is intentionally unchanged.

Add buzz_user_storage_bytes/buzz_user_storage_objects gauges labeled
by community + pubkey; the previous community-only gauges remain as
rollups of the new per-user rows.

Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
@wpfleger96 wpfleger96 changed the title feat(relay): add per-community storage metrics from audit log fix(relay): attribute storage metrics per user, not per community Jul 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant