W.William
Weeks-Balconi
View source ↗

Cloud & operations

Fabric Capacity Tuner — Size F-SKUs, Tame 430s

Reference guide8 min readOpen working example ↗

Microsoft Fabric capacity sizing is a balancing act between cost, concurrency, and throttling risk. The Fabric Capacity Tuner demo turns that tradeoff into a live model: choose an F-SKU, select the mix of DirectQuery, Import Refresh, Spark Notebook, Pipeline Activity, Dataflow Gen2, and KQL Query workloads, then raise concurrency until utilization moves from healthy to 430-throttled. The operating lesson is simple: size for sustained load, schedule bursty background jobs, and fix CU-heavy Spark patterns before buying a larger SKU.

  • 8 — F-SKUs

  • 6 — Workload Types

  • 5 — Mitigations

  • 430 — Throttled

Observe→ Decompose→ Schedule→ Mitigate→ Right-Size

F-SKU Sizing 101

Fabric F-SKUs are purchased capacity for a tenant or workspace estate. Each SKU gives a fixed number of Capacity Units (CU). Workspaces assigned to the capacity share that CU pool across Power BI, Data Factory, Data Engineering, Data Science, Real-Time Intelligence, Warehouse, and Lakehouse activity.

The simulator uses the same mental model an operator uses during an incident: compare concurrent CU demand against available SKU CU. Green means there is headroom, amber means monitor, orange means smoothing is absorbing pressure, and red means sustained demand exceeds the capacity envelope.

SKUCapacity UnitsApprox. list price / monthTypical role
F22 CU$262.80Small development, proof-of-concept, light reports
F44 CU$525.60Team sandbox with low concurrency
F88 CU$1,051.20Shared dev/test, light pipelines, simple semantic models
F1616 CU$2,102.40Entry production with disciplined scheduling
F3232 CU$4,204.80Medium production, multiple domains, mixed BI and ETL
F6464 CU$8,409.60Large production, full smoothing, high BI concurrency
F128128 CU$16,819.20Enterprise shared capacity with heavy Spark and Warehouse
F256256 CU$33,638.40Very large tenant-wide platforms or isolated critical estates

Smoothing matters: Fabric evaluates throttling through smoothed usage, not just a single instant. Background work can be smoothed across a 24-hour rolling window on larger capacities, which is why a short spike does not always produce an immediate 430. Sustained overage is different: once the smoothing buffer is exhausted, new or continuing work can be delayed, rejected, or throttled.

That is why the demo’s thresholds are deliberately conservative. Under 50% utilization, there is room for mistakes. From 50% to 80%, teams should watch trends. From 80% to 100%, every new schedule or notebook matters. Above 100%, the operator should expect the capacity to spend down smoothing credits and eventually throttle.

The right response is not always an immediate scale-up. First, determine whether the pressure is interactive, background, or accidental. A user-facing DirectQuery peak deserves a different response than a forgotten notebook pool, an import refresh storm, or a dataflow that is scanning unchanged rows.

  • Interactive pressure needs immediate user-impact mitigation.

  • Background pressure usually needs scheduling and smoothing.

  • Accidental pressure needs guardrails, not a bigger invoice.

Capacity Units (CU): The Currency of Fabric

Capacity Units are the budget Fabric spends to execute work. A Spark session consumes CU based on driver and executor cores. A report query consumes CU while the visual renders. A pipeline consumes CU while activities orchestrate movement and transformation. The shared capacity is healthy when the total smoothed demand stays below the SKU’s available CU.

For Spark notebooks, a practical rule from production operations is that one vCore is approximately one CU. An oversized environment with 8 driver cores and 8 executor cores can consume an entire F16. If dynamic allocation is disabled, that session can hold the capacity even while a user is between notebook cells.

The most useful dashboard question is not “what is the average usage?” It is “which workspace, item, or workload is holding CU during the peak?” The Microsoft Fabric Capacity Metrics app is the first stop for that answer because it breaks down utilization by item and operation.

Workload Profiles and Their CU Cost

The demo models six workload types. The CU values are intentionally simplified so the simulator is teachable: each selected workload contributes to per-user CU demand, and concurrency multiplies it. In real operations, the exact number depends on query shape, model design, Spark configuration, data size, cache state, and whether the work is interactive or background.

WorkloadModeSimulator CUWhat drives costFirst optimization
DirectQueryInteractive2 CUVisual count, query complexity, source latencyReduce visuals, aggregate tables, migrate to DirectLake where possible
Import RefreshBackground4 CUDataset size, refresh frequency, transformation shapeIncremental refresh and overnight scheduling
Spark NotebookInteractive or batch8 CUDriver/executor cores, shuffle volume, joins, file pruningRight-size environment and enable dynamic allocation
Pipeline ActivityBackground2 CUCopy volume, orchestration fan-out, retry behaviorStagger schedules and avoid unnecessary polling loops
Dataflow Gen2Background6 CUPower Query steps, joins, refresh windowPush filters earlier and materialize reusable tables
KQL QueryInteractive3 CUTime range, cardinality, joins, result sizeConstrain time windows and pre-aggregate high-volume streams

When Fabric Returns HTTP 430

HTTP 430 is the capacity signal. It is different from a generic API rate limit. In Fabric, a 430 means the capacity’s smoothed usage has exceeded the allowed envelope and the service is protecting the capacity from more work. Operators often see this as failed notebook sessions, queued pipeline runs, delayed report renders, or API calls rejected during a peak.

Interactive throttling hurts users immediately because the person waiting for a visual, notebook cell, or SQL query is on the critical path. Background throttling is often less visible at first because scheduled jobs can be delayed, smoothed, or retried, but it still creates downstream freshness risk.

StatusFabric-specific interpretationWhat to do
200Request completed successfully.No incident. Record latency and CU if optimizing.
202Request accepted; Fabric is running it asynchronously.Poll the operation status with backoff instead of tight loops.
429API rate limit or too many requests at the service boundary.Back off, add jitter, reduce request fan-out, check caller behavior.
430Capacity throttled because smoothed CU usage exceeded allowance.Check Capacity Metrics, stop runaway work, reschedule jobs, or scale up.
503Capacity or service unavailable, paused, deleted, or temporarily unhealthy.Verify capacity state, region health, and workspace assignment.

The Mitigation Playbook

A 430 is rarely solved by one change. The durable fix is a playbook: identify the consuming workload, reduce waste, move flexible work away from peaks, isolate blast radius, and then right-size the SKU with evidence.

1. Overnight scheduling

Use when: import refreshes, Dataflow Gen2 runs, Spark training, table maintenance, or pipeline batches are not user-facing. Move them into off-hours windows so background smoothing can spread load across the day instead of colliding with interactive BI hours.

2. Workspace splits

Use when: one domain or experimental team can starve everyone else. Assign high-risk workspaces to separate capacities, or at least separate production, non-production, and sandbox work. This turns a tenant-wide incident into a team-scoped one.

3. DirectLake migration

Use when: import refresh is burning CU just to copy data that already lives in OneLake. DirectLake can reduce refresh pressure by reading Delta files directly while preserving interactive report performance for suitable semantic models.

4. Pause non-prod outside business hours

Use when: dev, test, and sandbox capacities are idle overnight or on weekends. F-SKUs bill while running, so pause/resume automation can remove a large share of non-prod monthly spend without changing production posture.

5. Right-size workloads before right-sizing SKUs

Use when: Capacity Metrics shows a small number of items causing peaks. Fix oversized Spark environments, inefficient joins, unbounded queries, excessive refresh frequency, and polling loops before moving from F32 to F64.

PySpark Patterns That Save CU

PySpark is the place where small code choices can become large capacity events. The highest-return patterns are broadcast joins for small dimensions, predicate pushdown, partition pruning, compact file layouts, and Z-ordering columns used in filters and joins. These patterns reduce shuffle, scan fewer files, and shorten the time each session holds CU.

Notebook authors should also right-size Spark settings. Use dynamic allocation for interactive work, avoid fixed executor counts unless a job truly needs them, and reduce spark.sql.shuffle.partitions from the default when working on small or medium datasets.

from pyspark.sql.functions import broadcast, col

spark.conf.set("spark.sql.shuffle.partitions", "8")
spark.conf.set("spark.databricks.delta.optimizeWrite.enabled", "true")
spark.conf.set("spark.databricks.delta.autoCompact.enabled", "true")

orders = (
    spark.table("lh_sales_slv.sales_orders_clean")
         .where((col("OrderDate") >= "2026-01-01") & (col("OrderDate") < "2026-02-01"))
         .select("OrderId", "CustomerId", "OrderDate", "NetAmount")
)

customers = (
    spark.table("lh_sales_slv.dim_customer")
         .select("CustomerId", "Segment", "Region")
)

result = (
    orders
      .join(broadcast(customers), "CustomerId", "left")
      .repartition("OrderDate")
)

(result.write
    .format("delta")
    .mode("overwrite")
    .option("replaceWhere", "OrderDate >= '2026-01-01' AND OrderDate < '2026-02-01'")
    .saveAsTable("lh_sales_gld.fct_orders_month"))

The snippet keeps CU low by filtering early, selecting only needed columns, broadcasting a small dimension, partitioning the write by the date used for reruns, and avoiding a full-table overwrite. For large tables, pair this with Delta optimization and Z-ordering on high-selectivity columns used by report and warehouse queries.

Right-Sizing: F32 vs F64 vs Pause Strategy

Right-sizing starts with observed utilization, not fear. If an F32 is below 40% most of the week and only spikes during a nightly batch, the answer is probably scheduling or query tuning, not F64. If it spends business hours above 80% with interactive users waiting, F64 may be justified. If non-prod sits idle for 14 hours a day, pause strategy may save more than a downsize.

ChoiceBest fitMonthly implicationRisk
Stay on F32Peaks are rare, background, and controllable with schedule changesAbout $4.2K/monthRequires discipline; runaway notebooks can still cause 430s
Move to F64Interactive demand regularly exceeds F32 even after optimizationAbout $8.4K/monthCan hide inefficient workloads instead of fixing them
Pause non-prodDev/test/sandbox capacity is idle outside business hoursOften 60–70% savings on those capacitiesNeeds reliable automation and clear developer expectations

The practical operating loop is: observe seven days of Capacity Metrics, decompose by workspace and item, schedule flexible jobs away from the peak, mitigate inefficient patterns, and only then buy more CU. That loop keeps the platform reliable without turning every 430 into a procurement event.