feat(subnets): serve the per-subnet event summary from the lakehouse - #9304
Merged
Conversation
/api/v1/subnets/{netuid}/event-summary reported total_events 0 for EVERY
netuid -- 1, 8, 19 and 64 all answered zero -- while /subnets/{netuid}/events
served real rows off the same account_events stream. Two views of one stream,
opposite answers, the same shape as #9260.
All three surfaces ran tryPostgresTier(METAGRAPH_ACCOUNT_EVENTS_SOURCE) ??
buildSubnetEventSummary([], [], ...) and nothing Cloudflare-native replaced the
deleted Postgres tier, so add one shared reader and wire all three at once.
THE OBVIOUS PORT IS REJECTED. One grouped rollup carrying count(*) plus
count(DISTINCT hotkey) and count(DISTINCT coldkey) fails at this route's own
default window:
40015: scan budget exceeded: scanning too much data for count(DISTINCT),
count(DISTINCT) with GROUP BY
Note "with GROUP BY". Unlike #9252, #9261 and #9280 -- where the fix was ADDING
a GROUP BY -- adding one is not the fix here: two distincts in a single grouped
scan exceed the budget on their own, and this route also offers 90d, three
times the span that already fails. So each distinct is distributed into its own
nested aggregation, grouping to the (kind, key) pairs and then counting the
pairs per kind.
`hotkey IS NOT NULL` is load-bearing rather than tidy. COUNT(DISTINCT col)
ignores NULLs while GROUP BY col yields a NULL group, so without it every kind
whose rows carry no hotkey -- WeightsSet, and every Balances kind for coldkey --
would report one participant that does not exist.
An empty window is a MEASURED ZERO, not a decline: the query layer returns null
on failure and [] on a successful empty scan, so a quiet subnet can say so
rather than being indistinguishable from the broken tier this fixes. A failed
read still declines, so the card can never pair real counts with zeroed
participants.
Verified live for netuid 64 over 30d: WeightsSet 9,832 events, StakeAdded 8,517
events across 66 distinct hotkeys and 2,109 distinct coldkeys.
Writing the limit test found a real defect in the first draft: `??` only
catches null/undefined, so a literal 0 sailed through the default resolution
and produced LIMIT 0 -- a silently empty recent-events page. parseLimitParam
rejects 0 at the REST edge, but MCP and GraphQL call this reader directly. The
positivity check is now part of resolving an unusable limit rather than a
separate guard.
Three guards that could never fire were removed rather than left as untestable
defensive code: a cap floor the resolution already guarantees, an arithmetic
check on a cutoff derived from a validated window, and a column-name guard over
two string literals.
Closes #9303
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
metagraphed-data-api | 8621620 | Aug 03 2026, 08:29 PM |
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
metagraphed-registry-sync-api | 8621620 | Aug 03 2026, 08:29 PM |
|
Superagent didn't find any vulnerabilities or security issues in this PR. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
/api/v1/subnets/{netuid}/event-summaryreportedtotal_events: 0for every netuid, while/subnets/{netuid}/eventsserved real rows off the sameaccount_eventsstream:/event-summary/eventsTwo views of one stream, opposite answers — the same shape as #9260. All three surfaces ran
tryPostgresTier(…) ?? buildSubnetEventSummary([], [], …)and nothing Cloudflare-native replacedthe deleted Postgres tier.
The obvious port is rejected by the engine
One grouped rollup with
count(*)pluscount(DISTINCT hotkey)andcount(DISTINCT coldkey)failsat this route's own default window:
Note "with GROUP BY". Unlike #9252, #9261 and #9280 — where the fix was adding a
GROUP BY—adding one is not the fix here. Two distincts in a single grouped scan exceed the budget on their
own, and this route also offers 90d, three times the span that already fails.
Each distinct is therefore distributed into its own nested aggregation: group to the
(kind, key)pairs, then count the pairs per kind — the form the engine's own error message asksfor.
hotkey IS NOT NULLis load-bearing, not tidiness.COUNT(DISTINCT col)ignores NULLs whileGROUP BY colyields a NULL group, so without it every kind whose rows carry no hotkey(
WeightsSet, and every Balances kind for coldkey) would report one participant that does notexist.
An empty window is a measured zero
The query layer returns
nullon failure and[]on a successful empty scan, so a quiet subnetpublishes a real zero rather than being indistinguishable from the broken tier this fixes. A failed
read still declines, so the card can never pair real counts with zeroed participants.
Verification
Live against the lakehouse, netuid 64 over 30d:
WeightsSet9,832 events;StakeAdded8,517 events across 66 distinct hotkeys and 2,109 distinct coldkeys. All four query
shapes confirmed to run — including the rejected one, to prove the rejection.
Six mutations; five caught:
count(DISTINCT)IS NOT NULLfrom the distinct readsString(kind)The last is reported honestly: entries for kinds absent from the base rollup are never looked up, so
that guard has no observable effect. Its branch is covered; its mutation is not detectable, and I
did not add a test that would only appear to detect it.
Writing the limit test found a real defect in my first draft —
??only catchesnull/undefined, so a literal0sailed through the default resolution and producedLIMIT 0, asilently empty recent-events page.
parseLimitParamrejects0at the REST edge, but MCP andGraphQL call this reader directly.
I also removed three guards that could never fire rather than leave untestable defensive code: a
cap floor the resolution already guarantees, an arithmetic check on a cutoff derived from an
already-validated window, and a column-name guard over two string literals.
Patch coverage 21/21 = 100% with every branch taken, measured by intersecting the diff's line
ranges with v8's uncovered set. 1,513 tests pass across the affected suites;
tsc --noEmit,prettier and eslint clean.
One pre-existing failure is unrelated and reproduces on clean
origin/main:mcp-server.test.ts > "resolves real artifacts from the local env"(service_count >= 1) needslocally built artifacts, which CI produces.
Closes #9303