Representative interview topic

System Design Interview: How Do You Isolate Noisy Tenants and Schedule Fairly?

System designHard
Offer.cc Editorial TeamPublished Updated

Question

Design an asynchronous reporting service for thousands of enterprise tenants. A few tenants submit large export bursts at month end, but they must not destroy other tenants’ latency or availability. Explain tenant isolation, quotas, queues, scheduling, storage, degradation, scaling, and validation.

Prompt and context

The core is performance isolation and fairness, not merely adding a tenantId. The service accepts asynchronous report jobs that consume queues, CPU, database scans, object storage, and download bandwidth. A month-end burst makes a few tenants noisy. Start by defining service objectives, then explain what is shared and what is isolated.

Assume a tenant can read only its own data, reports may complete asynchronously, and temporary delay is preferable to cross-tenant leakage. Enterprise tenants may have different plans, regions, and retention policies; compliance, residency, and dedicated capacity are hard constraints to clarify. Fairness does not mean equal throughput forever: contract and security priorities can have explicit, auditable weights.

The scenario fits backend, platform, SRE, and system-design interviews. AWS presents shuffle sharding as a core multi-tenant isolation pattern, while public system-design material treats tenant isolation, quotas, and noisy neighbors as common design concerns. This question focuses on scheduling and blast radius rather than a complete SaaS feature set.

What interviewers assess

First, can you identify every isolation surface? Tenant identity, jobs, queues, workers, database connections, caches, object storage, and egress may all be shared; isolating only the ingress lets noise penetrate downstream.

Second, can you distinguish quotas, fair scheduling, and hard isolation? Tenant token buckets cap volume, fair queues choose who runs next, and shards or dedicated pools bound failures. They solve different problems.

Third, can you reason about the cost trade-off between large and small tenants? Full dedication creates idle capacity and operational burden; full sharing creates contention. A strong answer gives tiers and migration triggers.

Fourth, can you prove isolation? Measure latency, rejection, queue age, quota consumption, retries, and drops by tenant, queue, and resource layer rather than relying on global averages.

Clarifying questions

  • What are the service objectives? Define p95, maximum wait, success rate, and regional availability for interactive and asynchronous work.
  • Which jobs are prioritized? Contract tiers, urgent human work, scheduled reports, and exploration may have different weights.
  • What are the data and resource boundaries? Do some tenants need a separate database, region, key, object store, or worker pool?
  • How are burst and long-term quotas measured? Submission rate, concurrent jobs, scanned bytes, CPU time, storage, or egress?
  • How do users see queueing and rejection? Estimated completion, retry guidance, quota explanation, and admin reporting should be explicit.

30-second answer

“I first define latency, success, concurrency, and data-isolation objectives per tenant and plan, separating submission quotas, running concurrency, and downstream budgets. The gateway authenticates the tenant, validates job size and an idempotency key, and writes to a durable queue. A scheduler uses per-tenant token buckets and weighted fair queues; noisy or regulated tenants can move to dedicated or shuffle-sharded worker pools. Database scans, caches, object storage, and egress are metered too. During overload, reject or defer low-priority work with truthful status and cancellation. Validate with cross-tenant authorization tests, noise injection, failure exercises, and per-tenant p99, blast-radius, and recovery assertions.”

Step-by-step answer

Step 1: Define resources and service objectives

Split a report into submission, queueing, query, generation, object write, and download. Define a measurable objective for each, such as submission p95, queue age, completion time, download success, and isolation. Budget CPU, memory, scans, connections, queue slots, object requests, and egress instead of naming only a worker count.

Step 2: Establish trusted tenant context

Tenant identity comes from authenticated credentials and server authorization, not a caller-supplied tenantId. Jobs, queue messages, queries, object paths, cache keys, and download tokens carry verified context. Limit fields, time windows, and maximum scans so a legitimate tenant cannot exhaust shared resources with a broad query.

text
authenticated principal
  -> authorize tenant and report definition
  -> assign quota class and priority
  -> enqueue {tenantId, taskId, costEstimate, deadline}
  -> every worker and storage call re-checks tenant scope

Step 3: Choose isolation tiers

Small tenants can share queues and workers with tenant quotas, concurrency caps, and fair scheduling. High-volume or regulated tenants can receive a dedicated queue, partition, database schema, encryption key, or worker pool. AWS shuffle sharding maps each tenant to a combination of workers so one worker failure affects fewer tenants; it bounds blast radius while retaining some sharing efficiency.

Step 4: Design quotas and fair scheduling

Use a submission token bucket for ingress bursts, a concurrency cap for in-flight jobs, and a cost budget for scanned bytes or CPU. A weighted fair queue or per-tenant virtual queue prevents one tenant from occupying every worker; within a tenant, sort by priority, deadline, and age. Rejection should be an explainable temporary-overload or quota state, not an invitation to retry forever.

ControlLimitsPurposeWhen exceeded
Submission bucketPer-tenant rate and burstBound ingress peaksDefer or return retryable status
In-flight capRunning jobsKeep one tenant from filling workersQueue and show estimated wait
Cost budgetScanned bytes, CPU, memoryStop broad work from hurting downstreamCancel, split, or narrow the range
Weighted fair queueTenant share of dispatchPrevent starvationWeighted round-robin with age priority
Dedicated shardHigh-volume or regulated tenantBound performance and failure impactMove to isolated pool or degrade

Step 5: Protect downstream resources

Start a job only after the scheduler has a database, cache, and object-storage budget. Use read replicas, time windows, and scan limits for reports; partition results by tenant and region and issue short-lived download authorization. If connections, caches, threads, and egress remain globally shared, fair ingress cannot prevent downstream starvation. Give critical dependencies their own concurrency limits and bounded queues.

Step 6: Handle bursts, failure, and recovery

Persist job state, the quota snapshot, an idempotency key, and cancellation. A crashed worker may retry, but result writes must be idempotent through a version or result key. When a dependency is unavailable, pause only affected classes, preserve queue age and estimates, and avoid a full retry storm. On recovery, ramp admission per tenant and priority while watching p99, errors, and quota rather than releasing all backlog at once.

Step 7: Scale, migrate, and validate

Scale from useful throughput, queue age, resource utilization, and tenant weights, not average CPU alone. When moving a tenant from a shared pool to a dedicated shard, preserve idempotent job state and result paths, switch gradually, and keep rollback. Test cross-tenant authorization, noise injection, one-worker failure, a slow database, queue recovery, cancellation, and large-tenant migration; each test checks that other tenants still meet their objectives.

High-quality sample answer

“I would split the report into submission, queueing, query, generation, storage, and download and define latency, success, and isolation objectives for each. Tenant identity comes from authenticated context and the server reauthorizes the report definition; a tenant field in the request is input, not a boundary. Jobs enter a durable queue with tenant, cost estimate, idempotency key, and deadline.

Small tenants share workers, but each has submission-rate, in-flight, scanned-byte, and storage quotas. Weighted fair scheduling with age priority prevents a month-end burst from occupying every worker. High-volume or regulated tenants can move to a dedicated queue, partition, or shuffle-sharded worker pool to reduce the blast radius of a worker failure or hot tenant. Database connections, caches, object storage, and egress receive tenant or class budgets too.

On overage I return an honest queue or temporary-overload state and support cancellation; clients cannot retry forever. Workers use idempotent result keys, while dependency failure pauses only affected work and recovery ramps by tenant and priority.

I would validate with cross-tenant access tests, a single-tenant burst, worker and database faults, queue replay, cancellation, and migration exercises. I would inspect each tenant’s p99, queue age, rejection rate, resource use, and leakage assertions. I would expand an isolation pool or adjust weights only after both small and high-tier tenants meet their objectives.”

Common mistakes

  • Trusting tenantId from the request → A caller can forge scope → Derive it from authentication and re-check downstream.
  • Giving every tenant one fixed worker → Idle capacity grows and failures still spread → Use risk tiers and combination shards when needed.
  • Limiting only submission rate → In-flight work still consumes downstream → Cap concurrency, cost, and dependencies too.
  • Using global averages for fairness → Small tenants’ tail latency disappears → Record p95, p99, queue, and rejection by tenant.
  • Retrying forever under overload → Retry amplification takes down the service → Return explicit state, idempotent retries, and shared budgets.
  • Adding workers without a database budget → The dependency becomes the bottleneck → Budget every resource layer end to end.
  • Releasing all backlog on recovery → A new peak forms → Use hysteresis and gradual admission.
  • Migrating without idempotent state → Jobs duplicate or results disappear → Use versions, result keys, and rollback.

Follow-ups and responses

Follow-up 1: How is shuffle sharding different from ordinary sharding?

Ordinary sharding usually places a tenant on one fixed shard, so a shard failure affects all tenants there. Shuffle sharding maps each tenant to a combination of workers with limited overlap, reducing the set affected by one worker failure. It adds capacity, rebalancing, and hot-tenant migration concerns.

Follow-up 2: Can a premium tenant bypass fair scheduling?

Give it an explicit contract or paid weight, but keep total capacity, isolation, and security boundaries. Reserve budget for premium work while recording a minimum service objective for ordinary tenants; “priority” must not mean unlimited preemption.

Follow-up 3: What if one report scans the entire database?

Estimate cost during parsing and planning, require a time range, cap bytes and concurrency, and split, defer, or reject work over budget. A larger database pool merely moves pressure into storage and is not a complete fix.

Follow-up 4: How do you prove there is no cross-tenant leak?

Build an access matrix from authenticated principals across APIs, queue replay, workers, caches, object paths, exports, and admin tools. Add negative tests so missing or forged context is denied by default, and assert tenant labels and content on real results.

Public sources

Related questions

Related interview tool

Use Solve for a system design answer

Clarify the requirements first, then move through scale, architecture, component choices, and trade-offs.

View the tool