feat(identity): write profile_aliases on sign-in (anon → identified) - #322
Merged
Conversation
Start populating the (unused) profile_aliases table so anonymous events can later be merged onto the identified profile at query time — the Mixpanel model: never rewrite event rows, keep a small mapping and resolve on read. - add upsertAlias() writer in profile.service.ts (direct insert; guards drop empty ids and self-maps so we never create a chain) - re-enable the `alias` track op in track.controller.ts to call it (was stubbed to "Alias is not supported") Write path only. Nothing reads profile_aliases yet — query-time resolution (funnels), historical backfill, and chart/retention resolution are each a separate follow-up PR. The companion mixpanel-proxy change (emit the alias on sign-in) is what activates this; the code is inert until that ships.
This was referenced Jun 17, 2026
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.
What
Start populating the (currently unused)
profile_aliasesClickHouse table with(anonymous id) → (identified profile id)mappings when a user signs in.This is the write path only of identity merge. Nothing reads the table yet — we ship collection first so it fills with real data to validate the read side against.
Why
Anonymous events and post-sign-in events land on different
profile_ids (the Mixpanel/anon id vs the identifiedbackend_userid). Because funnels group byprofile_id, one user looks like two people, collapsing first-open → conversion funnels.Upstream OpenPanel hasn't solved this (issues #77 "Stitching", #270, #173) — their blocker is rewriting event rows. We sidestep it the way Mixpanel does: keep a small
profile_aliasesmapping and resolve at query time (a later PR) — zero event rows rewritten.Changes
packages/db/src/services/profile.service.ts— newupsertAlias({ projectId, profileId, alias }). Direct insert (one row per sign-in; no buffer needed). Guards drop empty ids and self-maps (alias === profileId) so we never create a chain.apps/api/src/controllers/track.controller.ts— re-enable thealiastrack op (was a 400 "Alias is not supported" stub) to callupsertAlias.Activation (required companion — separate repo)
The code is inert until the
openpanel/mixpanel-proxychange emits the alias op on sign-in. PR: Dashverse/openpanel#80. Ship together.Not in this PR (separate follow-ups)
conversion.service+funnel.service), gated by a project allowlist, deduped withargMax(profile_id, created_at).Notes
profile_aliasesis plainMergeTree, so the proxy emits once per sign-in (deduped within batch), not per event. Occasional dupes are tolerated — the read PR resolves withargMax(profile_id, created_at).chart/import/notification/reportsare unrelated (in-progress distribution-chart work); the two files changed here typecheck clean.