Apples-to-apples latency test of the same API on AWS and Linode, exposed through increasing infrastructure layers (DNS → WAF → compute), measured from multiple regions.
Status: Built, deployed, and measured on AWS (US vantages). Scope reduced to AWS-only (Linode deferred — see Open / Deferred); the comparison is layer-for-layer on AWS (T0 → T1 → T2).
- Performance Measurements — stakeholder summary: US latency of the production path (CloudFront edge + WAF), first-call vs second-call.
- analysis/RESULTS.md — full technical detail incl. the layer-by-layer (T0/T1/T2) comparison; raw data in
results/.
Everything is reproducible from code (infra/aws/). No local Terraform needed — tf.sh falls back to Docker.
cd infra/aws
cp terraform.tfvars.example terraform.tfvars # set your domain (see REGISTER.md to register one)
make all # ECR repo -> build & push image -> EC2 + Route53 + ACM + CloudFront + WAF
make destroy # tear it all down (keeps the registered domain)Compute is a CPU instance (c7i.large) — the API is near-zero compute, so a GPU would add cost without changing the latency results. instance_type is a variable, so a different class can be set if ever needed. Measure: bench/run.sh (Dallas/local) and bench/globalping.py (US cities).
Measure the latency cost each layer adds, and compare AWS vs. Linode layer-for-layer, using managed services on both sides.
GET /v1/personalization — TLS-only, stateless, no DB. Returns a ~4 KB bank-style personalization payload (made-up customer + dashboard config). Compute is near-zero so we measure infra + network, not app logic.
Payload: customer (name, segment, locale), greeting, ordered dashboard tiles, theme, feature flags, personalized offers, notifications, entitlements, experiments. Padded to a fixed byte length so both clouds return identical-length bodies. Emits Server-Timing + X-Tier/X-Served-By headers.
| Tier | Path | AWS | Linode |
|---|---|---|---|
| T0 Direct | Client → Compute | Yes | Yes |
| T1 + DNS | Client → DNS → Compute | Yes | Yes |
| T2 + WAF | Client → DNS → WAF (edge) → Compute | Yes (CloudFront + AWS WAF) | No (contract-only) |
All tiers TLS-only; each tier = its own hostname (t{0,1,2}.{aws,linode}.<domain>) so we measure them independently. (No API Gateway — dropped by design.)
Comparison scope:
- Cross-cloud apples-to-apples: T0 and T1 (exist on both).
- WAF: AWS-only → a with-vs-without-WAF comparison (T1 vs T2) on AWS. Linode's WAF tier is a documented gap (see below).
- L3/L4 firewall tier intentionally skipped: AWS Security Groups ≈ Linode Cloud Firewall (both free packet filters, ~0 latency) — an equivalent exists on both, but it's not a meaningful latency comparison.
| Capability | AWS | Linode / Akamai |
|---|---|---|
| Compute | CPU instance (c7i.large) |
(deferred — would match the same CPU class) |
| DNS | Route 53 | Linode DNS Manager |
| WAF (L7) | AWS WAF on CloudFront (edge) | Akamai App & API Protector — contract-only, no trial |
| L3/L4 firewall | Security Group (free) | Cloud Firewall (free) — skipped, see Tiers |
| TLS | ACM (on CloudFront) | Let's Encrypt |
Why CloudFront on AWS: AWS WAF can't attach to a raw EC2 instance — it needs CloudFront, ALB, or API Gateway. We use CloudFront so AWS's WAF tier is edge-shaped — matching Akamai's edge WAF for when/if it's added later → fairest future comparison.
A Cloud Firewall / Security Group decides who can connect; a WAF inspects what they send.
| Cloud Firewall / Security Group (L3/L4) | WAF (L7 — AWS WAF / Akamai) | |
|---|---|---|
| Sees | source/dest IP, port, protocol | full request: URL, headers, query, cookies, body |
| Inside TLS? | No (blind to payload) | Yes — terminates TLS, parses HTTP |
| Stops | port scans, bad IPs, L3/L4 noise | SQLi, XSS, RCE, bad bots, OWASP threats |
| Latency | ~0 (packet ACL) | real — TLS termination + inspection per request |
Comparing AWS WAF (L7) to Linode Cloud Firewall (L3/L4) would not be apples-to-apples — the firewall does far less work and would look artificially faster. Fair pairings are L3/L4↔L3/L4 (SG ↔ Cloud Firewall) and L7↔L7 (AWS WAF ↔ Akamai).
Production note (Chase): A bank needs an L7 WAF (PCI-DSS, OWASP). AWS ships one managed + self-serve (AWS WAF + CloudFront/ALB). Linode-the-IaaS does not — you'd contract Akamai (App & API Protector) or self-host ModSecurity/Coraza + OWASP CRS. That structural gap is itself a key procurement finding.
- AWS:
us-east-1(Ashburn). Linode: Washington DC (us-iad) — closest physical match. - Vantages: TBD after deployment (you're in Dallas). Decide self-hosted probes vs. Globalping vs. mix in Phase 5.
HTTP/2 · TLS 1.3 (min 1.2) · keep-alive on · gzip on · HSTS. Pinned identically across both clouds. The 4 KB is the uncompressed body; we also note on-wire size.
- Capture full waterfall per request:
dns / tcp / tls / ttfb / download / total+ app time fromServer-Timing. - Interleave AWS & Linode from the same probe; discard warm-ups; ≥1000 samples per (tier × cloud × vantage).
- Report p50/p90/p95/p99, not means. Separate cold vs. warm (edge/WAF cold paths).
- Record exact instance specs, TLS/HTTP versions, keep-alive/compression state per run.
Use each provider's default / free managed rule set (e.g. AWS Managed Rules baseline, Akamai default policy). Goal is to measure the layer's latency overhead — nothing custom.
Provision → ~1 day of testing → keep live 48 h → destroy to minimize cost. Keep Terraform/IaC so it can be redeployed later. Cost (CPU origin + AWS edge/WAF over a few days, low tens of USD) is acceptable; teardown keeps it bounded.
Note (only if you switch to a GPU class): AWS G-instance vCPU quota defaults to 0 and needs a Service Quotas increase (can take a day+). Not required for the CPU instance used here.
Go app (tiny, fast) → Docker (same image both clouds) → Terraform IaC → k6 + curl -w for measurement → Python/pandas for analysis.
app/ ~4KB personalization API + Dockerfile
infra/aws/ Terraform: EC2 (CPU), Route53, CloudFront + WAF, ACM
infra/linode/ Terraform/Ansible: instance, DNS, certs (deferred; WAF contract-only)
bench/ k6 harness, probe IaC, globalping scripts
results/ raw + aggregated data
analysis/ tables & charts
- Spec sign-off (this doc) → 1. App → 2. AWS infra → 3. Linode infra → 4. Harness → 5. Run & collect (pick vantages) → 6. Analysis → 7. Teardown + cost writeup.
- Domain: buying a dedicated throwaway domain for SSL + per-tier subdomains.
- WAF: AWS WAF on CloudFront. Akamai (Linode WAF) is contract-only, no trial → Linode WAF tier deferred; WAF comparison is with-vs-without on AWS for now.
- L3/L4 firewall tier skipped (SG ≈ Cloud Firewall, ~0 latency).
- Compute: CPU instance (
c7i.large). GPU was originally specced for a matched cross-cloud class, but the API is near-zero compute and Linode (the reason for matching) was deferred — so a GPU would add cost with no effect on the latency results.instance_typestays configurable. - Protocol: bank-standard (HTTP/2, TLS 1.3, keep-alive, gzip, HSTS).
- Lifetime: test ~1 day → live 48 h → destroy; keep IaC for redeploy. Cost acceptable.
- VCS: local project = HelloWorld-Latency, on personal GitHub.
- Vantages (Phase 5) — self-hosted probes vs. Globalping vs. mix. Decide after deployment.
- Linode L7 WAF — revisit if/when an Akamai contract is in play; otherwise self-hosted ModSecurity/Coraza is the fallback to restore symmetry.