Networking & Protocols
Defense-in-depth architecture and attack economics
You deployed a CDN, rate limiting, and a WAF. Then the attacker switches to cache-miss HTTP floods targeting your most expensive database query, spreading across 10,000 IPs each under your per-IP limit. No single defense catches it. You need to understand how the layers interact and when to escalate to humans.
Defense-in-depth architecture: the full stack. No single defense stops all attacks. The layered approach:
- Anycast edge scrubbing — every CDN PoP is an active scrubbing center. Attacks are ingested at the nearest PoP rather than concentrated on the origin. Combined capacity across 330+ Cloudflare PoPs exceeds 37 Tbps. A 10 Gbps attack becomes a rounding error spread across many nodes.
- Stateless L3/L4 rate limits — per-ASN, per-prefix rate limits drop obvious amplification sources and SYN flood sources before TCP state is created.
- WAF at the edge — detects application-layer patterns (SQLi, XSS, bot fingerprints). Running at PL2 (balanced false-positive/coverage), not PL4 (paranoid) for a public API.
- Token bucket per IP + per user — stops obvious botnets and authenticated user abuse.
- Adaptive concurrency limiting at origin — when in-flight requests exceed capacity threshold, reject new requests with fast 503. Service remains stable; users get errors instead of timeouts.
- Observability and human escalation — when automated defenses fail, on-call engineers add custom rules.
| Layer | What it stops | What it misses |
|---|---|---|
| Anycast edge | Volumetric floods (Gbps-scale) | Intelligent low-rate attacks per IP |
| Stateless L3/L4 rate limits | Amplification, SYN floods | HTTP-level attacks on valid ports |
| WAF (PL2) | Known attack signatures, bot patterns | Zero-days, obfuscated payloads, business-logic abuse |
| Rate limit (per IP/user) | Obvious botnets, auth abuse | Distributed botnets with residential proxies |
| Adaptive concurrency | Distributed overload, cache-miss floods | Attacks below the overload threshold |
| mTLS | Lateral movement inside the network | External-facing attack vectors |
mTLS in service meshes with SPIFFE. Istio or Linkerd deploy sidecar proxies on every pod. The control plane (Istiod) runs a SPIFFE-compatible certificate authority. At startup, each sidecar receives a short-lived certificate (1–24 hours). Certificates rotate via SDS (Service Workload API) push — no sidecar restart needed. Every service-to-service call: (1) mTLS handshake (20–50 ms overhead per new connection on older hardware), (2) encrypted payload, (3) both sides verify certificates. Prevents lateral movement if the pod network is compromised. Cost: cert rotation adds operational complexity and monitoring burden (expired cert = infrastructure incident, not a bug).
Protocol/state-exhaustion in depth. SYN floods: each SYN allocates a half-open connection slot in the server’s backlog. When backlog overflows, the server drops new SYN packets from legitimate clients. SYN cookies encode connection state as a cryptographic cookie in the ISN — no memory allocated; legitimate clients reply with a valid ACK that decodes the cookie. ACK floods: RST rate limiting (limit RSTs per second) and firewall suppression of unmatched ACKs. TCP RST injection (on-path MITM): RFC 5961 challenge-ACK forces the attacker to know the exact sequence number rather than just be in-window.
Rate limiting internals: distributed systems complexity. Token bucket with Redis backing: T = min(C, T + R * delta_time). Distributed: each request atomically INCR key; EXPIRE key window. At 100k req/sec, Redis adds 0.5–1 ms per request = 50–100 ms total added latency. Mitigation: local per-server counter + periodic Redis sync (accepts slight inaccuracy, cuts to microsecond decisions). HyperLogLog for approximate rate limiting: ~1.6 kB per sketch, ~2% error, suitable for ASN-level or IP-range limits.
Adaptive concurrency: load shedding formula. Track in-flight requests Q. Set max_queue threshold. For new requests, survival probability = max(0, 1 - Q / max_queue). Accept the request with that probability. This creates graceful degradation: at 50% overload, 50% of new requests are rejected; at 100% overload, all new requests are rejected. Users see fast 503s instead of queue timeouts (which can be 30+ seconds). Circuit breakers reject requests to backends with recent failure rates above threshold — preventing cascading failure across the entire service graph.
Trace a sophisticated attack using amplification + application-layer tactics.
WAF anomaly scoring during an attack
2026-05-15 14:23:00 | requests=45000/sec | score-p50=0.5 | score-p95=3.2 | score-p99=8.1
2026-05-15 14:24:00 | requests=120000/sec | score-p50=6.1 | score-p95=14.2 | score-p99=28.5
2026-05-15 14:25:00 | requests=450000/sec | score-p50=12.3 | score-p95=19.8 | score-p99=32.1
2026-05-15 14:26:00 | blocked=380000 | allowed=70000 | score-threshold=5 The WAF is scoring traffic and blocking at threshold=5. What is happening in this timeline, and what should the operator do?
Attack economics. Attacker cost: ~$50–500/month for a botnet service capable of 10 Gbps sustained. Origin defense cost: ~$500/month CDN bill for 10 Gbps sustained traffic. If the attacker can generate 100 Gbps, the origin cannot match. But a CDN with global PoPs ingests 100s of Tbps and spreads the cost across millions of customers — per-customer cost is tiny. The economics favor the attacker only if you defend alone. Sharing infrastructure (CDN) is the answer: you cannot out-scale a botnet; you can make the attack economically unattractive by making it fail.
Observability at attack time. Key signals: (1) request rate per second — 10x normal is suspicious; (2) geographic distribution of sources — all from one ASN is suspicious; (3) anomaly score p99 — normal users score <1, attack traffic scores >10; (4) cache-hit rate — attack traffic targeting unique parameters shows sudden drop. Alert thresholds: request rate 10x baseline, anomaly score p99 spike, source-IP entropy drop (100 IPs instead of 100,000), or cache-hit rate drop below 80% during a traffic spike. Human escalation if attack persists >5 minutes or exceeds 50 Gbps.
An e-commerce service is under sustained application-layer attack. You must pick a defense strategy.
Design the DDoS defense architecture for a 100 Gbps-capable video CDN serving global users. The CDN operates 50 PoPs in 30 countries.
- Absorb 100+ Gbps attacks at the edge without exceeding 10% of any PoP's capacity.
- Defend against volumetric (L3/L4), protocol (SYN floods), and application-layer (HTTP floods) attacks.
- Maintain p50 latency < 50 ms and p99 < 200 ms for legitimate users during attack.
- Detect and block new attack patterns within 60 seconds.
- Anycast ingestion distributes attack load; no single point of failure.
- Stateless L3/L4 filtering catches 80% of volumetric attacks with minimal CPU.
- WAF at PL2 balances detection vs false positives for a public API.
- Adaptive limiting rejects on overload, not preemptive IP blocking.
- Human escalation at 5 minutes catches novel attack patterns.
- Measure false-positive rate and origin load continuously.
Why this works
Why is adaptive concurrency limiting the preferred answer for a Black Friday e-commerce attack, not WAF PL4? WAF PL4 makes preemptive decisions at the request level based on content patterns. If those patterns are wrong (5% false positive), you block paying customers. Adaptive concurrency limiting makes reactive decisions at the system level based on actual load. It never blocks a request preemptively — it only rejects when the system is already overloaded (which is bad regardless of attack). The tradeoff: adaptive limiting accepts that some attack requests go through until the system hits capacity, then rejects everything equally. That is acceptable when the alternative is blocking 5% of legitimate Black Friday customers.
Your WAF is at Paranoia Level 2 and attacks get through (only 70% blocked). You raise it to PL4. Legitimate customers now complain (5% false positives). What is a better approach?
- 01Why is adaptive concurrency limiting preferable to WAF PL4 for a high-traffic production service under attack?
- 02During a Rapid Reset attack (CVE-2023-44487), why does the attack bypass HTTP/1.1-only rate limiting?
- 03What metrics should an on-call engineer monitor during a DDoS attack and what thresholds signal escalation?
Defense-in-depth against DDoS requires stacking multiple layers because no single defense stops all vectors. Anycast edge absorption distributes volumetric attacks across 330+ global PoPs; stateless L3/L4 filters drop amplification and SYN floods before they consume connection state; WAF at PL2 detects known application-layer patterns with tolerable false positives; adaptive concurrency limiting at the origin rejects on overload rather than preemptive IP blocking. Attack economics favor defenders only when using shared CDN infrastructure — a botnet generating 100 Gbps for $500/month is defeated by a CDN that amortizes defense capacity across millions of customers. When automated defenses fail, observability (request rate, anomaly scores, cache-hit rate, source-IP entropy) gives on-call engineers the signal they need to add custom rules within the 60-second escalation window.
appears again in258
- Why GraphQL gets N+1junior
- DataLoader mechanics: tick-boundary batchingmiddle
- Batch function contracts: ordering, shapes, errorsmiddle
- Federation and lookahead: batching beyond DataLoadermiddle
- Query complexity defences: depth, cost, persisted queriesmiddle
- Senior GraphQL API: scheduling contract, tenant isolation, observabilitysenior
- The journey of a request: seven stops from socket to responsejunior
- Accept and parse: from kernel queue to a typed requestmiddle
- Routing and middleware: choosing what runs, and in what ordermiddle
- Handler and response: from business logic to bytes on the wiremiddle
- Streaming and backpressure: when the client reads slower than you writesenior
- Timeouts and tail latency: budgets, deadlines, and the fan-out trapsenior
- Middleware and DI: the two patterns that shape every backendjunior
- Writing middleware: signatures, next(), and the three framework modelsmiddle
- Inversion of control: how dependencies reach a classmiddle
- DI scopes and lifecycles: singleton, request, transientmiddle
- DI as a testing seam: fakes, mocks, and the boundary that matterssenior
- DI containers in production: resolution graphs, circular deps, and when not tosenior
- Blocking vs non-blocking I/O: two ways to waitjunior
- The event loop: one thread, ordered phasesmiddle
- What blocks the loop: CPU work and sync callsmiddle
- Offloading CPU work: worker threads and the libuv poolmiddle
- Backpressure and bounded concurrencysenior
- Throughput under load: tail latency and saturationsenior
- Why pool: the cost of creating a connectionjunior
- Pool sizing: why bigger is not fastermiddle
- Acquisition and timeouts: the wait queue is the real latency dialmiddle
- Why idempotency: making retries safejunior
- Server-side state machine: four states of an idempotency keymiddle
- Retry strategies: backoff, jitter, and thundering herdmiddle
- Outbox and inbox: effectively-once across the dual-write boundarymiddle
- Concurrency and cache architecture for idempotency at scalesenior
- Observability, production failures, and global-scale designsenior
- The event loop: one thread, three queuesjunior
- Tasks, microtasks, and scheduler.yield()middle
- Timer accuracy, throttling, and idle workmiddle
- Microtask starvation, Long Tasks, and LoAFsenior
- Node.js event loop: phases, nextTick, and loop lagsenior
- React, Vue, and INP observability in productionsenior
- The render pipeline: six stages from bytes to pixelsjunior
- Stage costs and the renderer process modelmiddle
- Invalidation, dirty bits, and containmiddle
- Compositor layers: promotion, overlap, and GPU memorymiddle
- DevTools flame strip and the frame lifecyclemiddle
- Layout thrash: forced synchronous layoutsenior
- BeginMainFrame, compositor-driven animations, and GPU memorysenior
- Production observability: LoAF, INP, and the full attack surfacesenior
- What V8 is and why performance varies 100×junior
- V8''''s four-tier JIT pipeline and profile-guided tieringmiddle
- Hidden classes, transition trees, and memory layoutmiddle
- Inline caches, IC states, and deoptimizationmiddle
- Orinoco GC: parallel scavenger, concurrent marking, and write barriersmiddle
- TurboFan''''s speculative engine and the deopt-loop trapsenior
- V8 in production: isolates, pointer compression, and real failuressenior
- Service worker lifecycle and cache strategiesmiddle
- Service worker edge cases: version skew, durability, and navigation trapssenior
- What the reconciler does: render vs commitjunior
- The fiber object and the double-buffer treemiddle
- Render phase purity and commit phase sub-stepsmiddle
- Reconciliation: diffing heuristics and the key trapmiddle
- Priority lanes, time-slicing, and useTransitionmiddle
- Bailout, memoisation, and tearingsenior
- React Profiler, the Compiler, and production observabilitysenior
- Rendering strategies: SSG, SSR, ISR, streaming, and hydrationjunior
- SSG, SSR, ISR, streaming, and RSC — how each worksmiddle
- Hydration cost: selective, progressive, islands, resumabilitymiddle
- Hydration mismatch: causes, detection, and the determinism rulesenior
- RSC, per-route strategy, and production observabilitysenior
- Core Web Vitals: what LCP, INP, and CLS measurejunior
- LCP: four phases, one dominant costmiddle
- INP: input delay, processing, presentationmiddle
- CLS: why layout shifts happen and how to stop themmiddle
- Lab vs field: why the two disagree and how to use eachmiddle
- Metric tradeoffs, RUM attribution, and the CI+field loopsenior
- The full picture: URL to LCP to INP as a relay racejunior
- Eight layers traced: from the service worker to the second navigationmiddle
- Five canonical breaks: where production reliably diessenior
- The three-track method: reading traces and building a monitored systemsenior
- What is a cache stampede and why it makes things worsejunior
- Lock and single-flight: bounding concurrent rebuildsmiddle
- XFetch: coordination-free probabilistic early expirationmiddle
- Stale-while-revalidate and CDN request coalescingmiddle
- Detecting stampedes and designing TTL for productionmiddle
- Metastable failure, fencing tokens, and production postmortemssenior
- What a relation is: tables, rows, keys, and constraintsjunior
- Constraints, keys, and Postgres data typesmiddle
- Normal forms, denormalization, and why schemas stickmiddle
- JSONB, arrays, and when a side table winsmiddle
- Heap storage, TOAST, and column alignmentsenior
- Schema integrity: deferral, versioning, and production failure modessenior
- Relational vs document, wide-column, graph, and key-valuesenior
- What an index is and how it speeds up queriesjunior
- The leading-column rule and composite index designmiddle
- Partial, expression, and covering indexesmiddle
- Index types: GIN, GiST, BRIN, Hash, Bloom, and HOT updatesmiddle
- Index-only scans, the Visibility Map, and INCLUDEsenior
- Production failure modes and the index audit playbooksenior
- Index design exercise: full-text search strategysenior
- EXPLAIN and execution plans: what the planner decides and whyjunior
- Scan types: Seq, Index, Bitmap, Index-Onlymiddle
- Join algorithms and the row-estimate cascademiddle
- pg_statistic, ANALYZE, and production observabilitymiddle
- Extended statistics: fixing correlated-column estimate failuressenior
- Plan cache, cost-constant tuning, and planner internalssenior
- Production failure modes and plan stabilitysenior
- MVCC: why readers and writers never wait for each otherjunior
- Row versions and snapshots: the on-disk mechanicsmiddle
- HOT updates and isolation levels: what you gain and what you paymiddle
- Vacuum and bloat: keeping the storage tax boundedmiddle
- CLOG, XID wraparound, and MultiXact: deep visibility internalssenior
- SSI internals and production autovacuum tuningsenior
- Real-world MVCC failures, deployment patterns, and distributed snapshotssenior
- Connection pools: amortising the cost of a Postgres backendjunior
- PgBouncer session, transaction, and statement modesmiddle
- Pool sizing: the (cores × 2) + spindles formula and the two-layer stackmiddle
- Pool exhaustion and idle-in-transaction: the 3 AM failure modemiddle
- Migrating to transaction mode: rollout playbook and PgBouncer 1.21 prepared statementsmiddle
- The Postgres process model and why raising max_connections degrades throughputsenior
- Pooler landscape 2026, serverless connection storms, and the full failure-mode taxonomysenior
- What a schema migration is and why it replaces ad-hoc DDLjunior
- ADD COLUMN: instant in PG 11+ vs rewrite in older Postgresjunior
- The lock-queue failure mode: why instant DDL can freeze the databasemiddle
- Safe DDL patterns: NOT VALID, CONCURRENTLY, and unsafe-op fixesmiddle
- Expand-contract: zero-downtime for breaking schema changesmiddle
- Advisory locks, migration tools, and deploy coordinationsenior
- Migration failure taxonomy and production disciplinesenior
- Why sharding exists: the single-Postgres ceilingjunior
- Shard-key selection: hash, range, list, and directory strategiesmiddle
- Partitioning vs sharding: same word, two different thingsmiddle
- Co-location and Citus: the invariant that makes sharding usablemiddle
- The hot-shard failure mode: detection, isolation, and durable policymiddle
- Schema-based sharding and multi-tenancy alternativessenior
- Online resharding, 2PC, and the operational cost of shardingsenior
- The seven acts: from CREATE TABLE to Citusjunior
- Acts 1–3 in depth: schema, indexes, and planner statisticsmiddle
- Acts 4–6 in depth: MVCC bloat, connection pooling, and safe migrationsmiddle
- Act 7 in depth: sharding, co-location, and the seven-tier tradeoff cascademiddle
- Observability, anti-patterns, and production triagesenior
- Raft roles, terms, and why majority quorums prevent split brainjunior
- How Raft replicates a log entry and decides it is safe to commitmiddle
- Raft leader election: timeouts, voting rules, and the four safety propertiesmiddle
- Raft in the real world: partitions, slow disks, and client routingmiddle
- Raft extensions: pre-vote, learners, snapshots, and linearizable readssenior
- Raft in production: membership changes, Multi-Raft, and observabilitysenior
- Where data fetching happens — and why it decides LCPjunior
- Fetch waterfalls — diagnosis and the Promise.all curemiddle
- React Server Components and Suspense streamingmiddle
- Client-side cache: TanStack Query, SWR, and stale-while-revalidatemiddle
- LCP, prefetch, and race conditions in interactive fetchingmiddle
- Senior internals: RSC payload, caching layers, and production failure modessenior
- What the three signals are: logs, metrics, and tracesjunior
- Metrics and cardinality: the cost model of a time-series databasemiddle
- Logs and volume: the cost model of structured loggingmiddle
- Traces and sampling: the cost model of distributed tracingmiddle
- Join keys and exemplars: making the three signals composemiddle
- Observability 2.0: wide events and the cost shiftsenior
- Failure modes and engineering practice: cardinality budgets, PII, and samplingsenior
- Why structured logs exist: the diary vs the spreadsheetjunior
- The production log schema: fields every line must carrymiddle
- Log levels and alert routingmiddle
- Sampling strategies and log costmiddle
- PII redaction and log injectionsenior
- Trace context propagation in logssenior
- OTel Logs Data Model and audit logs as a subsystemsenior
- OTel signals, Semantic Conventions, and the OTLP wire formatmiddle
- Auto-instrumentation and manual spans: the 80/20 of OTelmiddle
- The OTel Collector: receivers, processors, exporters, and deployment patternsmiddle
- Sampling strategies: head, tail, and parent-basedmiddle
- Vendor neutrality, eBPF instrumentation, the Operator, and browser/serverless OTelsenior
- Operating the OTel Collector: reliability, version skew, failure modes, and governancesenior
- RED and USE: two checklists, one triage disciplinejunior
- Instrumenting RED in Prometheus: counters, histograms, and cardinality disciplinemiddle
- USE on Linux: CPU, memory, disk, network, and PSImiddle
- Golden signals, dashboard layout, and service mesh auto-REDmiddle
- Cardinality as a cost driver: labels, PII, exemplars, and samplingmiddle
- Native histograms, SLO tie-in, and production failure patternsmiddle
- SLI, SLO, and the error budget: reliability by the numbersjunior
- Choosing SLIs and SLO targets: ratios, not feelingsmiddle
- Multi-window multi-burn-rate alerting: why AND beats ORmiddle
- Error budget policy, latency SLOs, and composite journeysmiddle
- Iceberg SLIs, composite SLO math, and SLA vs SLOsenior
- Production SLO failures, self-observability, security, and the big picturesenior
- Flame graphs: reading the picture that shows where time goesjunior
- Sampling vs instrumentation profiling: why 99 Hz wins in productionmiddle
- Profile types: CPU, memory, off-CPU, mutex — which one to reach formiddle
- Continuous profiling: always-on flame graphs with eBPF and trace-id correlationmiddle
- How flame graphs are built from samples, and the production workflows that use themmiddle
- Linux perf, eBPF internals, PGO, and the limits of samplingsenior
- Profiling in production: security, war stories, OTel profiles, and the infrastructure designsenior
- The debugging funnel: SLO → RED → trace → profilejunior
- OTel architecture: one SDK, four signals, one wire formatmiddle
- Cost discipline: keeping observability under 5% of infra spendmiddle
- The incident loop: from pager to postmortem to preventionmiddle
- Scale, security, and the ROI of observable systemssenior
- Why profile first: measure where time actually goesjunior
- Amdahl''''s law and self-time: the ceiling on every speedup you can shipmiddle
- The measurement loop: microbench, macrobench, prod profile, observer effectmiddle
- Reading flame graphs: shapes, per-language profilers, and the 60-second scanmiddle
- Statistical baselines: why one run is not a measurementmiddle
- Profiler history and microbenchmark pitfalls: Knuth to GWPsenior
- Hardware counters, cold-start profiles, and profile securitysenior
- Continuous profiling at scale: costs, CI gates, trace correlation, and anti-patternssenior
- What makes a hot path: symptom vs causejunior
- Five shapes of hotspot: CPU, alloc, cache, lock, syscallmiddle
- Reading parent and child chains: where to apply the fixmiddle
- JIT deopt, the fix-and-verify loop, and PR-time profilingmiddle
- Hardware counters and Intel TMA: sub-category diagnosissenior
- False sharing and native-bridge hot pathssenior
- Hot paths in production: security, tail latency, and tooling lineagesenior
- Memory hierarchy: why the same O(N) loop can be 17x slowerjunior
- Row-major vs column-major: access order and the 9x gapjunior
- Cache lines, struct layout, and false sharingmiddle
- Branch prediction and branchless codemiddle
- SIMD, SoA vs AoS, and memory bandwidthmiddle
- Hardware prefetcher, TLB, and memory-level parallelismsenior
- Cache-oblivious algorithms, PGO, and production failuressenior
- GC basics: what the runtime taxes you forjunior
- GC algorithms: generational, concurrent, and per-runtimemiddle
- GC tradeoffs: pause, throughput, heap — and object poolingmiddle
- GC tuning: pacing, heap shape, and allocation observabilitymiddle
- GC internals: tri-color invariant, write barriers, and per-runtime deep-divessenior
- GC in production: observability, security, edge cases, and fleet governancesenior
- N+1: one logical operation, many round-tripsjunior
- Fix families: JOIN, IN, preload, and DataLoadermiddle
- Detecting N+1: query logs, APM traces, and CI gatesmiddle
- DataLoader: batching across resolver treesmiddle
- Cross-protocol N+1: HTTP fan-out and Redis MGETmiddle
- N+1 at scale: pool exhaustion, plan changes, and denormalisationsenior
- Batching: amortize fixed cost per operationjunior
- The batching window: size and wait timemiddle
- Batching in Kafka and Postgresmiddle
- io_uring and observability of batchingmiddle
- From Nagle to io_uring: evolution of batchingmiddle
- Backpressure, failure isolation, and batch security in productionsenior
- What a bundle actually costs: download, parse, compile, executejunior
- Core Web Vitals: LCP, INP, and CLSmiddle
- Code splitting: route-level, component-level, vendor splittingmiddle
- Tree shaking and compression: removing what you don''''t usemiddle
- Third-party scripts: the silent budget killermiddle
- CI enforcement and RUM: making budgets stickmiddle
- V8 JIT pipeline, HTTP priorities, and bundle securitysenior
- The performance loop: discipline, not a projectjunior
- Classify and fix: matching bottleneck families to remediesmiddle
- Observability stack and CI gates: catching regressions before they shipmiddle
- Incident to enforcement: SLO burn to verified fix in 35 minutesmiddle
- Culture, economics, and org-scale performancesenior
- At-most-once, at-least-once, exactly-once: the three delivery contractsjunior
- The three failure legs — where duplicates and losses actually happenmiddle
- Consumer-side dedup: the cheapest path to exactly-once processingmiddle
- Kafka exactly-once semantics: idempotent producer and transactionsmiddle
- SQS visibility timeout, DLQ, and the outbox patternmiddle
- Exactly-once in production: impossibility proof, hybrid patterns, and real incidentssenior
- What OAuth is and why passwords are not the answerjunior
- Authorization code flow with PKCEmiddle
- ID token validation and JWKS cache managementmiddle
- Refresh token rotation and scope-based least privilegemiddle
- Sender-constrained tokens: DPoP and mTLSsenior
- OAuth in production: audience attacks, observability, and real failuressenior