CRUMB a card from devarno-cloud

Click Tracking Pipeline

smo1 intermediate 7 min read

ELI5

When someone clicks a short link they are sent on immediately; writing down the click happens afterwards in the background. The API first bumps the link’s counters in the main database (that is what the score reads), then stores the detailed click row. That row goes to ClickHouse if purr-api could reach ClickHouse when it started, otherwise to a table in the main database.

Technical Deep Dive

Source: zoomies-edge src/analytics/tracker.ts:16-77, src/handlers/redirect.ts:163-171, src/index.ts:201-231 (origin/main 5bdde59); purr-api cmd/server/main.go:116-122, 162-172, internal/handler/redirect_handler.go:51-93, 322-385, internal/service/analytics_service.go:70-160, 341-346, internal/service/click_store_postgres.go:28-40, internal/service/click_store_clickhouse.go:42-45, internal/repository/link_repository.go:148, migrations/000008_link_clicks.up.sql (bb6cfc2); smo1-io docs/decisions/2026-10-09-launch-to-revenue-scope.md:40, 57, 123, read 2026-10-25.

Pipeline

sequenceDiagram
autonumber
actor U as User
participant Z as zoomies-edge
participant API as purr-api
participant PG as Postgres
participant CS as ClickStore
U->>Z: GET smo1.io/slug
Z-->>U: 302 Location
Z-)API: POST /api/analytics/track with slug, ip, ua, referer, country, city, visitor_id
API->>PG: GetBySlugUnfiltered slug, uid or custom_slug
API->>API: parse UA, referer, visitor hash, is_bot
API->>PG: IncrementClicks on links
API->>PG: RecordVisitor into link_visitors
API->>PG: IncrementTotalClicks on users
API->>CS: InsertClick, ClickHouse or Postgres link_clicks
API-->>Z: 204 always

Why async

The redirect must not wait on analytics; the code comment states a p99 target of 100 ms (redirect.ts:165). The tracking promise is registered with waitUntil so the Workers runtime does not cancel it after the response.

Counters first, store second

TrackClick increments links.total_clicks and last_clicked_at, records the visitor and increments users.total_clicks before the event insert (analytics_service.go:136-157). pawprintz reads those Postgres columns, so scores keep moving even if the event store is down. Every error on the track endpoint still returns 204 so the edge never retries or logs noise.

Which store

main.go:116-122 pings ClickHouse once at boot using CLICKHOUSE_HOST (default localhost), CLICKHOUSE_PORT, CLICKHOUSE_DB. On failure clickhouseDB is nil and NewPostgresClickStore writes to link_clicks (migration 000008), which was added for exactly this fallback by the 2026-10-09 launch scope. Production’s store is UNVERIFIED: a 2026-10-10 read-only Railway check found no ClickHouse variable in production, ClickHouse provisioning is deferred to M4.5, and the production project census on 2026-10-18 listed only Postgres and purr-api. Both facts point to Postgres; the boot log line Click analytics store: ... would confirm it.

link_clicks columns: link_id, user_id, clicked_at, visitor_id, country, city, device, browser, os, referrer_domain, utm_source … utm_content, is_bot. There is no IP column.

Gaps

  • Proxy-mode clicks are never tracked. handleProxyMode streams the destination without calling trackClick (index.ts:201-231).
  • Attribution is host-blind. The track POST carries only slug; purr-api resolves it with uid = $1 OR custom_slug = $1 LIMIT 1. A petrova.smo1.io custom slug that collides with an apex custom slug can be credited to the wrong link.
  • The edge visitor_id is discarded; see smo1-007.
  • Clicks served by purr-api’s own /:identifier route (direct hits on purr.smo1.io/<uid>) are tracked from Gin with CF-IPCountry as country (redirect_handler.go:75-85).

Key Terms

  • ClickStore — interface with ClickHouse and Postgres implementations, chosen once at boot
  • link_clicks — the Postgres fallback event table (and the ClickHouse table name in litter-box)
  • IncrementClicks — the counter update scores depend on
  • 204 always — the track endpoint never reports failure to the edge

Q&A

Q: Why is the redirect returned before analytics are recorded? A: So a slow or failing purr-api, ClickHouse or Postgres never delays or breaks a visitor’s redirect. The Worker sends the 302 and keeps the tracking POST alive with waitUntil.

Q: Which database stores raw click events? A: Whichever store purr-api chose at boot: ClickHouse if it answered the startup ping, otherwise Postgres link_clicks. Production is most likely on Postgres; that is UNVERIFIED pending the boot log.

Q: What hash function is used for visitor deduplication? A: SHA-256 in purr-api, truncated to 16 hex characters, over IP plus User-Agent with no date. The edge’s own daily 32-bit hash is sent but ignored.

Q: Which clicks never reach purr-api at all? A: Requests the edge classes as bots, and every visit to a proxy-mode link.

Examples

The 2026-10-18 production demo walk created smo1.io/melty, clicked it once, and saw “one real boop counted; Analytics charts populated” (gate G-5-4). That boop moved links.total_clicks first and then landed in the click store; a fix the same day (purr-api #64) made both stores return 30 days for the “Boopz Over Time” chart instead of 14.

neighbors on the map