Why We Ripped Out Tableau
Tableau wasn't just expensive. The real costs were stale data, analyst bottlenecks, and hidden liabilities from misunderstood metrics that were poisoning our business decisions.
Venkat Sakamuri
DeepSQL R&D · Ex Oracle Query Engine Team · YC & CMU

TL;DR
- Your BI tool's license cost is a rounding error. The real cost is the team of analysts acting as a ticket queue and the bad business decisions made from their stale, misunderstood reports.
- Live queries from BI tools against an OLTP database are a time bomb. One bad query plan can blow out your buffer cache, trigger I/O storms, and take down your production application.
- "Semantic Drift"—where a metric like
is_activeis misinterpreted by an analyst building a dashboard—is a data quality bug that creates irreversible business liability. You can't justgit reverta bad product strategy.
For two years, we ran Tableau Server. It was the default choice, the path of least resistance. We paid the licenses, hired a data analyst, and pointed it at a read replica of our production Postgres database. And for a while, it seemed fine. The VPs got their dashboards. The product managers could see top-line numbers. The cost seemed contained.
We were wrong. The true cost wasn't the six-figure annual bill. The true cost was a creeping, invisible tax on the entire engineering organization and, worse, the strategic decisions of the company.
The Analyst Bottleneck is a Symptom of a Deeper Sickness
Every request for a new slice of data started with a JIRA ticket. "As a PM, I want to see the adoption rate of the new checkout flow, segmented by users who have enabled 2FA." This ticket would land in the analyst's queue. The wait time was typically 3-5 days.
This delay is annoying, but it's not the real problem. The real problem is what happens next. The analyst, who is smart and capable but lacks deep context on the product's evolution, has to translate that request into SQL. They see a column in the users table called checkout_flow_version. They see another called mfa_enabled. Seems simple. They write the GROUP BY, build the visualization, and ship the dashboard.
The PM looks at the chart and sees that 2FA users have a 20% lower adoption rate. Alarm bells go off. Do 2FA users hate the new flow? Do we have a bug? A meeting is called. Engineering time is spent investigating.
What no one realized is that mfa_enabled was a field populated by a backfill script that only ran on accounts created before the new checkout flow was launched. The two cohorts were mutually exclusive by definition. The analyst, through no fault of their own, had correlated two fields that had no meaningful relationship.
This is Semantic Drift. The business definition of a field (mfa_enabled) had drifted away from its physical implementation. The dashboard was technically accurate but strategically useless, and worse, misleading. We wasted a week of a PM's and an engineer's time chasing a ghost created by a BI workflow that separates context from execution. A simple schema mistake or a poorly understood column isn't a code bug you can roll back; it's a liability that gets baked into your decision-making. You can't revert launching the wrong feature based on a bad chart.
Why Your DBA Hates Your BI Tool
The other reason we lived in a world of JIRA tickets and analyst queues was self-preservation. We couldn't let business users run queries directly against the database. It was too dangerous.
Anyone who's managed a production Postgres or MySQL instance knows the terror of the mystery query. You get a PagerDuty alert: P99 latency is through the roof. You log in, run pg_stat_activity, and see a query from tableau_user that's been running for 30 minutes, consuming all available I/O. It's doing a sequential scan on your 5TB events table.
Why? Because the BI tool generated a garbage query. Something a human would never write. A common pattern is forcing a Nested Loop join where a Hash Join is needed, because the tool wasn't smart enough to structure the query to let the planner use work_mem effectively.
Here’s a taste of a plan we saw that caused a production brownout:
-- EXPLAIN ANALYZE from a real incident
Limit (cost=0.56..5018.73 rows=10 width=141) (actual time=1805361.431..1805361.433 rows=0 loops=1)
-> Nested Loop (cost=0.56..5018.73 rows=10 width=141)
-> Index Scan using users_pkey on users u (cost=0.43..8.45 rows=1 width=73)
Filter: (email ~~ '%@someco.com'::text)
-> Index Scan using events_user_id_timestamp_idx on events e (cost=0.13..5010.27 rows=1 width=68)
Index Cond: (user_id = u.id)
Filter: ((properties ->> 'action'::text) = 'login_failed')
Planning Time: 0.812 ms
Execution Time: 1805361.549 ms
Look at that execution time: 1.8 million milliseconds. 30 minutes. For zero rows. The planner decided to scan a massive index on events for every single user that matched the email filter, applying a slow filter on a JSONB column after the index scan. This single query blew out our Postgres shared_buffers, evicting hot pages our application relied on and causing a cascade of performance issues.
The standard answer to this is extracts. Just copy the data out of the production replica into Tableau's own columnar engine. This solves the performance problem but creates two new ones: your data is now perpetually stale (by hours or even a day), and you've just signed up to build and maintain yet another ETL pipeline. You're burning CPU and I/O to copy data around, just so you can look at an old, potentially misleading picture of your business.
At Oracle, we obsessed over this. On the Exadata query engine team, we wouldn't have solved this with extracts. We’d use the Resource Manager to cap the runaway session's CPU and I/O, killing the query before it hurt anyone else. We leaned heavily on storage indexes (Zonemaps, which I personally worked on) to let queries prune massive partitions of data at the storage layer, making ad-hoc analytics orders of magnitude cheaper and safer without requiring a perfect B-tree index for every conceivable query. Postgres has BRIN indexes, but the broader tooling for safely managing multi-tenant workloads isn't there by default.
The True Cost
The Tableau license felt expensive until we calculated the true cost:
- Salary Cost: 1.5 full-time data analysts just to service the report-building queue.
- Infrastructure Cost: Over-provisioned read replicas to handle the spiky, inefficient query load from extract refreshes.
- Opportunity Cost: Every day a PM waited for a chart was a day we weren't making a data-informed decision.
- Liability Cost: The unquantifiable but massive cost of making a wrong strategic bet—hiring for the wrong segment, building the wrong feature—based on a dashboard that was subtly but critically wrong. This is the silent killer.
We didn't have a dashboard problem. We had a systems problem. We were using 2010's architecture—manual ETL, analyst queues, stale data—to answer 2024's questions. We ripped it out.
What DeepSQL does about this
We built DeepSQL to solve this problem at the root. It connects to your database and ingests not just the schema, but also the business context from your documentation and collaboration tools. When a product manager asks, "How many weekly active users from the new campaign have enabled 2FA?", DeepSQL already understands what weekly active user means from a business context. It writes the correct, performant SQL with the right joins and filters, because it has a complete graph of your data and business logic. Before executing, it generates the query plan, predicts the cost, and ensures it won't harm the database. We get live answers from the source of truth, without the analyst queue, without stale extracts, and without the risk of a bad query taking down production.
