Architecture reference · analytics warehouse · ~9 minute read · last updated 2026-05-17 · Phase 2C live

How Admaxxer migrated to self-hosted ClickHouse — Phase 2D ships attribution_breakdown

Admaxxer is moving its analytics warehouse off Tinybird onto a self-hosted ClickHouse cluster running on a dedicated Hetzner box in Hillsboro, OR. This page is for merchants who notice their dashboard numbers, security reviewers, and anyone curious about how a phased warehouse migration with zero customer-visible downtime works in practice. The short version: Tinybird stays canonical until your workspace’s numbers match on both sides for at least 14 days; only then does our admin team flip your workspace onto the ClickHouse path. You see no change either way — this is the polished version of “we’re upgrading the engine without shaking the cup.”

Phase 2D update (2026-05-17) — attribution_breakdown migrated

Sources & Attribution now reads from ClickHouse for flagged workspaces. The attribution_breakdown pipe — the one powering the Channel Attribution Drilldown on /marketing-acquisition — is the second pipe to land a CH-native translation after summary_kpis. Five things landed today:

How to verify your numbers: open /marketing-acquisition and look at the data-source badge on the Channel Attribution table after your workspace flips. A sky-blue ClickHouse chip means the row was sourced from CH; absence means Tinybird. Per-row per-column numbers should agree to within ±1% — if they don’t, file a support ticket and we’ll flip your workspace back to Tinybird in under a second. The operator-facing flag flip lives at /documentation/admin/clickhouse-runbook.

Why Admaxxer’s Sources & Attribution beats Triple Whale + Datafast

Self-hosted ClickHouse is table stakes for serious DTC analytics — both Triple Whale and Datafast run ClickHouse under the hood and we ship on the same architecture for the same reasons. The differentiators are upstream of the warehouse: pricing, attribution-model surface, and transparency. Phase 2D’s attribution_breakdown cutover makes all three concrete on the Sources & Attribution screen.

Axis Admaxxer (this ship) Triple Whale Datafast
Warehouse hosting + pricing Self-hosted ClickHouse on a dedicated $40/mo box. Flat marginal cost — no per-event upcharge as you scale. Managed warehouse with event-volume billing — costs scale with traffic, not just usage. Co-located ClickHouse, similar architecture; pricing tied to billable analytics events per their docs.
Attribution models exposed Seven models on a single dropdown: last_click, first_click, linear_all, linear_paid, time_decay (7d half-life), position_based (40/20/40), and markov chains. Four data lenses: pixel, Shopify journeys, platform-reported, and a side-by-side all-lenses view. Sonar supports “switch between attribution models on the fly” for a curated set per triplewhale.readme.io. Single-touch attribution with optional view-through per datafa.st/docs; multi-model selector positioning is narrower.
Pipe + datasource transparency Every pipe, every datasource, every materialized-view CREATE TABLE is visible inside merchant-readable docs and the GitHub repo. The exact SQL behind a tile is one click away. Internals are proprietary; merchant-facing docs cover surfaces, not the warehouse layer. Documentation focuses on dashboard surface and pixel install; underlying warehouse pipes not exposed.
Migration discipline Per-workspace dual-write parity gate, customer-facing data-source badge during burn-in, three-tier rollback (flag flip <1s / pipe kill-switch <5s / container reset <3min), parity script in the repo for every migrated pipe. Migrations happen server-side without merchant-visible signaling. Migrations happen server-side without merchant-visible signaling.

Citations (fair-use, ≤15 words per quote): triplewhale.readme.io describes Sonar as a unified attribution layer with on-the-fly model switching. datafa.st/docs covers analytics-event billing and single-touch attribution with optional view-through. Differentiator rows are fact-of-product, not editorial.

Phase 2C update (2026-05-17) — VITATREE USA is live

Four things landed today:

The operations runbook for this cutover lives at /documentation/admin/clickhouse-runbook (admin-only access). It documents how to check parity status for a workspace, flip a workspace flag on, roll back a workspace, trigger a backfill for a specific date window, read the CH performance dashboard and system.query_log, and the escalation matrix.

Wave 3 update (2026-05-17) — source_medium_breakdown + p_summary_series

Two more pipes shipped today, in lockstep:

What each pipe powers, in customer-facing terms: source_medium_breakdown is the table you scan to answer “which channel paid off yesterday?”; p_summary_series is the inline chart you glance at to answer “is this metric trending up, flat, or down?”. Both are read-path-only changes — the API shapes returned by /api/v1/analytics/source-medium and /api/v1/analytics/summary-series are byte-identical before and after cutover; every field name, every type, every nullable shape stays the same. The migration is invisible at the consumer level until your workspace is explicitly opted in by an admin.

Admin runbook for these two new flags (admin-only access): /documentation/admin/clickhouse-runbook. The runbook documents how to read parity status for the new pipes, flip a workspace flag per pipe, roll back per pipe, and the sparkline-shape verification step added for p_summary_series.

Why we’re migrating

Three reasons, in order of weight:

  1. Per-query billing was capping us at our growth point. Tinybird’s Develop tier ($40/mo) was sufficient through the early product but our pipe-count × workspace-count is now bumping the vCPU ceiling daily. The next Tinybird tier is $249/mo+; a dedicated Hetzner CCX23 ($40/mo, 4 dedicated AMD cores, 16 GB RAM) gives us 8× the headroom for the same spend, no per-query upcharge, and no scaling surprises as we ship features.
  2. Cross-region latency was a real cost. Tinybird runs in AWS us-east-1. Admaxxer’s app servers run in Hetzner Hillsboro, OR. Every dashboard query paid ~30–50 ms of network round-trip before the query even started. Co-locating ClickHouse with the app server cuts that to 0.8–1.6 ms over a private LAN — on a dashboard with 12 tiles each firing its own query, that’s 400+ ms shaved per page load. You feel this on the dashboard render.
  3. We already self-host everything else. Admaxxer’s Postgres (OLTP), Redis (queues + cache), and pixel ingest all migrated off managed cloud onto the same Hetzner box earlier this month (May 11–12, 2026). ClickHouse is the last managed dependency. Cancelling the Tinybird subscription cleanly closes the per-query-billing chapter and makes our cost model predictable at every customer-count target.

The Triple Whale and Datafast playbooks both self-host their warehouse layer at scale — the patterns are well-trodden and the cost curve at our growth point clearly favours owning the box.

What changes for you — right now: nothing

This is the most important section. Every Admaxxer dashboard you load today still reads its numbers from Tinybird. The migration is happening behind a feature flag that defaults OFF for every workspace. We only flip your flag on after our admin team has verified that the ClickHouse path returns numbers that match Tinybird’s within ≤1% on a per-column basis for at least 14 days running.

When your workspace flips:

If anything looks off the day your flag flips, mention it to support — we can flip it back to Tinybird instantly while we investigate. The rollback is one column update on our admin DB; no deploy needed.

How parity verification works

Every numeric column emitted by summary_kpis (the pipe that powers your dashboard’s 80+ KPIs — MER, ROAS, AOV, NC-ROAS, NCPA, units sold, etc.) is computed on BOTH Tinybird and ClickHouse for your workspace over identical date windows. A nightly canary script (scripts/clickhouse-verify-summary-kpis-parity.ts) runs the comparison and emits a per-column drift report:

The burn-in requirement is 14 consecutive days of ok across every column before flag-on. This is intentionally conservative — the polled-fallback path that fills install-day workspaces' tiles (GL#313 + GL#319 + GL#327 in our key-lessons archive) takes several days to accumulate enough data on both sides to compare meaningfully, so we want a window that covers it.

Per-workspace cutover schedule

Cutover happens workspace-by-workspace, not all at once. The order is:

  1. Cohort 1: USD DTC workspaces with full data history (≥ 90 days). The most stable parity profile — both sides have months of overlapping data to compare. Flag-on candidates start landing here ~14 days after the dual-write started for your workspace.
  2. Cohort 2: USD DTC workspaces with install-day or early-stage data. Parity hinges on the polled-fallback fold; we want extra runway to confirm the ClickHouse translation preserves it.
  3. Cohort 3: Non-USD workspaces. Blocked on the FX-conversion pipe migration. Once that ships, parity check unlocks and the same 14-day burn-in starts.
  4. Cohort 4: SaaS and Lead-Gen workspaces. These read different pipes (p_saas_summary, etc.) — their CH equivalents land in a separate phase.

You can’t opt your own workspace in or out — the flag is admin-controlled. We’re intentionally not exposing this as a customer-facing toggle because the parity gate is the contract; flipping manually would defeat the gate.

How to verify your numbers (any day, any backend)

Two pieces of advice that apply regardless of which warehouse backs your dashboard:

1. Compare to Shopify Admin’s native reports

Shopify Admin → Analytics → Reports has authoritative revenue and orders numbers straight from Shopify’s order edge. Pick a 30-day window in both Admaxxer and Shopify Admin — pixel-attributed revenue on Admaxxer should land within ~5% of Shopify’s gross sales (the gap is real attribution loss: ad-blockers, Safari ITP, CSP-filtered events). For polled metrics (orders, NC orders, units sold), the numbers should be within ~1% — we read those directly from Shopify Admin via the daily-poll fallback, so they’re effectively the same source.

2. Compare to Meta Ads Manager and Google Ads

Pull spend / impressions / clicks for the same date range from each platform’s native reporting. Admaxxer’s spend numbers should match to the cent — we read directly from the Marketing API daily and store the response unchanged. Purchase / conversion counts will differ slightly because each platform has its own attribution model (default 7-day click + 1-day view for Meta, etc.); the spend is the ground-truth comparison.

If you see discrepancies that don’t match these ranges, file a support ticket with the specific tile + date window. We’ll either explain the gap (often it’s currency or attribution-window mismatch, both fixable) or root-cause it as a real bug. Either way, the warehouse swap doesn’t change this calculus — the parity gate ensures our numbers stay correct across the cutover.

Security and data isolation in the new setup

Same model as before. The ClickHouse cluster:

For full architecture details see Tinybird auth model (the patterns generalize) and our public infrastructure reference in the codebase.

Where to learn more

Related: Admin operations runbook · Tinybird auth model · Marketing acquisition deep dive · Dashboard analytics card reference · Multi-currency plumbing · Documentation home

If you have questions about your workspace’s migration status or want to know when your flag is scheduled to flip, reach out to support@admaxxer.com.