feat: collection-scoped activity bucketing - #4410
Conversation
Mirrors project-side activityRelations bucketing (hash-based, 10 buckets) for collections. Adds 10 streaming MVs, 10 raw MV datasources, 10 daily enrich/dedup copy pipes, 10 cleaned bucket datasources, the collection_buckets resolver, and the activityRelations_collection_bucket_union query pipe. Points activities_filtered/activities_filtered_retention/activityTypes_by_project at the new collection bucket union instead of the full unbucketed scan. Adds collectionSlug support to contributors_leaderboard and contributor_dependency via a scalar-total pattern instead of a window function, avoiding the same collection-scale timeout that motivated collection_contributors_leaderboard. Signed-off-by: Gašper Grom <gasper.grom@gmail.com>
collection_contributors_leaderboard.pipe and collection_contributor_dependency.pipe (+ its copy pipe/datasource) are superseded now that contributors_leaderboard.pipe and contributor_dependency.pipe support collectionSlug scope directly via a scalar-total pattern. Insights has been updated to call the unified pipes and that change is deployed to production; these pipes had zero remaining traffic and were removed from Tinybird staging/production before this commit. Signed-off-by: Gašper Grom <gasper.grom@gmail.com>
PR SummaryHigh Risk Overview Collection queries in Contributor and aggregate APIs are consolidated: Adds Reviewed by Cursor Bugbot for commit c6d883e. Bugbot is set up for automated code reviews on this repo. Configure here. |
|
Your PR title doesn't contain a Jira issue key. Consider adding it for better traceability. Example:
Projects:
Please add a Jira issue key to your PR title. |
There was a problem hiding this comment.
Pull request overview
Adds collection-scoped Tinybird activity bucketing to improve query performance.
Changes:
- Adds 10 collection bucket ingestion, cleanup, and query pipelines.
- Routes collection activity queries through the new buckets.
- Unifies contributor pipes and removes collection-specific equivalents.
Review blockers include community-collection data loss, stale membership fan-out, dependency-query scalability, and overlapping copy schedules.
Reviewed changes
Copilot reviewed 61 out of 61 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
contributors_leaderboard.pipe |
Adds collection scope and scalar totals. |
contributor_dependency.pipe |
Adds collection scope. |
collection_contributors_leaderboard.pipe |
Removes legacy collection leaderboard. |
collection_contributor_dependency.pipe |
Removes collection dependency endpoint. |
collection_contributor_dependency_copy.pipe |
Removes dependency precomputation. |
collection_buckets.pipe |
Adds collection hash resolution. |
activityTypes_by_project.pipe |
Routes collections through new union. |
activityRelations_collection_bucket_union.pipe |
Unions ten collection buckets. |
activityRelations_collection_bucket_MV_snapshot_0.pipe |
Adds bucket 0 backfill. |
activityRelations_collection_bucket_MV_snapshot_1.pipe |
Adds bucket 1 backfill. |
activityRelations_collection_bucket_MV_snapshot_2.pipe |
Adds bucket 2 backfill. |
activityRelations_collection_bucket_MV_snapshot_3.pipe |
Adds bucket 3 backfill. |
activityRelations_collection_bucket_MV_snapshot_4.pipe |
Adds bucket 4 backfill. |
activityRelations_collection_bucket_MV_snapshot_5.pipe |
Adds bucket 5 backfill. |
activityRelations_collection_bucket_MV_snapshot_6.pipe |
Adds bucket 6 backfill. |
activityRelations_collection_bucket_MV_snapshot_7.pipe |
Adds bucket 7 backfill. |
activityRelations_collection_bucket_MV_snapshot_8.pipe |
Adds bucket 8 backfill. |
activityRelations_collection_bucket_MV_snapshot_9.pipe |
Adds bucket 9 backfill. |
activityRelations_collection_bucket_MV_0.pipe |
Adds bucket 0 fan-out MV. |
activityRelations_collection_bucket_MV_1.pipe |
Adds bucket 1 fan-out MV. |
activityRelations_collection_bucket_MV_2.pipe |
Adds bucket 2 fan-out MV. |
activityRelations_collection_bucket_MV_3.pipe |
Adds bucket 3 fan-out MV. |
activityRelations_collection_bucket_MV_4.pipe |
Adds bucket 4 fan-out MV. |
activityRelations_collection_bucket_MV_5.pipe |
Adds bucket 5 fan-out MV. |
activityRelations_collection_bucket_MV_6.pipe |
Adds bucket 6 fan-out MV. |
activityRelations_collection_bucket_MV_7.pipe |
Adds bucket 7 fan-out MV. |
activityRelations_collection_bucket_MV_8.pipe |
Adds bucket 8 fan-out MV. |
activityRelations_collection_bucket_MV_9.pipe |
Adds bucket 9 fan-out MV. |
activityRelations_collection_bucket_clean_enrich_copy_pipe_0.pipe |
Adds bucket 0 cleanup. |
activityRelations_collection_bucket_clean_enrich_copy_pipe_1.pipe |
Adds bucket 1 cleanup. |
activityRelations_collection_bucket_clean_enrich_copy_pipe_2.pipe |
Adds bucket 2 cleanup. |
activityRelations_collection_bucket_clean_enrich_copy_pipe_3.pipe |
Adds bucket 3 cleanup. |
activityRelations_collection_bucket_clean_enrich_copy_pipe_4.pipe |
Adds bucket 4 cleanup. |
activityRelations_collection_bucket_clean_enrich_copy_pipe_5.pipe |
Adds bucket 5 cleanup. |
activityRelations_collection_bucket_clean_enrich_copy_pipe_6.pipe |
Adds bucket 6 cleanup. |
activityRelations_collection_bucket_clean_enrich_copy_pipe_7.pipe |
Adds bucket 7 cleanup. |
activityRelations_collection_bucket_clean_enrich_copy_pipe_8.pipe |
Adds bucket 8 cleanup. |
activityRelations_collection_bucket_clean_enrich_copy_pipe_9.pipe |
Adds bucket 9 cleanup. |
activities_filtered.pipe |
Routes collection activity filtering. |
activities_filtered_retention.pipe |
Routes collection retention queries. |
collection_contributor_dependency_copy_ds.datasource |
Removes dependency cache schema. |
activityRelations_collection_deduplicated_cleaned_bucket_0_ds.datasource |
Defines cleaned bucket 0. |
activityRelations_collection_deduplicated_cleaned_bucket_1_ds.datasource |
Defines cleaned bucket 1. |
activityRelations_collection_deduplicated_cleaned_bucket_2_ds.datasource |
Defines cleaned bucket 2. |
activityRelations_collection_deduplicated_cleaned_bucket_3_ds.datasource |
Defines cleaned bucket 3. |
activityRelations_collection_deduplicated_cleaned_bucket_4_ds.datasource |
Defines cleaned bucket 4. |
activityRelations_collection_deduplicated_cleaned_bucket_5_ds.datasource |
Defines cleaned bucket 5. |
activityRelations_collection_deduplicated_cleaned_bucket_6_ds.datasource |
Defines cleaned bucket 6. |
activityRelations_collection_deduplicated_cleaned_bucket_7_ds.datasource |
Defines cleaned bucket 7. |
activityRelations_collection_deduplicated_cleaned_bucket_8_ds.datasource |
Defines cleaned bucket 8. |
activityRelations_collection_deduplicated_cleaned_bucket_9_ds.datasource |
Defines cleaned bucket 9. |
activityRelations_collection_bucket_MV_ds_0.datasource |
Defines raw bucket 0. |
activityRelations_collection_bucket_MV_ds_1.datasource |
Defines raw bucket 1. |
activityRelations_collection_bucket_MV_ds_2.datasource |
Defines raw bucket 2. |
activityRelations_collection_bucket_MV_ds_3.datasource |
Defines raw bucket 3. |
activityRelations_collection_bucket_MV_ds_4.datasource |
Defines raw bucket 4. |
activityRelations_collection_bucket_MV_ds_5.datasource |
Defines raw bucket 5. |
activityRelations_collection_bucket_MV_ds_6.datasource |
Defines raw bucket 6. |
activityRelations_collection_bucket_MV_ds_7.datasource |
Defines raw bucket 7. |
activityRelations_collection_bucket_MV_ds_8.datasource |
Defines raw bucket 8. |
activityRelations_collection_bucket_MV_ds_9.datasource |
Defines raw bucket 9. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Adds bucket_id/num_buckets params to all 10 activityRelations_collection_bucket_MV_snapshot_N pipes, mirroring pull_request_analysis_initial_snapshot.pipe's chunking pattern. Lets the on-demand historical backfill run in smaller sequential chunks instead of one long-running job, avoiding the copy-job quota/overload issues seen running the full backfill in one shot. Signed-off-by: Gašper Grom <gasper.grom@gmail.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 61 out of 61 changed files in this pull request and generated 3 comments.
Comments suppressed due to low confidence (4)
services/libs/tinybird/pipes/activities_filtered.pipe:31
- This route drops all community-collection activity.
segments_filtered_by_collectionintentionally resolves both curated and community slugs (seeinsights_projects_populated_copy.pipe:1-16), but every new collection bucket MV filters its fan-out tossoUserId IS NULL. A request for anssoUserId IS NOT NULLcollection therefore has valid segments yet reads an empty source. Keep the previous all-bucket source as a fallback for community collections, or include those collections in the new bucket pipeline.
{% if defined(collectionSlug) %} activityRelations_collection_bucket_union
services/libs/tinybird/pipes/activities_filtered.pipe:31
- Pre-fanning membership only when an
activityRelationsrow is inserted makes historical results stale when an existing project is later added to a collection.insights_projects_populated_dsrefreshes memberships every 15 minutes, but these streaming MVs are not triggered by that refresh and the snapshots are on-demand only, so old activities are never copied into the new collection's bucket. Before switching the shared filter to this source, add a recurring membership-aware rebuild/backfill or another path that preserves current-membership semantics.
{% if defined(collectionSlug) %} activityRelations_collection_bucket_union
services/libs/tinybird/pipes/activityRelations_collection_bucket_clean_enrich_copy_pipe_0.pipe:57
- Bucket 0 and bucket 1 are both scheduled at 01:00, and
activityRelations_snapshot_merger_copyalso runs then. This contradicts the stated five-minute staggering and launches multiple full-table jobs together. Give bucket 0 a distinct five-minute slot so only one collection full-replace runs at a time.
services/libs/tinybird/pipes/activityRelations_collection_bucket_MV_0.pipe:8 - This references a collection-bucketing addendum that does not exist in
bucketing-architecture.md; that document currently describes only project-side buckets. Add the promised architecture and operational/backfill guidance, especially because collection membership changes require a different consistency model from deterministic project bucketing.
up to N rows across up to N different buckets - this is intentional write amplification, see
bucketing-architecture.md's collection-bucketing addendum.
- Replace 10 per-bucket datasources with 1 shared consolidated target - All copy pipes append with daily snapshotId (snapshot + TTL pattern) - TTL of 2 days auto-cleans old snapshots - Consume-side pipe filters to max(snapshotId) for latest data - No breaking change to endpoint shape Signed-off-by: Gašper Grom <gasper.grom@gmail.com>
…XXXX) Delete the 10 per-bucket datasources and the abandoned contributors_ds from an earlier consolidation attempt - none are referenced by any pipe after collapsing to the single collection_insights_aggregate_consolidated_ds. Also fixes stale comment references in the 10 copy pipes that still named the old contributors_ds target. Signed-off-by: Gašper Grom <gasper.grom@gmail.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 75 out of 75 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (4)
services/libs/tinybird/pipes/activities_filtered.pipe:31
- This routes every
collectionSlugrequest to a datasource populated only for curated collections (ssoUserId IS NULLin each new bucket MV). However,insights_projects_populated_copy.pipe:3-15intentionally includes both curated and community collections after IN-1195, andsegments_filtered_by_collectionsupports both. Community collection requests will therefore return no activities here (and in the two direct union consumers). Materialize community collections too, or retain a fallback to the project-bucket union for them.
{% if defined(collectionSlug) %} activityRelations_collection_bucket_union
services/libs/tinybird/pipes/contributor_dependency.pipe:9
- Collection dependency requests now always execute the live per-member aggregation. The deleted collection-specific endpoint documents that this live
GROUP BY memberIdtimed out and triggered Tinybird concurrency alerts for large collections; moving the percentage denominator to a scalar removes the window overhead but not that expensive grouping. Preserve an equivalent precomputed default path before routing production collection requests through this pipe.
services/libs/tinybird/pipes/collection_insights_aggregate.pipe:37 - A collection with no matching contribution rows now returns
NULLbecause this is a left join, whereas the previousuniq(...)aggregate returned0. Preserve the endpoint's numeric response contract so consumers do not receive a nullable count.
services/libs/tinybird/pipes/activityRelations_collection_bucket_clean_enrich_copy_pipe_0.pipe:57 - Bucket 0 and bucket 1 are both scheduled at
0 1 * * *, so two full-bucket replacement jobs start simultaneously despite the stated five-minute staggering intended to avoid load spikes. Assign distinct slots across all ten jobs.
Signed-off-by: Gašper Grom <gasper.grom@gmail.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 75 out of 75 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (5)
services/libs/tinybird/pipes/contributor_dependency.pipe:9
- Collection requests to this pipe now always execute the live leaderboard aggregation. The deleted
collection_contributor_dependency.pipedocuments that this same collection-wideGROUP BY af.memberIdpath caused production timeouts/rate-limit alerts and therefore usedcollection_contributor_dependency_copy_dsfor the default unfilteredlimit=100request. The scalar-total change removes the percentage window, but it does not remove that full member aggregation, so routing the Insights client to this unified pipe reintroduces the known production failure mode. Preserve an equivalent precomputed path (or otherwise demonstrate that the default collection query no longer performs the full live aggregation) before deleting the copy pipeline.
services/libs/tinybird/pipes/activities_filtered.pipe:31 - This unconditionally routes every
collectionSlugthrough buckets populated only for curated collections: all new fan-out MVs filtercollectionswithisNull(ssoUserId).segments_filtered_by_collectionandinsights_projects_populated_dsexplicitly support community collections too, so those requests now return no activities. Either include community collections in the bucket population or retain a correct fallback for them.
{% if defined(collectionSlug) %} activityRelations_collection_bucket_union
services/libs/tinybird/pipes/activities_filtered_retention.pipe:27
- Community collections are supported by
segments_filtered_by_collection, but the new collection bucket MVs only materialize slugs wheressoUserId IS NULL. Consequently retention requests for community collections are routed to empty tables. Populate all supported collection types or keep a non-curated fallback here.
{% if defined(collectionSlug) %} activityRelations_collection_bucket_union
services/libs/tinybird/pipes/activityTypes_by_project.pipe:25
- This breaks activity-type discovery for community collections. The collection bucket MVs explicitly exclude collections with a non-null
ssoUserId, while the existing collection-scoping pipe accepts those slugs; this branch therefore returns an empty activity-type list for a previously supported collection type. Include community collections in the buckets or use the existing all-activity union for that scope.
services/libs/tinybird/pipes/activityRelations_collection_bucket_MV_0.pipe:18 - This fan-out captures collection membership only when an
activityRelationsinsert triggers the MV.insights_projects_populated_dsrefreshes membership every 15 minutes, but adding or removing a project from a collection does not replay historical activities through these MVs. Added projects therefore lack all historical activity, and the new precomputed collection aggregate can continue counting stale rows after removals. Rebuild the raw collection buckets when membership changes, or resolve membership in a scheduled/query-time layer that is refreshed independently of activity inserts.
SELECT a.*, arrayJoin(p.collectionsSlugs) AS collectionSlug
FROM activityRelations a
LEFT JOIN insights_projects_populated_ds p ON p.segmentId = a.segmentId
Signed-off-by: Gašper Grom <gasper.grom@gmail.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 75 out of 75 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (5)
services/libs/tinybird/pipes/activityRelations_collection_bucket_MV_0.pipe:18
- The materialized view denormalizes
collectionsSlugsonly when an activity row is inserted.insights_projects_populated_dsis replaced every 15 minutes, but changes to collection membership do not trigger this MV: adding a project omits its historical activity, and removing it leaves old bucket rows indefinitely. Add a scheduled reconciliation/rebuild (or derive collection membership at read time) so membership changes update existing activity rows.
SELECT a.*, arrayJoin(p.collectionsSlugs) AS collectionSlug
FROM activityRelations a
LEFT JOIN insights_projects_populated_ds p ON p.segmentId = a.segmentId
services/libs/tinybird/pipes/collection_insights_aggregate.pipe:40
- Filtering on the global maximum snapshot exposes a partial daily snapshot. Bucket 0 advances
max(snapshotId)at 02:05, while buckets 1-9 are appended through 02:50; during that window—and for an entire day if one job fails—collections in unfinished buckets have no matching contributor row. Select the latest snapshot independently per collection so yesterday's value remains available until that collection's bucket succeeds.
services/libs/tinybird/pipes/contributors_leaderboard.pipe:50 - The scalar denominator removes the window function, but this node still groups and sorts every distinct collection member before applying
LIMIT. Because this PR deletes the collection dependency pipe's precomputed path, default collection bus-factor requests now execute this live aggregation—the removed pipe documents that this exact work caused production timeouts and rate-limit/concurrency alerts for large collections. Preserve an equivalent precomputed route before removing that materialization.
services/libs/tinybird/pipes/collection_insights_aggregate.pipe:33 - A collection with no qualifying activity has no row in the grouped contributor copy, so this left join returns
NULL. The previous live aggregate returned0fromuniq(...), making this a response-contract regression for empty collections. Coalesce the missing count to zero.
services/libs/tinybird/pipes/activityRelations_collection_bucket_clean_enrich_copy_pipe_0.pipe:57 - Bucket 0 and bucket 1 are both scheduled at 01:00, so two full-bucket
COPY_MODE replacejobs run concurrently despite the stated five-minute staggering; bucket 2 then starts at 01:05. Move one job (and reconcile the documented sequence) so every heavy replacement is actually staggered.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 75 out of 75 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (3)
services/libs/tinybird/pipes/collection_insights_aggregate.pipe:38
- This contributor source contains only curated collections: every upstream collection bucket MV filters to
ssoUserId IS NULL. However,insights_projects_populated_dsand the new projects aggregate intentionally include both curated and community collections (insights_projects_populated_copy.pipe:1-16; projects copy lines 25-35). Thus this endpoint returns no contributor row for every community collection, regressing the IN-1195 behavior the previous implementation supported. Add a community-collection contributor path or include those collections in the materialization.
services/libs/tinybird/pipes/activityTypes_by_project.pipe:33 - The community fallback bypasses the mandatory latest-snapshot filter for
activityRelations_deduplicated_cleaned_ds(services/libs/tinybird/lambda-architecture.md:470-489). AlthoughDISTINCThides duplicate rows, types that exist only in an older snapshot can still be returned. Filter this source tomax(snapshotId).
services/libs/tinybird/pipes/collection_insights_aggregate.pipe:43 - Selecting rows whose timestamp equals the daily maximum does not enforce the promised one-row-per-collection contract. All copy pipes use append mode and truncate
snapshotIdto the day, so any same-day retry appends another row with the same key; this subquery then returns every duplicate and the endpoint emits duplicate result rows. Make each run’s version unique and select its value withargMax, or otherwise deduplicate retries before joining.
… routing (CM-XXXX) Activity-bucket routing intentionally scopes to curated collections only (ssoUserId IS NULL) - community collection activities are not used for widgets. The fallback UNION referenced a nonexistent activityRelations_deduplicated_cleaned_ds datasource and was never valid. Signed-off-by: Gašper Grom <gasper.grom@gmail.com>
| FROM collection_insights_aggregate_consolidated_ds | ||
| GROUP BY collectionSlug | ||
| ) | ||
| ) c |
There was a problem hiding this comment.
Snapshot retries can duplicate aggregate rows
Medium Severity
Contributor copy pipes append daily rows with COPY_MODE append and a day-granularity snapshotId, but the serving query only filters to max(snapshotId) and does not collapse duplicates. A same-day retry of a bucket copy creates multiple identical (collectionSlug, snapshotId) rows, so the join can return multiple result rows for one collection.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 3891018. Configure here.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 75 out of 75 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (2)
services/libs/tinybird/pipes/activityRelations_collection_bucket_union.pipe:23
- Routing collection queries to these tables reduces freshness from hourly to daily. The previous project-bucket clean tables are rebuilt hourly (
activityRelations_bucket_clean_enrich_copy_pipe_*.pipe), andbucketing-architecture.md:35-43explains that hourly replacement is needed because activities, bot status, and repository enablement are mutable. All collection activity widgets now read tables rebuilt only at 01:00–01:45 UTC, so new activity and bot/repository changes can remain stale for nearly 24 hours. Preserve equivalent freshness (or add a hot delta layer) unless this product-level latency regression is explicitly accepted.
services/libs/tinybird/pipes/collection_insights_aggregate.pipe:44 - Same-day retries are still not deduplicated here. Every copy uses
COPY_MODE appendwithtoStartOfInterval(now(), INTERVAL 1 day), and the target is a plainMergeTree, so rerunning a bucket appends another row with the same(collectionSlug, snapshotId). This tuple filter returns every such row, causing duplicate endpoint rows (and potentially different counts with no deterministic winner). Use a distinct run/version timestamp and select one latest row per collection, or make the target a versioned replacing table.
Drop the ssoUserId IS NULL restriction from the collection-bucket fan-out MVs and their snapshot/backfill counterparts, so community collection activity is now bucketed alongside curated collections. Public collection listing/search (collections_filtered.pipe) is intentionally left scoped to curated-only - this only widens activity-bucket routing. Signed-off-by: Gašper Grom <gasper.grom@gmail.com>
| FROM activityTypes | ||
| WHERE isCodeContribution = 1 OR isCollaboration = 1 | ||
| ) | ||
| GROUP BY ar.collectionSlug |
There was a problem hiding this comment.
Stale collection membership overcount
High Severity
The per-bucket contributor copy pipes count uniq(memberId) from the collection activity buckets with no check against current collectionsSlugs membership. After a project is removed from a collection, its fan-out rows remain in the MV and cleaned tables, so uniqueContributorCount stays inflated. The previous live query restricted to current member segments, and live widgets still do via segments_filtered_by_collection, so the aggregate widget drifts from them.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit cd52952. Configure here.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 75 out of 75 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (4)
services/libs/tinybird/pipes/collection_insights_aggregate.pipe:44
- The latest-snapshot predicate does not collapse retries. All ten producers use
COPY_MODE appendand write a day-truncatedsnapshotId, while this target is a plainMergeTree; rerunning a bucket on the same day therefore leaves multiple rows with the same(collectionSlug, snapshotId), and this subquery returns all of them, duplicating the endpoint row after the join. Use a distinct run/version timestamp plus a replacing/aggregating target, or otherwise materialize exactly one latest row per collection.
services/libs/tinybird/pipes/collection_insights_aggregate.pipe:33 - A collection with enabled projects but no qualifying contribution/collaboration rows has no entry in the contributor aggregate, so this
LEFT JOINnow returnsNULL. The previous liveuniq(...)aggregate returned0, and the documented response is a contributor count. Coalesce the missing aggregate to zero.
services/libs/tinybird/pipes/activityRelations_collection_bucket_union.pipe:23 - Every table read by this union is rebuilt only once daily (01:00–01:45), whereas the project cleaned buckets used by the previous collection path are rebuilt hourly (
activityRelations_bucket_clean_enrich_copy_pipe_*.pipe). Routing collection widgets here therefore increases activity-data latency from roughly one hour to as much as 24 hours. Preserve the existing freshness SLA—e.g. refresh these buckets hourly with safe staggering or add an incremental current-data layer—before switching all collection queries to this union.
services/libs/tinybird/pipes/collection_insights_aggregate.pipe:34 - Using the projects aggregate as the driving table drops empty collections entirely. The previous aggregate query always returned one row (
projectCount = 0,uniqueContributorCount = 0,avgHealthScore = NULL) even when the collection had no enabled projects; this query returns no rows because the daily projects copy cannot materialize a group for that slug. Drive the response from the requested slug (or the collections table) and left-join both aggregates so the endpoint contract remains one row per valid collection.
…-XXXX)" This reverts commit cd52952. Signed-off-by: Gašper Grom <gasper.grom@gmail.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
There are 3 total unresolved issues (including 2 from previous reviews).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit c1ee75d. Configure here.
| FROM activityTypes | ||
| WHERE isCodeContribution = 1 OR isCollaboration = 1 | ||
| ) | ||
| GROUP BY ar.collectionSlug |
There was a problem hiding this comment.
Aggregate counts disabled project contributors
Medium Severity
The per-bucket contributor copy pipes aggregate uniqueContributorCount from collection bucket rows with no check that the activity's project is still enabled = 1. The previous live collection_insights_aggregate restricted members via segments from enabled projects only, so disabled projects still present in the bucket can inflate the precomputed count served to the UI.
Additional Locations (2)
Reviewed by Cursor Bugbot for commit c1ee75d. Configure here.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 75 out of 75 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (10)
services/libs/tinybird/pipes/collection_insights_aggregate.pipe:38
- The two precomputed inputs cover different populations: this contributor table is populated only from curated-collection buckets, while
collection_insights_aggregate_projects_copy_pipe.pipe:31-33expands every collection slug, including community collections. A community slug therefore returns a project row withuniqueContributorCount = NULL, regressing the live aggregate that previously resolved its member segments. Align both materializations to the same collection population or retain a contributor fallback for community collections.
services/libs/tinybird/pipes/collection_insights_aggregate.pipe:44 - This latest-per-collection filter does not collapse duplicate rows with the same daily
snapshotId. Every bucket copy uses append mode andtoStartOfInterval(now(), INTERVAL 1 day), so retrying a copy on the same day appends another identical key; both rows satisfy this tuple predicate and the endpoint returns duplicate aggregate rows. Use a unique run/version timestamp and select one latest row per collection, or make the target/query deterministically deduplicate retries.
services/libs/tinybird/pipes/activityRelations_collection_bucket_clean_enrich_copy_pipe_2.pipe:8 - The documented run time is stale: this bucket is scheduled at 01:10 on line 57, not
:05. Update the description so operators do not use the wrong schedule when diagnosing copy jobs.
services/libs/tinybird/pipes/activityRelations_collection_bucket_clean_enrich_copy_pipe_3.pipe:8 - The documented run time is stale: this bucket is scheduled at 01:15 on line 57, not
:10.
services/libs/tinybird/pipes/activityRelations_collection_bucket_clean_enrich_copy_pipe_4.pipe:8 - The documented run time is stale: this bucket is scheduled at 01:20 on line 57, not
:15.
services/libs/tinybird/pipes/activityRelations_collection_bucket_clean_enrich_copy_pipe_5.pipe:8 - The documented run time is stale: this bucket is scheduled at 01:25 on line 57, not
:20.
services/libs/tinybird/pipes/activityRelations_collection_bucket_clean_enrich_copy_pipe_6.pipe:8 - The documented run time is stale: this bucket is scheduled at 01:30 on line 57, not
:25.
services/libs/tinybird/pipes/activityRelations_collection_bucket_clean_enrich_copy_pipe_7.pipe:8 - The documented run time is stale: this bucket is scheduled at 01:35 on line 57, not
:30.
services/libs/tinybird/pipes/activityRelations_collection_bucket_clean_enrich_copy_pipe_8.pipe:8 - The documented run time is stale: this bucket is scheduled at 01:40 on line 57, not
:35.
services/libs/tinybird/pipes/activityRelations_collection_bucket_clean_enrich_copy_pipe_9.pipe:8 - The documented run time is stale: this bucket is scheduled at 01:45 on line 57, not
:40.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 75 out of 75 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (3)
services/libs/tinybird/pipes/collection_insights_aggregate.pipe:38
- This still joins different collection populations.
collection_insights_aggregate_projects_copy_pipeexpands every slug ininsights_projects_populated_ds, whose producer explicitly includes curated and community collections (insights_projects_populated_copy.pipe:1-16), while all contributor bucket MVs requiressoUserId IS NULL(for exampleactivityRelations_collection_bucket_MV_0.pipe:10-20). Community collections therefore have a projects row but can never have a contributor row, regressing the aggregate that IN-1195 explicitly restored. The contributor materialization needs to cover the same population, or this endpoint needs a correct community path.
services/libs/tinybird/pipes/collection_insights_aggregate.pipe:44 - Same-day retries are not collapsed here. Every aggregate job appends with
snapshotId = toStartOfInterval(now(), INTERVAL 1 day), and the target is a plain MergeTree, so rerunning a bucket appends another row with the same(collectionSlug, snapshotId). This tuple predicate selects every duplicate latest row and the LEFT JOIN then returns duplicate endpoint rows. Add a distinct run/version column with replacing semantics, or aggregate to exactly one deterministic latest row per collection.
services/libs/tinybird/pipes/collection_insights_aggregate.pipe:33 - Because this is a LEFT JOIN, a collection with no qualifying contributor aggregate (for example, no code/collaboration activity or an expired/failed snapshot) returns
uniqueContributorCount = NULL; the previous liveuniq(...)aggregate returned0, and the response documents this field as a count. Coalesce the missing aggregate to zero to preserve the endpoint contract.


Summary
collectionSlug, mirroring the existing project-side bucketing) so collection queries no longer scan the full unbucketed activity relations table: 10activityRelations_collection_bucket_MV_Nstreaming MVs, 10 raw bucket datasources, 10 daily COPY_MODE clean/enrich pipes, and aactivityRelations_collection_bucket_union.pipe(10-way UNION ALL) query layer, sincecollectionSlugisn't known at Jinja-template time and a project can belong to multiple collections.activities_filtered.pipe,activities_filtered_retention.pipe, andactivityTypes_by_project.pipeat the new collection bucket union whencollectionSlugis scoped.contributors_leaderboard.pipeandcontributor_dependency.pipeto supportcollectionSlugscope directly, using a scalar-total pattern (a separate cheap unfiltered aggregate cross-joined into the grouped/limited result) instead of a window function over the full grouped result — this reaches the same performance as the dedicated collection-specific pipes without needing them.collection_contributors_leaderboard.pipeandcollection_contributor_dependency.pipe(+ its copy pipe/datasource), superseded by the above.Deploy status
Already deployed to Tinybird production and validated:
orm-persistence-frameworks) returned correct counts: 170 activities / 49 unique members / 39 unique projectsNote: staging was skipped for the initial bucket infra deploy — it was at its 100-datasource capacity limit — and went straight to production after validation looked solid.
Cross-repo dependency
insights PR #2045 updates the Insights app to call
contributors_leaderboard/contributor_dependencydirectly withcollectionSluginstead of the old collection-specific pipes. That PR has already been merged and deployed to production, which is what unblocked deleting the old pipes in this PR.Test plan