Building a Startup Analytics Pipeline: ELT, Not ETL
If I could go back to the first month of building product analytics at a startup, I would change exactly one decision: stop transforming data before storing it. We started with the classic ETL approach. It took three painful pivots before we understood why that was wrong.
Fictional product. Real engineering patterns.
What we got wrong first
On PulseBoard — a Customer Success platform — the initial need seemed clear: measure client engagement. But "engagement" is a catch-all term. Is it page views? Time spent? Content downloads? Login frequency? Feature adoption? The answer changed every week as the CS team discovered how clients actually used the product.
With a classic ETL pipeline, every definition change would have required modifying the transformation code, redeploying, and sometimes replaying historical data. In early-stage, that cycle is too slow. We learned this the hard way when the CS team asked us to retroactively weight "time spent on onboarding docs" differently — and our pipeline could not recompute three months of history without a full rewrite of the ingestion logic.
Looking back, the mistake was treating analytics like a feature to ship. It is not. It is infrastructure for learning, and it needs to be as flexible as the questions that drive it. I wish someone had told me that before we spent six weeks building a pipeline we would tear down.
What we rebuilt — and why it survived every pivot
The choice was radically different. Snowplow trackers embedded in React via custom hooks captured every meaningful interaction — opening a document, watching a video, clicking through an onboarding checklist, downloading a resource. Events landed in Snowflake as raw, immutable data. No transformation on the way in. Over 500,000 events per day flowed through this pipeline, and not a single one was modified before storage.
All business logic lived in version-controlled DBT models. Raw events became sessions. Sessions became engagement metrics per account. Engagement metrics fed into health score models. When the engagement definition changed — and it changed often — we modified a DBT model, rebuilt, and new metrics were computed over the full history. No application redeployment needed. The entire transformation chain from raw event to dashboard metric was auditable in Git.
This is what I mean by insurance against your own ignorance. We did not know, when we built the pipeline, that "onboarding completion rate" would become the single strongest predictor of retention. But because we had stored every raw interaction from day one, we could build that metric retroactively without losing a single data point.
Where the real difficulty hid
CubeJS sat on top of Snowflake as a semantic layer, exposing pre-aggregated measures the frontend could query without writing SQL. CS managers got dashboards with engagement trends, account comparisons, and cohort analysis — all powered by the same pipeline. The architecture was clean. The hard part was not technical.
The engagement score condensed dozens of signals into a single health indicator per account. Page views, time spent, content downloads, login frequency, feature adoption — each weighted based on its correlation with retention. Recent activity was weighted more heavily than historical. Feature breadth — using many features lightly — scored differently from feature depth — using one feature heavily.
The hardest part was not the math. It was calibration — the gap between what data says and what experienced people know. Early versions of the scoring model produced numbers that did not match CS intuition about which accounts were healthy. An account with high page views but no downloads scored "engaged" — but the CS team knew from experience that passive browsing without action was a churn signal, not an engagement signal. Another account barely logged in but downloaded every resource we published — the model scored them at risk, but CS knew they were a power user who consumed content offline.
Iterating meant adjusting weights in DBT models, rebuilding the transformation chain, and comparing outputs against known retention outcomes. We went through seven iterations before the score aligned with what the CS team saw on the ground. The version-controlled, code-based approach made this iteration cycle fast — typically a few hours from hypothesis to validated change — and every adjustment was auditable. In hindsight, this calibration loop was where the real product value was built — not in the pipeline architecture, but in the conversation between data and domain expertise.
Pipeline latency: the constraint nobody warned us about
Analytics that arrive too late are useless for proactive CS. If a health score drops but the dashboard only reflects it the next day, the CS team cannot intervene before the client disengages.
The pipeline latency target was under 15 minutes from event capture to dashboard availability. Snowplow events hit the collector in near-real-time. The Snowflake loading window was the bottleneck — we tuned the micro-batch frequency to balance freshness against compute costs. DBT incremental models processed only new events on each run rather than recomputing the full history, keeping transformation time manageable as the dataset grew.
The result was a pipeline where a CS manager could see a client's morning activity reflected in their dashboard before lunch. Not real-time, but fast enough for human decision-making.
What I would tell myself at the start
In a startup, your raw data is your most valuable asset. Store it first, ask questions later. ELT is not just a technical pattern — it is insurance against your own ignorance of what will matter tomorrow. The engagement model we ended up with looked nothing like the one we planned. But because we stored everything and transformed in version-controlled code, every pivot was a DBT rebuild away, not an engineering project.
The pipeline processed over 500,000 events per day with end-to-end latency under 15 minutes. But the metric that mattered most was not throughput — it was the number of times the CS team changed their engagement definition, and how quickly we could follow.