perf(rpc): batch goals bulkAnalytics ClickHouse queries for unfiltered goals - #680
perf(rpc): batch goals bulkAnalytics ClickHouse queries for unfiltered goals#680FindMalek wants to merge 2 commits into
Conversation
…d goals bulkAnalytics fired 2 ClickHouse queries per goal (completions + denominator) inside a Promise.all, so a website with 20 goals issued ~40 concurrent round-trips on every dashboard load. Goals with no filters at all (request-level or goal-level) now get grouped by their effective start date and counted in one batched query per date bucket via processGoalsConversionCountsBatch, plus one shared getTotalWebsiteUsers call, instead of 2 queries per goal. Any goal with a filter keeps the original one-query-per-goal path unchanged: buildIdentifiedEventStream only threads filter conditions through the step at array index 0 (correct for its funnel-entry-filter use case), so batching filtered goals together would silently apply one goal's filter to another's count. Scoping the batch to the zero-filter case keeps that shared query builder untouched. Fixes databuddy-analytics#679
|
@izadoesdev this is ready for review whenever you have a chance, CI should be green aside from the Vercel preview checks, which need a team member to authorize the deploy (outside my permissions as an external contributor) |
|
@FindMalek is attempting to deploy a commit to the Databuddy OSS Team on Vercel. A member of the Team first needs to authorize it. |
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Greptile SummaryThe PR batches filter-free goal conversion queries by effective start date while retaining the original individual path for filtered goals and adding per-goal fallback after batch failures.
Confidence Score: 5/5The PR appears safe to merge because no blocking failure remains. The 255-goal chunking keeps generated step identifiers within the UInt8 range, and failed batches now retry each goal independently without dropping healthy sibling results. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[bulkAnalytics request] --> B{Request or goal filters?}
B -->|Yes| C[Run goal individually]
B -->|No| D[Group by effective start date]
D --> E[Split into chunks of at most 255]
E --> F[Shared users query and batched completion query]
F -->|Success| G[Map per-step counts back to goals]
F -->|Failure| H[Fall back to isolated per-goal queries]
C --> I[Return analytics by goal ID]
G --> I
H --> I
Reviews (2): Last reviewed commit: "fix(rpc): cap goal batch size and isolat..." | Re-trigger Greptile |
Fixes two issues Greptile flagged on the batching PR: processGoalsConversionCountsBatch encoded each goal as a ClickHouse UInt8 step number, which wraps past 255 and silently merges unrelated goals' completion counts on unlimited-plan websites with large goal counts. Grouping and chunking is now capped at 255 goals per query and extracted into groupGoalsForBulkAnalytics, a pure function with its own tests, instead of inline router logic. A failed batched query previously marked every goal in that date bucket as failed. It now falls back to the original per-goal queries for just that bucket, so one bad query no longer takes down otherwise healthy sibling goals.
|
Fixed both — capped batches at 255 goals (extracted the grouping into a tested pure function), and combined-query failures now fall back to per-goal queries for that bucket instead of failing every goal in it. |
|
@greptile review |
Closes #679
Problem
goalsRouter.bulkAnalyticsfires 2 ClickHouse queries per goal (a completion count + agetTotalWebsiteUsersdenominator) inside aPromise.all. A website with 20 goals loading the goals dashboard issues ~40 concurrent ClickHouse round-trips on every request.Fix
Goals with no filters at all (neither request-level dashboard filters nor a goal-specific filter) get grouped by their effective start date and counted together:
getTotalWebsiteUserscall per date bucket (previously one per goal)processGoalsConversionCountsBatchquery per date bucket that matches every batched goal's step condition and returns per-goal completion counts viaGROUP BY(previously oneprocessGoalConversionCountquery per goal)Any goal carrying a filter keeps the exact original one-query-per-goal path, unchanged.
Why the scope stops at "no filters"
I traced
buildIdentifiedEventStream(the shared query builder also used by the funnels feature) and found it only threads filter conditions into the match condition for the step at array index 0 — correct for its actual use case (a funnel's entry filter gating step 1), but if I'd batched filtered goals together as "steps," any goal past index 0 would silently have its filter dropped, corrupting its completion count.With an empty filter list that branch never fires, so every step's match condition is identical in shape regardless of array position — which is what makes batching provably safe in that case. I didn't modify
buildIdentifiedEventStreamitself, since it's shared with funnels and a subtle bug there would have a much larger blast radius than this fix is worth.Testing
packages/rpc/src/lib/analytics-utils-goals-batch.test.ts: asserts the batched query issues exactly one ClickHouse call for N goals (instead of N), correctly maps results back by position, handles zero goals, and asserts the generated SQL never carries per-step filter gating.packages/rpc/package.json's explicit test file list.packages/rpcsuite: 182 pass, 0 fail, 14 skip.tsc --noEmitandbiome checkclean on all touched files.turbo run check-typesandturbo run test(triggered by the pre-push hook) both pass.Summary by cubic
Closes #679 by batching ClickHouse queries for unfiltered goals in
goalsRouter.bulkAnalytics. Previously each goal fired 2 queries (a completion count and the users denominator), so 20 goals meant ~40 round-trips per dashboard load. Goals with no filters now get grouped by effective start date, chunked by 255 goals per query, and counted in one query per chunk with a shared denominator call; a failed batched query falls back to the original per-goal path for just that chunk. Goals with filters keep the original per-goal behavior unchanged.Behavior notes
UInt8in ClickHouse and wrap past that limit; grouping and chunking live in the puregroupGoalsForBulkAnalyticshelper.buildIdentifiedEventStreamonly threads filters into the first step; batching filtered goals would silently corrupt their counts, so that shared query builder is left untouched.Written for commit dcef465. Summary will update on new commits.