Every couple of years the modeling debate flares up again. Activity schema partisans declare star schemas obsolete. Kimball loyalists point at every activity-schema deployment that has ground to a halt under self-joins. Both camps are arguing past each other. At $100M+ revenue, with the data volumes that go with it, the choice is not religious — it’s a sequence of trade-offs that vary by surface area.

What each model actually optimizes for

Dimensional modeling optimizes for read patterns that BI tools and analysts already understand. A fact table joined to conformed dimensions is fast on every modern warehouse, every BI vendor knows how to query it, and the semantic layer story is mature. The cost is upstream complexity: SCD type 2 logic, late-arriving facts, conformed-dimension governance across domains, and the discipline to keep grain consistent across ten-plus marts.

Activity schema optimizes for a single canonical event log that every downstream question reduces to a self-join over. One narrow table — customer, ts, activity, feature_json — replaces a fan of fact tables. The cost is query shape: every non-trivial question becomes a temporal self-join (first_before, last_after, nth_within), which warehouses can execute but at a real compute multiplier when the activity stream crosses the 10B-row mark.

Neither model is wrong. They are answers to different questions: “how do I make analysts productive on day one?” versus “how do I keep behavioral logic in one place as the business changes?”

Where the lines actually fall at scale

We’ve seen the same pattern across half a dozen $100M+ engagements. The split that holds up:

Dimensional wins for finance, ops, and external reporting. Anything that has to tie to GL, anything an auditor will read, anything a CFO will defend. These domains have stable grain, stable conformed dimensions, and a strong preference for queries that look the same year over year. Star schemas in a marts layer, owned by domain teams, governed through dbt contracts. The grain rarely changes; the questions rarely change; the BI tooling pays you back every quarter.

Activity schema wins for product, growth, and lifecycle. Anything where the question shape is “what did the user do before X, and what predicted Y”, anything where the team adds a new event type every sprint. One activity stream, one set of temporal aggregation macros, and the product analyst doesn’t need a new fact table every time marketing ships a feature. The schema is genuinely cheaper to maintain when the event taxonomy is in flux.

The overlap is where the religious arguments start. Customer analytics — churn, retention, expansion — sits on the seam. Most teams that pick one religion regret it within eighteen months. The pragmatic move is to model both: dimensional dim_customer and fact_subscription_event for the boardroom, plus an activity stream for the data scientists running survival models. The duplication is real but bounded, and the storage cost at $100M scale is a rounding error against the cost of an analytics team that can’t ship.

Mid-ground patterns that actually work

Three patterns we use routinely:

1. Activity schema over the bronze layer; dimensional in the gold layer. Land everything as activities — one row per business event, immutable, append-only. Build conformed facts and dimensions as downstream materializations off the activity stream. The activity log is the system of record for behavior; the marts are the read-optimized projection. dbt or SQLMesh handles the materialization graph cleanly. Reprocessing is cheap because activities never mutate.

2. Activity schema for behavioral domains; dimensional for transactional domains. Don’t unify the two. Subscription billing, AR/AP, inventory — keep these dimensional, with proper SCD2 on dim_customer and dim_product. Product telemetry, marketing touches, support interactions — keep these in an activity schema. Cross-domain questions (LTV by acquisition channel, support load by plan tier) join on natural keys at query time. You pay a small join cost; you keep two simple models instead of one complex one.

3. Materialize the temporal joins. The activity-schema critique that lands is real: ad-hoc first_before/last_after queries on a 50B-row activity table are not fast. The fix isn’t to abandon activity schema — it’s to materialize the temporal aggregations the business actually asks for. Customer-level features, session-level rollups, conversion-funnel snapshots: precompute these on a cadence and let analysts query the rollups. Your activity stream stays canonical; your read path stays cheap. This is just incremental modeling with a different grain, and dbt incremental models or SQLMesh’s interval logic handle it natively.

What to ask before you pick

Before you commit to a modeling religion, answer four questions about your environment. How often does the event taxonomy change? How big is your BI tool surface area? How much of your reporting has to tie to GL? How sophisticated is your data science team about temporal queries?

High taxonomy churn plus strong DS team plus narrow BI surface plus light GL coupling — activity schema, with marts as a secondary projection. Low churn plus broad BI plus heavy GL — dimensional first, activity stream only where product analytics demands it. Most $100M+ environments are somewhere in the middle, which is why the mid-ground patterns above tend to win.

The thing to avoid is picking a side because a vendor or a blog post told you to. Both models are tools. Use the one that fits the domain in front of you, and stop apologizing for the seam between them.