You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Writers want to know their reach. Add a per-article analytics view visible to the author showing views, estimated reads, reactions, and bookmarks over time.
Context: Part of the TechDiary comeback strategy — writers choose platforms where they can see their impact.
Implementation Plan: ClickHouse for Article Analytics
Why ClickHouse?
PostgreSQL (current DB) is not suited for analytics — counting millions of page view events with time-series aggregation is slow and pollutes the main DB. ClickHouse is purpose-built for this:
Columnar storage — aggregations over millions of rows are instant
Append-only inserts — perfect for event streams (page views)
Time-series queries — built-in date functions, fast GROUP BY toDate(timestamp)
Free tier available — ClickHouse Cloud has a generous free tier, or self-host via Docker
No impact on the main PostgreSQL DB
Architecture
Browser
└── POST /api/analytics/pageview (lightweight API route)
└── ClickHouse: INSERT INTO article_views
└── (batch insert via @clickhouse/client)
Dashboard page
└── Server Action: getArticleAnalytics(articleId)
└── ClickHouse: SELECT toDate(viewed_at), count() GROUP BY 1
ClickHouse Schema
CREATETABLEarticle_views (
article_id UUID,
viewer_id Nullable(UUID), -- NULL for anonymous
session_id String, -- fingerprint / session token hash
referrer Nullable(String),
country_code Nullable(String), -- from IP geo (optional)
viewed_at DateTime DEFAULT now()
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(viewed_at)
ORDER BY (article_id, viewed_at);
Use MergeTree — fast inserts, efficient range scans by article_id + viewed_at.
Deduplication — count unique session_id per article per day to avoid reload inflation:
SELECT toDate(viewed_at) asdate, uniq(session_id) as views
FROM article_views
WHERE article_id = {articleId}
AND viewed_at >= now() - INTERVAL 30 DAY
GROUP BYdateORDER BYdate
Analytics Server Action
// src/backend/services/analytics.actions.tsexportasyncfunctiongetArticleAnalytics(articleId: string){constuserId=awaitauthID();// verify ownership before returning dataconstrows=awaitclickhouseClient.query({query: ` SELECT toDate(viewed_at) as date, uniq(session_id) as views FROM article_views WHERE article_id = {articleId: UUID} AND viewed_at >= now() - INTERVAL 30 DAY GROUP BY date ORDER BY date `,query_params: { articleId },format: 'JSONEachRow',});returnrows.json();}
Dashboard UI
Time-series line chart using Recharts (already likely in deps via shadcn)
Summary cards: total views (all time), views this month, reactions, bookmarks
Reactions + bookmarks still queried from PostgreSQL via existing repositories
Activity
kingRayhan commentedon Apr 5, 2026
Implementation Plan: ClickHouse for Article Analytics
Why ClickHouse?
PostgreSQL (current DB) is not suited for analytics — counting millions of page view events with time-series aggregation is slow and pollutes the main DB. ClickHouse is purpose-built for this:
GROUP BY toDate(timestamp)Architecture
ClickHouse Schema
Use
MergeTree— fast inserts, efficient range scans byarticle_id + viewed_at.New Files
Environment Variables to Add
Tracking Logic
Client-side — fire-and-forget fetch on article page load:
API route — inserts into ClickHouse, returns 200 immediately (no await needed by client):
Deduplication — count unique
session_idper article per day to avoid reload inflation:Analytics Server Action
Dashboard UI
Package
Rollout Order
clickhouse.client.tsPOST /api/analytics/pageviewrouteanalytics.actions.tsquerykingRayhan commentedon Apr 5, 2026
#125