Enterprise-Scale Data Platform Design
On one Monday morning, three complaints reach the same platform team. The finance dashboards, which normally load in two seconds, are taking forty. A data scientist’s training job has been queued for an hour behind something she cannot see. And the recommendation service, which reads a feature table on every request, has started timing out at its 99th percentile. Nothing is broken. Each group is using the platform exactly as designed, and the design assumed they would not all do it at once. The example is invented for illustration.
A platform at enterprise scale is not a bigger version of a platform at team scale. The new problems are not about volume; they are about a shared resource serving groups whose requirements actively conflict, spread across organizations and jurisdictions, with somebody eventually asking who should pay for which part. This article works through those problems in the order they usually arrive: what the workloads want, what isolation actually separates, why concurrency is a different question from size, how several organizations share one platform, where data is allowed to live, when to share rather than copy, and how cost gets attributed.
Three workloads that want different things
The conflict is not a matter of one workload being greedy. Each is optimizing for something the others do not value.
| BI and reporting | Data science and ML | Operational serving | |
|---|---|---|---|
| Shape of the work | Many small queries, highly concurrent, repetitive | Few very large scans, often reading files directly, bursty | Constant small lookups by key, continuous |
| What it optimizes | Predictable latency at high concurrency | Throughput on one job; time to finish, not time to start | Tail latency and availability, on a scale of milliseconds |
| Freshness it needs | Usually hours; stated, not assumed | Point-in-time correctness matters more than recency | Seconds, and the same value the application just wrote |
| How it hurts the others | Steady load leaves little headroom for bursts | One scan evicts the cache and holds workers for its duration | Its availability target constrains every change to shared infrastructure |
| What failure looks like | A dashboard that is slow only in the morning | A job queued behind work nobody can identify | A timeout that reaches a customer |
The table describes one invented scenario, not a ranking of workload types. In it, a slow dashboard and a late training job are nuisances and a timeout is a customer-facing incident. Elsewhere the order can reverse: a regulatory report due at noon, or a model refresh that pricing depends on, can matter more than a lookup that tolerates a retry, and not every serving path needs second-level freshness or to read its own writes. Which workload to protect is decided by business impact, deadlines, and the service targets actually agreed for each. What the table does show is why no single global setting works: the workloads fail differently, and a capacity decision that treats them as one kind of load protects whichever one the setting happens to favor.
There is also a case worth naming before the architecture starts: an analysis that fits on one machine is often better served there than by any shared cluster, as covered in single-node analytics. Enterprise scale means the platform must handle large problems, not that every problem must be routed through it.
Isolation: four different things with one name
“Isolate the workloads” is the standard answer, and it hides a choice, because there are four separable things and each costs something different. Workload isolation in the narrow sense is only the first of them.
| What is separated | What it prevents | What it costs |
|---|---|---|
| Compute | One group’s queries consuming the CPU, memory, and slots another needs โ but not contention on storage, network, or shared services, which both pools still use | Potential idle capacity and cache fragmentation, depending on load, pool sizing, and warm-up. Compare cost at the same service targets |
| Storage | Contention for storage throughput, if the separated storage has its own I/O capacity rather than sitting behind the same bottleneck; it also bounds blast radius and simplifies access control | More places to manage. If the same data has to be in both, copies and the divergence that follows them; separating different data needs no copy |
| Metadata and catalog | One team’s schema change or catalog load affecting another’s planning | Registrations to keep in sync. If two catalogs can each commit a new current version of the same table, two answers to what is current; read-only registrations, aliases, and federation that defer to one authority do not have that problem |
| Access | People seeing data they should not | Administration, and the risk of over-restriction blocking legitimate work |
Most of the time the problem in the opening scenario is the first row only, and the right move is separate compute over the same storage, catalog, and access model. That combination is the main practical benefit of separating storage from compute: two pools of compute can read one copy of the data, so isolating performance no longer requires duplicating anything.
The failure mode to avoid is copying data in order to isolate performance. When the contended resource is compute, a copy served by separate compute does stop the groups affecting each other, but through the compute, and it introduces the copy problem โ two versions of the truth, a pipeline to keep them aligned, and an argument about which is right. When the bottleneck is storage throughput, a copy helps only if it lands on separate I/O capacity; a copy behind the same bottleneck keeps contending. Separate the resource that is actually contended. Where that is storage throughput and a copy on separate I/O capacity would relieve it, weigh that gain against what the copy costs โ storage, the pipeline that keeps it aligned, the lag between the two, and the reconciliation โ and against cheaper ways to add throughput to the original, such as a different layout or a higher tier; copy when the gain is worth it.
Concurrency is not the same question as size
When a platform is slow, the reflex is to make it bigger. That answers one of two different questions, and choosing the wrong one costs money without fixing anything.
Snowflake’s documentation on multi-cluster warehouses, draws the distinction plainly for its own product. Scaling up means a larger warehouse, which helps slow-running queries and data loading โ a single unit of work that needs more resources. Scaling out means more clusters, which improves concurrency by letting more queries run simultaneously without queueing. The condition it describes is that when there are not enough resources to execute everything submitted, the extra queries are queued. A multi-cluster warehouse does not split one query across its clusters, which is why more clusters help only the queries that are waiting. Other engines behave differently: in many distributed engines adding workers also shortens individual queries, and a queue can also come from locks, admission limits, or an external service’s rate limits rather than from missing compute.
That is why the dashboards in the opening were slow. They were not slow because each query needed a bigger machine; they were slow because they were waiting.
The two effects are not cleanly separable, though, and it is worth being exact about why. Snowflake’s own guidance says resizing can also help reduce the queuing that occurs when a warehouse lacks the compute to process everything submitted concurrently โ a larger warehouse finishes each query sooner, which drains a queue faster. The same guidance then draws the boundary: resizing is not intended for handling concurrency issues, and additional warehouses or a multi-cluster warehouse are what address a large number of concurrent users and queries. So in Snowflake’s terms, size is the lever for how long a query runs and can relieve a queue as a side effect; cluster count is the lever aimed at the queue itself. Reaching for size when the problem is concurrency sometimes works, at a price that grows with every query that was already fast enough.
Automatic scaling also contains a policy decision, not just a switch. In auto-scale mode, its Standard policy starts an additional cluster when a query queues or resources are insufficient, favoring responsiveness. Its Economy policy starts one only when the system estimates there is enough load to keep it busy for at least six minutes, and marks the least-loaded cluster for shutdown when it estimates that cluster has less than six minutes of work left; the cluster actually shuts down once the queries running on it finish. That conserves credits at the price of queries queueing longer. Same feature, opposite behavior at the moment a queue forms. Its maximized mode, where the minimum and maximum cluster counts are equal and above one, starts all the clusters whenever the warehouse is running, for predictable load; the warehouse as a whole can still suspend.
The cost model follows directly: credits are consumed as warehouse size multiplied by the number of running clusters multiplied by hours. Both scaling directions are multiplicative, which is why “just turn on autoscaling” and “just increase the size” are both capable of producing a surprising invoice. Whatever the product, start by separating whether individual queries are too slow or too many are waiting โ execution time against queue time โ and then find out what the waiting is for and which resource is the bottleneck, because the right lever depends on that and on how the engine distributes work.
Two further controls matter at scale and are easy to overlook. Admission control โ limiting how much work is accepted at once โ protects the platform from the state where everything is running and nothing is finishing. And priority, whether by separate pools or explicit queues, is what lets the recommendation service’s lookups jump ahead of a backfill. Without either, the platform’s behavior under pressure is decided by arrival order.
Several organizations, one platform
At enterprise scale, “users” becomes “business units, subsidiaries, and sometimes external partners,” each with its own budget and its own view of what is confidential. Four things have to be separable, and they are separable to different degrees.
- Access. Who can read what, enforced consistently regardless of which engine asks.
- Cost. Whose budget pays for which compute and storage, at a granularity fine enough to act on.
- Performance. One tenant‘s heavy period not becoming another’s outage.
- Blast radius. A mistake โ a dropped table, a bad deployment, a leaked credential โ being bounded to one tenant.
| Arrangement | Separates well | Costs |
|---|---|---|
| Separate account per tenant | Administration and billing by default, and blast radius for resources inside the account. Access, performance, and failure isolation still depend on configuration: cross-account roles and resource policies can open access, and shared networks, identity, or services can carry failures across | Sharing data between tenants becomes a project; central governance has to be applied to every account; per-account capacity can sit idle, depending on how compute is provisioned |
| Separate workspace per tenant, within one account | Access and blast radius for objects; often billing attribution | Performance is not separated by the workspace itself; it depends on the compute each workspace uses and on any per-tenant queueing, reservation, or scheduling in a shared pool โ see below |
| One platform, separate compute and access per tenant | Performance, access, and cost, provided compute is attributed per tenant | Configuration discipline, and a mistake in a shared control plane reaches everyone |
| Hybrid: shared for common data, separate for regulated or noisy tenants | Matches the cost of isolation to the actual requirement | Two operating models to run, and a rule for deciding which tenant goes where |
The second row is the one that gets assumed rather than checked, and it is worth stating as a general caution: an organizational boundary is not automatically a compute boundary. Workspaces, projects, and folders separate who owns what and who can see what. Whether they separate performance depends on where the queries actually run and what they share there. A separate compute pool is the usual starting point; a shared pool with per-tenant queues, reservations, or scheduling can also bound contention; and separate pools still share storage, network, and platform services. On most platforms these are settings separate from the organizational container. Microsoft Fabric makes the distinction unusually visible: throttling is applied per capacity, so two workspaces assigned to the same capacity share the same throttling state, and separating them into different workspaces changes nothing about contention until they are assigned to different capacities. That shows the boundary in one product; other platforms that pool compute under an organizational container often behave similarly, but check the product rather than assuming it.
So the question to ask of any proposed arrangement is not how the tenants are organized but where each one’s queries actually run, what queueing, admission, or reservation policy governs that pool, and which resources remain shared behind it. Two tenants in one pool with no per-tenant queue or reservation share a queue and a failure mode, whatever the directory structure says; separate pools remove that, but not contention on whatever they still share.
The hybrid row is where most large organizations end up, and the thing to write down is the rule, not the current assignment. If nobody can say what makes a tenant require its own boundary โ a regulatory obligation, a distinct availability target, a pattern of load that harms others โ then tenants get their own boundary by escalation, and the platform slowly becomes a collection of unrelated platforms with a shared logo.
Where the data is allowed to live
Locality is usually treated as a networking topic and is more often a legal one. Three forces pull in different directions.
- Obligation. Regulation or contract may require that certain data stays in a jurisdiction, that it not be accessible from another, or that query results not leave it. This is a constraint, not a preference, and it is settled before the architecture rather than optimized within it. Where stored bytes sit depends on the provider, the service, and the location chosen for the resource: Amazon S3 states that objects stored in a region never leave it unless explicitly transferred or replicated, while Google Cloud Storage buckets can be regional, dual-region, or multi-region. Either way, the storage location settles where the bytes sit and nothing else.
- Latency. Distance costs a round trip that no tuning removes, so readers far from the data are slow unless a copy is near them.
- Cost. Cross-region transfer is charged, continuously, and is the line item most often missing from the design that created it.
The design consequence is that a genuinely global platform tends toward regional data with global metadata: each region holds its own data, while the catalog, definitions, access policy, and lineage are consistent everywhere. That arrangement keeps one description of what exists, and it addresses where data is stored. Whether it satisfies a given residency requirement depends on what that requirement actually restricts, and the honest check has four parts rather than one: where the data is stored, where it is processed, from where it can be accessed, and where query results may be sent. A table that never leaves its region can still be read by an analyst in another jurisdiction, and the results exported there, unless access control says otherwise โ so a residency obligation that covers access needs a control on identity and network path, not only on object location. The global metadata layer deserves the same reading. A catalog that looks the same everywhere does not necessarily copy every entry into every region: products differ in where each type of metadata is stored or replicated and where search is processed, and Google Cloud’s catalog documentation, for one, separates regional resources from global metadata types. Column names, descriptions, and any sample or profile values the catalog collects can themselves be sensitive, so check where each is kept rather than assuming that “metadata is global” is harmless. What the arrangement cannot do is make a cross-region join cheap. A workload that must combine data from two jurisdictions needs an explicit answer. Aggregating within each region, or masking before combining, can reduce what crosses, but it does not replace checking whether the transfer is permitted: an aggregate over one person’s salary is that person’s salary, and a pseudonymized value can still be linked. The summaries and masked values need the same assessment of sensitivity, re-identification risk, and applicable transfer restrictions as the rows they came from โ and whatever transfer is permitted still has to be paid for.
The related trap is that a storage lifecycle policy, of the kind described in storage tiering, lifecycle, and archival, is written per bucket and per region. A rule that is correct in one region and absent in another produces a retention difference nobody intended, and it will be discovered during an audit rather than an incident.
Sharing instead of copying
Every organizational boundary creates a request to get data across it, and the default answer โ extract it and send it โ is the one that produces the most copies. There are three broad approaches, and their differences are mostly about who holds the storage and how stale the reader’s view is.
| Approach | How it works | Freshness | Who pays for what |
|---|---|---|---|
| Extract and deliver | A pipeline produces files or loads a copy into the consumer’s system | Varies with the wait until the next delivery, the run time, and any retries or missed runs; measure the actual lag | Both sides store it; producer runs the pipeline |
| In-place sharing within one platform | The platform grants access to the producer’s objects through its own control plane | Immediate, because there is one copy | Producer stores; consumer pays for its own compute โ unless the provider creates a reader account for a consumer without its own, in which case the provider pays for that compute |
| Open table plus shared catalog | The consumer’s engine reads the producer’s table directly through an agreed format and catalog | Immediate, subject to the reader refreshing metadata | Producer stores; consumer pays its own compute and any cross-region transfer |
Snowflake’s Secure Data Sharing, is a concrete instance of the middle row and states the economics explicitly: no actual data is copied or transferred between accounts, the sharing happens through the services layer and metadata store, shared data does not occupy storage in the consumer account, the only consumer charges are for the compute used to query it (for a consumer with its own account; in a reader account the provider creates, the provider pays), and updates to shared objects become immediately available to consumers. The last point is the one that changes how people work, because a shared table cannot be stale relative to its source in the way an extract always is.
That description holds when provider and consumer are in the same region, and a global platform has to read the qualifier. Snowflake documents that sharing across regions or cloud platforms uses its data replication functionality, with the provider creating one copy of the dataset per region โ one per region, not one per consumer โ and refreshing it either manually or on a schedule. So the middle row quietly becomes the first row once a border is crossed: there is a second copy to store, a refresh cycle that bounds how current a consumer’s view is, and replication cost that scales with what is replicated. A design that assumes zero-copy sharing worldwide has omitted all three.
One correction is worth making because it is the natural next assumption: the schedule is not the guarantee. A schedule states how often a refresh is started; freshness is how old the data a consumer reads actually is, and the gap between them holds the replication run time, any retries, and any skipped cycles. Snowflake’s schedule parameter makes the last one explicit โ a valid run time is skipped if the previous run has not completed before the next one starts. A ten-minute schedule over a replication that takes seven minutes does not deliver ten-minute freshness, and if a run ever stretches past ten minutes the next cycle does not run at all. State the freshness target as an observed lag with a way to measure it, and treat the schedule as the setting you tune to meet it.
The constraint on that model is that it operates within one vendor’s platform. The third row is the open equivalent โ the arrangement discussed in external tables and the lake/warehouse boundary โ and it extends across engines at the price of the consumer’s engine having to understand the format and reach the storage. Which of the two fits depends on whether the data can be queried on the same platform โ directly, or through a reader account the provider creates and pays for โ which is a commercial question as much as a technical one.
Extracts still have their place: a partner outside any shared platform, a regulator who wants a fixed snapshot, a system that cannot read the format. What they should not be is the default for internal consumers, because each one becomes a dataset with its own drift, its own owner question, and its own line in next year’s reconciliation.
Cost, and who can see it
A platform whose cost cannot be attributed will be optimized by whoever is asked about the bill, which is the platform team, who are the least able to decide whether any particular workload is worth its cost. Visibility, showback, and chargeback support different decisions about spending. Each has value on its own; chargeback is not a required final stage.
- Visibility. Cost is measurable per workload, team, or tenant at all. This depends on the isolation decisions above: without identity recorded on shared compute, actual usage cannot be reconstructed afterwards with confidence. Its cost can still be allocated by an agreed rule โ a fixed split, a proportion, a business proxy โ or kept in a central budget, which is a policy choice rather than a measurement and should be labeled as one.
- Showback. Each team sees what it spends, without being billed. This is often where behavior can start to change, because some expensive workloads persist only because nobody who could change them sees the cost โ a dashboard refreshing every minute for an audience that looks at it daily, for instance. Whether it does change depends on whether the team can act on what it sees: whether it controls the schedule or the query, has a reason to care about the number, and can weigh the cost against the value and performance target the workload serves.
- Chargeback. Costs land on the team’s budget. That ties spending more directly to the team that decides on it, but it does not align every incentive: a team minimizing its own charge can still work against the organization’s total. One failure mode is teams avoiding the platform for cheaper unmanaged alternatives, which moves the cost rather than removing it. The FinOps framework treats neither showback nor chargeback as the more mature practice, and does not treat chargeback as required.
Cost controls act at different stages. Configuration and admission limits can constrain work before it starts. Timeouts and idle-compute suspension can stop further consumption after work has begun, within their supported scope. Budget reports and alerts instead use recorded usage and forecasts; an alert does not itself block execution, and collection and response delays allow more spending. Snowflake budgets, for example, can notify on projected spend and can invoke configured stored procedures for actions such as warehouse suspension. Those actions still depend on refresh timing, permissions, and the resources they cover. Check every control’s scope and delay; none of these descriptions alone establishes a real-time ceiling on the complete invoice.
Cost also belongs in the same conversation as service targets. A team asking for higher freshness or lower latency is asking for a change whose cost has to be estimated. Sometimes that is more compute; sometimes an incremental load, a better query, a changed schedule, or removing unnecessary recomputation meets the target at the same or lower cost. The exchange is much easier to have when both sides are stated together, as service level objectives and data freshness targets with a price attached rather than as a preference.
Questions that reveal a shared-platform problem
| Question | If the answer is unclear |
|---|---|
| Which workload’s latency is protected when the platform is under pressure? | Arrival order decides, and the workload with the tightest agreed target loses as often as any other |
| Is the current slowness queueing or execution, and what is it waiting on? | Capacity is added in a direction that does not help, or added at all when the wait is a lock, an admission limit, or an external service |
| What is separated between tenants: compute, storage, catalog, access? | Either copies nobody needed, or contention nobody expected |
| What makes a tenant require its own boundary? | Boundaries are granted by escalation and never reviewed |
| Which data may not leave a jurisdiction, and what enforces that? | A replication setting becomes a compliance finding |
| For each cross-boundary dataset, is it shared or copied, and why? | Extracts accumulate and diverge |
| Can each team see what it spends, and is anything capped before spending? | The platform team optimizes other teams’ workloads on their behalf |
References
Product behavior described here follows the September 2026 documentation. Confirm relevant capacity policies, limits, and lifecycle status when making a deployment decision.
- Snowflake Documentation, Multi-cluster Warehouses
- Snowflake Documentation, Introduction to Secure Data Sharing
- Snowflake Documentation, Sharing Data Securely Across Regions and Cloud Platforms
- Snowflake Documentation, Warehouse Considerations
- Snowflake Documentation, ALTER REPLICATION GROUP (REPLICATION_SCHEDULE)
- Microsoft Learn, Understand capacity throttling and smoothing
- AWS, Amazon S3 User Guide: What is Amazon S3?
- Google Cloud Storage Documentation, Bucket locations
- Google Cloud Documentation, Catalog locations and regional resources
- AWS Identity and Access Management User Guide, Cross account resource access in IAM
- FinOps Foundation, FinOps Framework: Allocation
- FinOps Foundation, FinOps Framework: Invoicing & Chargeback
- Snowflake Documentation, Manage reader accounts
- NIST SP 800-188, De-Identifying Government Datasets: Techniques and Governance (PDF)
- Snowflake Documentation, Monitor credit usage with budgets
Discover more from Insightful Data Lab
Subscribe to get the latest posts sent to your email.
