Skip to content

[codex] Fix ClickHouse activity dedupe build - #1218

Merged
Asherlc merged 3 commits into
mainfrom
Asherlc/fix-activities-empty-state-v1
Jun 2, 2026
Merged

Asherlc merged 3 commits into
mainfrom
Asherlc/fix-activities-empty-state-v1

Conversation

@Asherlc

@Asherlc Asherlc commented Jun 2, 2026

Copy link
Copy Markdown
Owner

Replaces the failing recursive ClickHouse activity dedupe graph with domain read models for source records, duplicate matches, duplicate groups, and deduped activities.
This fixes the production activities empty state caused by analytics.deduped_activities staying empty after ClickHouse hit MEMORY_LIMIT_EXCEEDED in the old recursive model.
Production has already been deployed to sha-142216b; the first analytics-worker dbt build completed successfully with 15/15 models in 28.24s and ClickHouse now reports 73 recent deduped activities.
Validated with focused read-model tests, analytics policy lint, root/server/web typechecks, production health check, and production ClickHouse row-count checks.


Summary by cubic

Rebuilt ClickHouse activity dedupe as incremental read models so builds are memory-safe and analytics.deduped_activities is repopulated. Adds deterministic matching and fixes a nullable sort key that blocked clean first builds and CI.

  • Bug Fixes

    • Replaced recursive dedupe with activity_source_recordsactivity_duplicate_matchesactivity_duplicate_groupsdeduped_activities (all ReplacingMergeTree(refresh_version)), eliminating ClickHouse MEMORY_LIMIT_EXCEEDED; dbt build succeeds and recent activities now show in production.
    • Uses a consistent null-ended 12-hour window for duplicate matching and merged bounds.
    • Ensured non-null sort key in deduped_activities by emitting assumeNotNull(user_id), fixing the ClickHouse “allow_nullable_key” error in clean e2e builds.
    • Updated tests to target the new models and assert the non-null user_id sort key; added the new models to DBT_SAFE_MODELS in entrypoint.sh.
  • Refactors

    • Deleted analytics/macros/bounded_activity_graph.sql and rewired deduped_activities to use the new read models.
    • Groups duplicates via a two-hop link walk (no recursive CTEs) with deterministic settings (max_threads: 1, join_use_nulls: 1) and deterministic device-priority tie-breakers (pattern length DESC, priority ASC, pattern ASC).
    • Expanded the production incident doc with June 1–2 root-cause, CI sort-key update, and mitigation details.

Written for commit ac5ee70. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Resolved issue where activity deduplication could fail due to memory constraints, causing empty activity views.
  • Refactor

    • Restructured activity deduplication pipeline to improve reliability and performance through clearer separation of processing stages.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @Asherlc, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@coderabbitai

coderabbitai Bot commented Jun 2, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR refactors the activity deduplication pipeline from a recursive-macro approach (bounded_activity_graph) to a set of incremental dbt models. It introduces three new stage models—activity_source_records, activity_duplicate_matches, and activity_duplicate_groups—that progressively compute source priorities, identify overlapping activity pairs, cluster duplicates into groups, and feed into a rewritten deduped_activities model. All new models use incremental append materialization with ReplacingMergeTree and soft-delete union-all patterns. This replaces the previous memory-intensive recursive CTE that caused a production incident.

Changes

Activity Deduplication Pipeline Refactoring

Layer / File(s) Summary
Activity source records and prioritization
analytics/models/read_models/activity_source_records.sql
New incremental model selecting activities with computed device and provider priority matching. Emits current rows with computed priority and stale rows (deleted, priority nulled) via union-all soft-delete pattern, stamped with refresh_version and refreshed_at.
Duplicate activity pair detection
analytics/models/read_models/activity_duplicate_matches.sql
New incremental model identifying overlapping activity pairs per user. Self-joins normalized ended_at values, computes overlap_ratio, filters to pairs > 0.8, and emits current matches with overlap_ratio and stale matches with overlap_ratio nulled using soft-delete union pattern.
Duplicate activity clustering and group assignment
analytics/models/read_models/activity_duplicate_groups.sql
New incremental model expanding connectivity between duplicate activities via fixed-depth UNION ALL path traversal. Assigns deterministic group_id as min(toString(connected_activity_id)) per cluster and emits current groups and stale groups (group_id nulled) via soft-delete pattern.
Deduped activities rewrite using new stage models
analytics/models/read_models/deduped_activities.sql
Rewrites deduping logic from recursive approach to joining activity_source_records with activity_duplicate_groups. Selects canonical activity per group by priority via row_number(), aggregates fields (min/max timestamps, argMinIf for name/notes, grouped arrays for external IDs and member activities), and simplifies incremental stale detection via anti-join on activity_id. Changes refresh_version stamping for stale rows from decrement to current value.
Test suite and configuration updates
analytics/models/read_models/read_model_microbatch.sql.test.ts, entrypoint.sh
Updates DBT_SAFE_MODELS allowlist in tests and entrypoint to include new stage models (activity_source_records, activity_duplicate_matches, activity_duplicate_groups) while removing activity_summary_rows. Replaces bounded-graph assertions with tests validating new stage materialization, soft-delete union patterns, consistent null-ended window semantics via coalesce(..., + INTERVAL 12 HOUR), and absence of recursive/visited/reachability logic.
Production incident documentation
docs/production-incident-baseline.md
Documents 2026-06-01 activities empty-state incident caused by MEMORY_LIMIT_EXCEEDED during recursive CTE execution, manual ClickHouse mitigation, and planned fix via domain-specific read models to replace bounded_activity_graph macro.

Sequence Diagram

sequenceDiagram
  participant postgres_fitness.activity as Activity Source
  participant activity_source_records as Source Records
  participant activity_duplicate_matches as Duplicate Matches
  participant activity_duplicate_groups as Duplicate Groups
  participant deduped_activities as Deduped Activities
  Activity Source->>Source Records: filter active, compute priority
  Source Records->>Duplicate Matches: normalize ended_at, identify overlaps
  Duplicate Matches->>Duplicate Groups: expand connectivity, assign group_id
  Duplicate Groups->>Deduped Activities: join with source records
  Deduped Activities->>Deduped Activities: select canonical by priority, aggregate fields
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • Asherlc/dofek#1217: Also modifies incremental stale-row logic in deduped_activities.sql, including refresh_version handling and tombstone emission patterns.
  • Asherlc/dofek#1190: Directly inverse: adds the bounded_activity_graph macro that this PR removes, and updates microbatch models to use it.
  • Asherlc/dofek#1211: Related activity deduplication pipeline work, replacing bounded_activity_graph usage with alternative dedupe table structure (deduped_activities/deduped_activity_members → activity_summary_rows).

Suggested labels

type/bug, area/analytics, component/dbt-models

Suggested reviewers

  • cubic-dev-ai
🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title uses imperative mood ('Fix'), stays under 70 characters (44 chars), includes a relevant area prefix ('[codex]'), contains no trailing punctuation, and clearly describes the main change: replacing the failing recursive ClickHouse activity-deduplication approach with incremental domain read models.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

github-actions Bot commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

Storybook previews for 573374b0 are ready:

This comment updates automatically on each PR push.

@Asherlc
Asherlc marked this pull request as ready for review June 2, 2026 01:56
Copilot AI review requested due to automatic review settings June 2, 2026 01:56
@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@surmado-code-review

surmado-code-review Bot commented Jun 2, 2026

Copy link
Copy Markdown

Automated Checks (advisory, non-blocking)

✅ All checks passed.


Surmado Code Review — Free tier limit reached

You've used all 10 free reviews this month. Deterministic checks (secrets, model strings) still ran above.

Upgrade to the Paid plan for 100 reviews/month + $15 per additional 100: https://app.surmado.com/checkout?plan=pr_review_starter

Or wait until your next monthly window for 10 more free reviews.


Surmado Code Review (v1.2-mt)

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-actions

github-actions Bot commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

Review app deployment was skipped for PR #1218.

Hetzner could not allocate the configured review app server type in the configured location. This is provider capacity/placement availability, not a code failure in this PR.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@analytics/models/read_models/activity_duplicate_groups.sql`:
- Around line 33-75: The current
duplicate_walk_rows/duplicate_walk/current_duplicate_groups logic only explores
up to two hops and must be replaced with a transitive-closure approach: stop
using the fixed first_link/second_link joins and instead iteratively expand
connected pairs from duplicate_links and source_records until no new
(activity_id, connected_activity_id) pairs are produced (or use a recursive CTE
pattern supported by our SQL engine), then compute group_id as the min
stringified connected_activity_id per activity from that converged closure;
reference duplicate_links, source_records,
duplicate_walk_rows/duplicate_walk/current_duplicate_groups when locating the
affected code and ensure the iteration converges (idempotent) so all chain
lengths like A-B-C-D collapse to one group.

In `@analytics/models/read_models/activity_source_records.sql`:
- Around line 38-40: The row_number() selection for device-priority is
non-deterministic when two active_device_priority rows have equal
source_name_pattern length; update the ORDER BY inside row_number() OVER
(PARTITION BY active_activity.id ORDER BY
length(active_device_priority.source_name_pattern) DESC, ...) to add a total
tie-breaker such as active_device_priority.id (or another unique/stable column)
so ties are resolved deterministically and the derived priority/canonical
activity in deduped_activities.sql remains stable.

In `@analytics/models/read_models/read_model_microbatch.sql.test.ts`:
- Around line 96-98: Remove the negative assertions that check for the deleted
dedupe implementation in the test (the expect(...).not.toContain checks for "{{
activity_dedup_graph() }}", "connected_components AS", and
"visited_activity_ids") from
analytics/models/read_models/read_model_microbatch.sql.test.ts (also remove the
duplicate checks referenced at the other ranges); instead keep and/or strengthen
the positive assertions that validate the new staged models and expected SQL
fragments for the current implementation (look for test helpers or existing
positive expect(sql).toContain(...) checks that assert the new staged model
behavior and leave those intact).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 775519ed-d0bb-4c25-a546-9ab83a1bcd04

📥 Commits

Reviewing files that changed from the base of the PR and between 8bacbef and 142216b.

📒 Files selected for processing (8)
  • analytics/macros/bounded_activity_graph.sql
  • analytics/models/read_models/activity_duplicate_groups.sql
  • analytics/models/read_models/activity_duplicate_matches.sql
  • analytics/models/read_models/activity_source_records.sql
  • analytics/models/read_models/deduped_activities.sql
  • analytics/models/read_models/read_model_microbatch.sql.test.ts
  • docs/production-incident-baseline.md
  • entrypoint.sh
💤 Files with no reviewable changes (1)
  • analytics/macros/bounded_activity_graph.sql

Comment thread analytics/models/read_models/activity_duplicate_groups.sql
Comment thread analytics/models/read_models/activity_source_records.sql Outdated
Comment thread analytics/models/read_models/read_model_microbatch.sql.test.ts Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0 issues found across 3 files (changes from recent commits).

Requires human review: This PR significantly restructures the core activity deduplication logic, replacing a recursive CTE with multiple incremental read models, which is a high-risk change to production data pipelines that requires human review to ensure correctness and data integrity.

Re-trigger cubic

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3 issues found across 8 files

Confidence score: 3/5

  • There is concrete data-quality risk in analytics/models/read_models/activity_duplicate_groups.sql: limiting duplicate grouping to 2 hops can split a single connected duplicate chain into multiple groups, which may produce multiple canonicals for one real activity.
  • analytics/models/read_models/deduped_activities.sql has a likely regression risk because stale-match filtering only uses activity_id while dedupe identity is (user_id, activity_id), so outdated rows can persist when user_id changes.
  • This lands at moderate merge risk (not an immediate blocker) because the issues are medium-high severity with high confidence and affect correctness/determinism in read models rather than just style or housekeeping.
  • Pay close attention to analytics/models/read_models/activity_duplicate_groups.sql, analytics/models/read_models/deduped_activities.sql, and analytics/models/read_models/activity_source_records.sql - fix connected-component grouping depth, key matching on both dedupe columns, and deterministic tie-breaking in ranking.

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread analytics/models/read_models/activity_duplicate_groups.sql
Comment thread analytics/models/read_models/deduped_activities.sql
Comment thread analytics/models/read_models/activity_source_records.sql Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0 issues found across 3 files (changes from recent commits).

Requires human review: This PR fundamentally rewrites the activity deduplication pipeline, replacing a recursive CTE with four new incremental models, which is a high-impact change that touches core business logic and data infrastructure, and a CI issue was discovered after production deployment, so human review is...

Re-trigger cubic

@Asherlc
Asherlc merged commit bb7bc36 into main Jun 2, 2026
77 checks passed
@Asherlc
Asherlc deleted the Asherlc/fix-activities-empty-state-v1 branch June 2, 2026 02:24
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.

2 participants