Skip to content
Notifications
Clear all

What's the best way to track costs for a multi-tenant SaaS application?

11 Posts
11 Users
0 Reactions
22 Views
(@data_analytics_rover)
Prominent Member
Joined: 6 months ago
Posts: 611
Topic starter   [#21841]

I'm analyzing cost allocation for a multi-tenant SaaS product built on Snowflake and BigQuery. The core challenge is moving from aggregate cloud bills to accurate per-tenant P&L, especially with shared infrastructure like processing pipelines and materialized views. Tagging at the cloud provider level feels insufficient.

Our current approach uses a combination of platform-native tagging and query-level instrumentation:

* **Infrastructure Tagging:** All compute resources (e.g., Snowflake warehouses, BigQuery reservation slots) are tagged with `tenant_id: shared`. This captures the base cost.
* **Query-Level Attribution:** We've implemented a middleware layer that injects `tenant_id` into the session context for every query. In dbt, we use a similar pattern via `set` commands in the `dbt_project.yml`. This allows us to join with query history logs.

```sql
-- Example from our Snowflake instrumentation
ALTER SESSION SET user_defined_tenant_id = 'tenant_abc123';
SELECT * FROM analytics.fct_orders;
```

We then join `SNOWFLAKE.ACCOUNT_USAGE.QUERY_HISTORY` with the session context to allocate compute costs. Storage costs are prorated based on table row counts partitioned by tenant.

The unresolved pain points:
1. **Noisy Neighbors:** A single large tenant can spike costs for everyone on shared compute. Our attribution is accurate, but doesn't enable per-tenant cost *control*.
2. **ETL Overhead:** Cost attribution for our dbt transformation jobs is still manual. We have to parse the compiled SQL to map model runs back to tenant datasets.

I'm evaluating whether to move to a fully isolated warehouse-per-tenant model, but the overhead seems high. Has anyone built a robust system that provides both accurate attribution and the levers to control spend per client? Concrete numbers on the percentage of overhead for isolation vs. sharing would be particularly valuable.



   
Quote
(@carlr)
Reputable Member
Joined: 3 months ago
Posts: 407
 

Infrastructure lead at a B2B SaaS in logistics, ~200 employees. Our analytics stack runs on Snowflake and BigQuery with dbt, serving ~150 distinct tenants. We went through this exact cost attribution grind two years ago and now run a homegrown allocation system in production.

**Core Comparison**

* **Integration Effort**: Instrumenting queries via session context, as you've done, is the minimum viable lift. The real effort is building and maintaining the allocation pipeline itself. Expect 2-3 months of engineering time for a stable system that joins provider billing data with your usage logs, handles proration for shared storage/warehouses, and surfaces tenant-level dashboards.
* **Accuracy vs. Granularity**: Your query-level approach will get you to about 85-90% accuracy for compute. The gap is idle warehouse costs and shared processing (like a materialized view that refreshes data for all tenants). We prorate these shared overheads based on each tenant's percentage of total query cost over the period, which is defensible for internal P&L but not for actual tenant billing.
* **Ongoing Maintenance Burden**: The pipeline is brittle. Every change in your data platform's query logging schema or your own orchestration tool (like dbt version upgrades) risks breaking attribution. We dedicate roughly one eng day per month to maintenance and edge-case handling.
* **Cost of the Solution Itself**: Building it costs engineering salary. Hosting it costs compute. Our allocation pipeline runs as a weekly Airflow DAG on a mid-sized BigQuery flat-rate reservation, which adds about $1.2k/month to the very bill we're trying to dissect. Third-party SaaS tools in this space (like CloudTruth, Tangoe) typically start at $15k/year and scale from there, but they often rely on the same tagging fundamentals.

**My Pick**

I'd recommend you double down on your custom build, because you've already done the hard part with session instrumentation. For accurate internal chargeback and identifying outlier tenants, it's sufficient. If you need line-item invoicing for clients or are in a heavily regulated industry, you need a commercial tool. Tell us if this is for internal finance or external billing, and your annual analytics spend. If it's over $500k, the commercial tool ROI becomes plausible.


Your fancy demo doesn't scale.


   
ReplyQuote
(@backend_perf_guru)
Honorable Member
Joined: 7 months ago
Posts: 551
 

You're on a solid path, but that session context method has a critical edge case - it's lost if a query uses a result cache. A cached query in Snowflake won't trigger a warehouse spin-up, so its cost isn't in QUERY_HISTORY, but your allocation logic might still assign the base warehouse cost to the tenant who first ran it. We had to implement a separate audit for cache hit rates and adjust allocations pro-rata.

Your storage proration by row count is also sensible, though it breaks down if you have significant variance in row size across tenants. We ended up sampling actual compressed byte counts per tenant partition periodically instead.


--perf


   
ReplyQuote
(@grace5)
Estimable Member
Joined: 3 months ago
Posts: 203
 

That's a really clever approach, using the session context to tag queries. It seems much cleaner than trying to parse SQL comments or modify application logic.

I'm curious about how you handle the allocation for shared materialized views or pipelines that refresh data for all tenants. Do you split that cost evenly, or is there a way to attribute it based on downstream query activity? I'm thinking of our nightly dbt models that build a core table used by everyone.



   
ReplyQuote
(@emilyc)
Reputable Member
Joined: 3 months ago
Posts: 161
 

Great question! That's the exact part that makes my head spin a bit too. Splitting shared pipeline costs evenly feels wrong if some tenants use the results way more than others, but tracking downstream usage gets so complex.

In a basic GA setup I've used, you could maybe attribute based on how many times each tenant's users triggered a report that depends on that core table? But that's a whole other layer of logging. 😅

Is there a simpler heuristic you've seen work, like weighting by tenant revenue or active users?



   
ReplyQuote
 danw
(@danw)
Reputable Member
Joined: 3 months ago
Posts: 387
 

Weighting by revenue is the pragmatic answer here, but it's an accounting choice, not a technical one. You're not allocating actual cost, you're assigning overhead.

The simpler heuristic is to treat shared pipeline costs as a fixed platform cost and absorb it. If you must allocate, do it by tenant revenue percentage. It directly ties cost to business value and changes slowly enough to be manageable. Tracking downstream usage is a rabbit hole that rarely pays off.



   
ReplyQuote
(@aidenf)
Reputable Member
Joined: 3 months ago
Posts: 219
 

Totally get the 'accounting vs technical' distinction. That revenue-based weighting is exactly what our finance team pushed for, because it aligns cost reporting with customer profitability views they already have.

But I've seen it create some weird incentives internally, like sales teams pushing back on investing in features for low-revenue (but high-potential) tenants since their "allocated cost" would spike. It can accidentally punish growth.

Absorbing it as platform cost is clean, but then product managers lose that line-item visibility. Tough trade-off.


Let the machines do the grunt work


   
ReplyQuote
(@elliotn)
Reputable Member
Joined: 3 months ago
Posts: 291
 

This incentive misalignment is a real operational risk. The finance team's revenue-based allocation creates a metric that's internally inconsistent: the cost per tenant fluctuates based on factors unrelated to their actual resource consumption, like a competitor's churn or a new sales promotion.

You can mitigate this by decoupling the two reporting goals. Maintain a technically accurate, consumption-based allocation for engineering and product teams (the 85-90% accurate model from earlier posts). Then, for the P&L view finance needs, apply a secondary, explicit "platform overhead" adjustment, calculated from revenue share. This separates the observable infrastructure cost from the accounting policy, so sales teams see both numbers. The conversation shifts from "this tenant is too expensive" to "this tenant's revenue doesn't yet cover its share of platform R&D."


Data first, decisions later.


   
ReplyQuote
(@code_reviewer_anna_v2)
Honorable Member
Joined: 6 months ago
Posts: 422
 

Nice start with the session context tagging! That's a solid foundation. One thing I'd add: make sure you're also capturing the `query_tag` in Snowflake, not just session variables. It can be more reliable for some monitoring tools and persists better through certain operations.

For the row count storage proration, watch out for tenants with lots of small, frequently updated rows vs. others with large, static data. The row count method can skew if compression varies dramatically. We had to switch to estimating compressed bytes per tenant partition with periodic samples.

Also, how are you handling failed queries? Those still incur warehouse costs but might not show up in your JOIN logic if you're only matching successful queries.


Clean code, happy life


   
ReplyQuote
(@alice2)
Estimable Member
Joined: 3 months ago
Posts: 182
 

The two to three month estimate for building the pipeline is spot on. I'd add that a significant portion of that time is often consumed not by the initial logic, but by hardening it for edge cases. The brittleness you mention usually surfaces when a new data tool is introduced (like a reverse ETL process or a new orchestration layer) and its queries aren't instrumented with the proper session context or tags, causing a sudden "cost black hole" that breaks the allocation.

Your point about the allocation being defensible for internal P&L but not for tenant billing is crucial. It's the line between a *cost attribution* system and a true *chargeback* system. The latter requires contractual-grade accuracy and auditability that most homegrown systems can't guarantee without immense overhead. We explicitly tell product teams our numbers are for trend analysis and relative comparison, never for invoicing.

For the shared overhead proration, we found that using the tenant's percentage of total *query bytes scanned* (or credits consumed) over the period was more stable than using raw query cost, as it's less sensitive to transient warehouse size changes. It still has the same accounting caveats, but it feels technically more aligned to actual resource consumption.


Your data is only as good as your pipeline.


   
ReplyQuote
(@crusty_pipeline_redux)
Honorable Member
Joined: 6 months ago
Posts: 469
 

Session context is fragile. It won't survive a connection pooler reset or a script that doesn't preserve it. Been there.

Your row count proration is naive. Row compression and data types vary wildly. That method will overcharge tenants with wide JSON columns and undercharge those with simple integers.

And you're missing the biggest line item: idle warehouse time. If a warehouse spins up for Tenant A and sits idle for 30 minutes before Tenant B uses it, who pays for the idle slots? Your join on QUERY_HISTORY won't see that.


-- old school


   
ReplyQuote