CHAPTER 25
DEEP DIVE · FROM BASICS TO CODE

Understanding: System Design foundations

You can read this chapter before running the experiment. Then return to the live trace and match each concept to a real event.

FIRST, IN PLAIN LANGUAGE

System Design is not a contest to put fashionable technologies on a whiteboard. First identify what the product must do, how much traffic and data it serves, and what latency or loss it can tolerate. Then draw the request path, locate state and bottlenecks, and only then choose a load balancer, cache, database, queue, or additional replicas.

TECHNICAL FOUNDATION

System design connects functional requirements to quality attributes: latency, availability, durability, consistency, security, and cost. Numerical estimates establish an order of magnitude, an SLI measures observed behavior, an SLO sets a target, and architecture decisions redistribute specific risks. Horizontal scaling helps a stateless tier; state, hot keys, and downstream limits need separate treatment.

Why it mattersA senior or AI Product Engineer should explain not only the happy path but also a tenfold spike, a dependency failure, a duplicate request, replica lag, and restoration from backup. A good design makes trade-offs explicit and testable.
WHERE THE WORK RUNS
01REQUIREMENTSuse cases · SLO · constraints
02DATA FLOWAPI · state · sync/async
03SCALERPS · bytes · concurrency
04FAILUREStimeouts · retries · recovery
01 · GLOSSARY

Terms used in this experiment

Understand the words first, then the execution order.

01

System Design

The process of designing system components, data, and interactions against explicit requirements, constraints, and failure modes.

02

Functional requirement

A user capability or business rule, such as uploading a document, placing an order, or finding a publication.

03

Quality attribute

A measurable property such as latency, availability, durability, consistency, security, or cost.

04

SLI / SLO / SLA

An SLI is a measurement, an SLO is an internal target for it, and an SLA is a contractual promise with consequences.

05

Throughput and concurrency

Throughput is completed work per unit of time; concurrency is work simultaneously present in the system.

06

Load balancer

A component that distributes traffic across healthy replicas; it does not scale a shared database or shared state.

07

Horizontal scaling

Adding service instances. It is easier for stateless APIs and harder for stateful components and background work.

08

Backpressure

A mechanism that prevents a producer from feeding work indefinitely faster than a consumer can process it.

09

Replication and partitioning

Replication creates copies for availability or reads; partitioning divides a data set across nodes.

10

RPO / RTO

RPO bounds acceptable data loss in time, while RTO bounds acceptable service recovery time.

11

Idempotency

Repeating an operation with the same key does not create a second business effect, which matters after timeouts and retries.

02 · MECHANICS

What happens step by step

Each step maps to an observable runtime state.

  1. 01
    Fix the scope and primary use cases

    Name actors, reads, writes, data, and the capabilities deliberately excluded from this design.

  2. 02
    State measurable objectives

    For example, 99% of reads below 300 ms over 28 days, under 0.1% errors, and an RPO of five minutes.

  3. 03
    Estimate the order of magnitude

    Use DAU, actions per user, peak factor, and payload size to estimate average and peak RPS, storage, and bandwidth.

  4. 04
    Draw the request and data flow

    Show the client, edge or load balancer, API, cache, primary database, replicas, queue, workers, and external dependencies.

  5. 05
    Assign ownership of state

    For every record, decide its source of truth, access key, consistency boundary, lifecycle, and recovery method.

  6. 06
    Walk through failure modes

    Consider timeout, duplicate, partial failure, overload, stale cache, replica lag, zone loss, and poison messages.

  7. 07
    Prove the design with measurements

    Use load tests, p95 and p99, saturation, queue depth, error rate, fault injection, and restore drills.

03 · CONTEXT

Where the result needs context

These details explain why similar code can sometimes produce a different trace.

01

An average hides the peak

Daily average RPS can look small while a campaign creates a tenfold burst within one minute.

02

Caching changes correctness

It reduces load and latency but introduces TTL, invalidation, stampede, and stale responses.

03

Retries multiply overload

Without a deadline, exponential backoff, and jitter, retries amplify an outage into a retry storm.

04

A queue does not create capacity

It absorbs a short burst; if input remains above processing rate, backlog and waiting time grow without bound.

05

A replica may lag

Read replicas scale reads, but read-after-write may require the primary or a session-consistency mechanism.

06

Multi-region is not a free checkbox

It adds network latency, conflict handling, failover complexity, cost, and a need for regular recovery testing.

01
Theory

First understand which parts of Node participate in execution.

02
Simplified code

Then remove instrumentation and focus on the central mechanism.

03
Runtime code

Finally match the model to the code that produces the live trace.

04 · Simplified code

A minimal model without instrumentation

src/demos.js · educational snippetJavaScript
const requestsPerDay = dau * actionsPerUser;
const averageRps = requestsPerDay / 86_400;
const peakRps = averageRps * peakFactor;

const replicas = Math.ceil(
  peakRps / (rpsPerReplica * targetUtilization),
);
05 · Runtime code

The complete code executed by the scenario

This is not an alternative example: these are the functions and files used by the Run button.

ACTUAL SOURCE

The source is generated from the real server function. Scenarios using a child process or Worker include every participating file.

src/system-design-lab.js
scenario79 lines
const SECONDS_PER_DAY = 86_400;
const BITS_PER_BYTE = 8;

export function estimateCapacity({
  dailyActiveUsers,
  actionsPerUser,
  peakFactor,
  averagePayloadKb,
  rpsPerReplica,
  targetUtilization,
}) {
  const requestsPerDay = dailyActiveUsers * actionsPerUser;
  const averageRps = requestsPerDay / SECONDS_PER_DAY;
  const peakRps = averageRps * peakFactor;
  const peakMegabitsPerSecond =
    (peakRps * averagePayloadKb * 1024 * BITS_PER_BYTE) / 1_000_000;
  const usableRpsPerReplica = rpsPerReplica * targetUtilization;
  const servingReplicas = Math.max(
    1,
    Math.ceil(peakRps / usableRpsPerReplica),
  );

  return {
    requestsPerDay: Math.round(requestsPerDay),
    averageRps: Number(averageRps.toFixed(1)),
    peakRps: Number(peakRps.toFixed(1)),
    peakMegabitsPerSecond: Number(peakMegabitsPerSecond.toFixed(1)),
    servingReplicas,
    replicasWithOneFailure: servingReplicas + 1,
  };
}

export async function systemDesignCapacity(emit) {
  const assumptions = {
    dailyActiveUsers: 120_000,
    actionsPerUser: 35,
    peakFactor: 8,
    averagePayloadKb: 6,
    rpsPerReplica: 250,
    targetUtilization: 0.6,
  };

  emit(
    'requirements',
    'input',
    'Цель: выдержать пользовательский пик и потерю одной API replica',
  );
  emit(
    'capacity',
    'assumption',
    `Предположения: DAU=${assumptions.dailyActiveUsers}, действий/пользователь=${assumptions.actionsPerUser}, peak factor=${assumptions.peakFactor}`,
  );

  const estimate = estimateCapacity(assumptions);

  emit(
    'capacity',
    'result',
    `Среднее=${estimate.averageRps} RPS; проектный пик=${estimate.peakRps} RPS; egress≈${estimate.peakMegabitsPerSecond} Mbit/s`,
  );
  emit(
    'api-tier',
    'headroom',
    `Нужно ${estimate.servingReplicas} serving replicas при 60% target utilization; N+1=${estimate.replicasWithOneFailure}`,
  );
  emit(
    'database',
    'constraint',
    'Расчёт API не доказывает capacity базы, connection pool, cache или downstream',
  );
  emit(
    'validation',
    'next-step',
    'Следующий шаг: load test с реальным traffic mix и проверкой p95/p99, ошибок и saturation',
  );

  return estimate;
}

The application instruments its own live trace: rows and timestamps are recorded by real emit(...) calls, while the scenario supplies source and lane labels. This is not a V8/libuv profiler or a direct view of their internal queues. await and Promise keep the HTTP stream open until the scenario completes.

06 · RECIPES

Practical patterns worth keeping nearby

Compare the goal, code, and caveats instead of memorizing syntax without a model.

01

Rough capacity budget

Estimate peak RPS before selecting a replica count.

const requestsPerDay = dau * actionsPerUser;
const averageRps = requestsPerDay / 86_400;
const peakRps = averageRps * peakFactor;

const replicas = Math.ceil(
  peakRps / (rpsPerReplica * targetUtilization),
);
  • Label every input as an observed fact or an assumption.
  • Validate p95, p99, and errors, not just RPS.
  • The database, connection pool, or downstream may saturate before API CPU.
02

Idempotent write

Survive a repeated POST after a client timeout.

INSERT INTO payments (idempotency_key, order_id, amount)
VALUES ($1, $2, $3)
ON CONFLICT (idempotency_key) DO NOTHING
RETURNING id, status;
  • The idempotency key needs a UNIQUE constraint.
  • A duplicate should return the previous outcome.
  • Define the key scope and retention period.
03

Deadline and bounded retry

Avoid waiting forever or creating a retry storm.

const response = await fetch(url, {
  signal: AbortSignal.timeout(800),
});

if (response.status >= 500 && attempt < 2) {
  await sleep(backoffWithJitter(attempt));
}
  • Not every operation is safe to retry.
  • An overall deadline matters more than unrelated timeouts.
  • 429 and Retry-After require an explicit policy.
04

Bounded queue consumer

Protect the database from unbounded worker concurrency.

new Worker('previews', renderPreview, {
  connection,
  concurrency: 8,
  limiter: { max: 40, duration: 1_000 },
});
  • Concurrency limits one worker process.
  • The rate limit must account for every replica sharing the downstream.
  • Alert on queue depth and age of the oldest job.
05

Readiness is not liveness

Remove an unready Pod from traffic without needless restarts.

@Get('/ready')
ready() {
  return this.dependencies.canServeTraffic()
    ? { status: 'ready' }
    : (() => { throw new ServiceUnavailableException(); })();
}
  • Readiness asks whether the instance should receive new traffic.
  • Liveness should detect a state that a restart can repair.
  • Deep checks of every dependency can create load themselves.
07 · PRODUCTION CASES

How a learning mistake becomes an incident

A realistic service: the original code, observable failure, corrected implementation, and why the correction works.

CASE 01

Report export ran inside the HTTP request

A Nest endpoint builds a heavy report and waits for external storage, so a spike holds every HTTP connection open.

INCIDENT CONTEXT

Latency reaches the timeout, clients repeat the POST, the same export runs more than once, and the database pool is exhausted.

BEFOREPROBLEMATIC IMPLEMENTATION
@Post(':id/export')
async export(@Param('id') id: string) {
  const rows = await this.reports.loadAllRows(id);
  const file = await this.renderer.render(rows);
  return this.storage.upload(file);
}

A long CPU and I/O chain is tied to the request lifetime and lacks an idempotency key, bounded concurrency, and a separate capacity budget.

AFTERCORRECTED IMPLEMENTATION
@Post(':id/export')
@HttpCode(202)
async export(
  @Param('id') id: string,
  @Headers('idempotency-key') key: string,
) {
  const job = await this.exports.enqueueOnce({ reportId: id, key });
  return { jobId: job.id, statusUrl: '/exports/' + job.id };
}

@Processor('exports')
export class ExportWorker extends WorkerHost {
  async process(job: Job<ExportJob>) {
    return this.exports.buildAndStore(job.data);
  }
}

HTTP acknowledges quickly, a durable queue buffers the spike, a unique key deduplicates retries, and worker concurrency protects the database and storage.

FUNCTIONS AND CONSTRUCTS

What the unfamiliar calls from both code samples actually do.

@HttpCode(202)
States that work was accepted but is not complete; the client needs a separate URL or event to observe the outcome.
@Headers()
Reads the idempotency key from an HTTP header; the server must validate its format, scope, and user ownership.
enqueueOnce()
An application operation that atomically creates a job or returns the existing outcome for the same key.
WorkerHost.process()
The Nest BullMQ job handler whose concurrency, timeout, retries, and cleanup are configured outside the controller.
WHY THE CORRECTION WORKS

An asynchronous boundary helps when the result is not required in the same response; the queue still needs limits, a retry policy, and observable waiting time.

WHAT PRODUCTION SHOWED
  • HTTP acceptance p95 and the ratio of 202 responses to errors.
  • Queue depth, oldest-job age, retries, and dead-letter jobs.
  • Database pool saturation and export completion time.
CASE 02

A read replica was added without a consistency rule

Immediately after creating an article, the UI fetches it, while random read balancing may select a lagging replica.

INCIDENT CONTEXT

The user intermittently receives 404 for a newly created object even though the write committed successfully on the primary.

BEFOREPROBLEMATIC IMPLEMENTATION
@Get(':id')
findOne(@Param('id') id: string) {
  return this.randomReplica.query(
    'SELECT * FROM articles WHERE id = $1',
    [id],
  );
}

The replica is selected without considering replication lag or the user-facing read-after-write expectation.

AFTERCORRECTED IMPLEMENTATION
@Get(':id')
findOne(
  @Param('id') id: string,
  @Headers('x-read-token') token?: string,
) {
  const db = token && this.readTokens.isFresh(token)
    ? this.primary
    : this.replica;

  return db.query(
    'SELECT * FROM articles WHERE id = $1',
    [id],
  );
}

A short-lived token routes related post-write reads to the primary, while ordinary scalable reads stay on a replica.

FUNCTIONS AND CONSTRUCTS

What the unfamiliar calls from both code samples actually do.

x-read-token
A short-lived opaque marker connecting a read to a recent user write without exposing a database position.
isFresh()
Validates signature, scope, and expiry; this is a consistency-routing policy, not synchronous replication.
this.primary
A connection pool to the node that accepted the write and therefore sees the commit before asynchronous replicas.
SQL $1
A positional SQL parameter that separates the user value from query text and prevents SQL injection.
WHY THE CORRECTION WORKS

Replication is more than topology: the product must define where stale reads are acceptable and where read-after-write consistency is required.

WHAT PRODUCTION SHOWED
  • Replication lag in seconds and bytes.
  • The fraction of reads temporarily routed to the primary.
  • Not-found responses immediately following a successful write.
08 · DO NOT CONFUSE

Common misconceptions

The myth is on the left; the accurate model is on the right.

MYTH

System Design means drawing more boxes.

ACTUALLY

A diagram is useful only with requirements, numbers, state ownership, failure modes, and testable trade-offs.

MYTH

A load balancer makes the whole system scalable.

ACTUALLY

It distributes traffic, but a shared database, lock, hot key, or external API may remain the bottleneck.

MYTH

Two replicas guarantee availability.

ACTUALLY

The same bad deployment, shared zone, corrupt data, or broken failover can remove both copies.

MYTH

A backup means the data is safe.

ACTUALLY

You need measurable RPO and RTO, independent storage, integrity checks, and rehearsed restores.

MYTH

Autoscaling fixes every overload.

ACTUALLY

Scaling arrives late and may overload the database faster without admission control and backpressure.

09 · SELF-CHECK

Explain it in your own words

If you can explain the answer without quoting documentation, your mental model is starting to take shape.

  1. Which functional and non-functional requirements do you ask for before drawing an architecture?
  2. How can DAU produce a rough peak RPS, and why is that still insufficient?
  3. How do SLI, SLO, and SLA differ?
  4. Why can a queue smooth a burst but not fix permanently insufficient capacity?
  5. Where does the system need an idempotency key, and what is its scope?
  6. When is a stale read acceptable, and when must a read use the primary?
  7. How do you prove that a backup meets its RPO and RTO?
  8. Which signals reveal a bottleneck before user-facing timeouts?