⌨️ YOU PRESS ENTER  ·  WHAT HAPPENS NEXT?
https://devdunia.com/
⬇ ENTER ⬇
200 OK — PAGE LOADS IN ~143ms
🗺️ FULL JOURNEY — EVERY ACTOR AT A GLANCE OVERVIEW
🧑‍💻
YOU — THE BROWSER
Chrome / Firefox / Safari · Chrome Canary 185.199.108.153
You type https://devdunia.com and press Enter. The browser parses the URL, checks its own DNS cache, then calls the OS resolver API (getaddrinfo("devdunia.com")) to get the server IP.
UDP query: devdunia.com IN A? → 8.8.8.8:53
🔍
DNS RECURSIVE RESOLVER
8.8.8.8 (Google DNS) · 1.1.1.1 (Cloudflare) · or your ISP's resolver
Receives the query. Checks its own cache — miss. Starts walking the DNS tree: asks Root NS → gets referral to .com TLD NS → gets referral to Cloudflare Auth NS → gets the final A record: 185.199.108.153. Returns the IP to your OS in under 30ms.
TCP SYN → 185.199.108.153:443 — starting connection
⚖️
LOAD BALANCER
nginx / AWS ALB / HAProxy · public IP 185.199.108.153
Accepts the TCP connection and terminates TLS (decrypts the traffic). Reads the HTTP request headers, runs a health check on available backends, picks one using round-robin or least-connections, and forwards the plain HTTP request over the private network (10.0.0.12:8080). Adds X-Forwarded-For header with your real IP.
HTTP GET / → 10.0.0.12:8080 (plain HTTP, private network)
🖥️
APP SERVER
Node.js / Django / Rails / Go · running on 10.0.0.12:8080
nginx hands it to the app framework. The router matches GET / to the HomeController. Middleware runs in order: auth check → rate limiter → request logger → CORS. The controller checks Redis for a cached response — miss — so it fires a SQL query at the database on 10.0.0.50:5432.
SELECT id, title, content FROM pages WHERE slug='home' → 10.0.0.50:5432
🗄️
DATABASE (PostgreSQL)
10.0.0.50:5432 · connection pool (PgBouncer) · B-Tree index on slug
Borrows a pre-warmed connection from the pool (no 60ms open cost). Parses the SQL, runs the query planner — picks an index scan on idx_pages_slug instead of a full table scan. Finds 1 matching row in the buffer pool (no disk I/O needed). Returns the row in 0.38ms.
⬆ RESPONSE TRAVELS BACK — DB → App → LB → Browser
DB Returns 1 row to the App Server via the connection pool. Takes 0.38ms.
App Server Injects DB data into the HTML template. Sets response headers (Cache-Control, CSP, Content-Encoding: br). Streams the response back to the Load Balancer.
Load Balancer Re-encrypts with TLS and forwards the 200 OK back to the browser over the original TCP connection.
Browser Receives the HTML, starts parsing and rendering. Fires parallel requests for CSS, JS, images over the same HTTP/2 connection. Page is visible in ~420ms.
🔬 ANATOMY OF A URL STEP 0 — BEFORE ANYTHING ELSE

Before the browser does anything with the network, it parses the URL into its components. Each part drives different behaviour — the scheme decides the port, the host goes to DNS, the path goes in the HTTP request.

https
SCHEME
devdunia.com
HOST
443
PORT (implied)
/
PATH
(none)
QUERY
https → port 443 automatically. http → port 80. The browser also checks if the URL is in its HSTS preload list — if so, it upgrades http to https before even making a connection.
⚡ FULL REQUEST FLOW — EVERY HOP 10 STEPS
1
🌐
DNS RESOLUTION
BROWSER → OS → RESOLVER
Actor: Browser + OS + Recursive Resolver (8.8.8.8) + Root NS + TLD NS + Auth NS
The browser calls getaddrinfo("devdunia.com"). The OS checks its cache, then forwards a UDP query to the configured resolver (e.g. 8.8.8.8). The resolver walks the DNS tree — Root NS → .com TLD NS → Cloudflare Auth NS — and returns the A record.
🗂️ BROWSER CACHE MISS 📄 /etc/hosts MISS ✓ RESOLVER ANSWERS
"devdunia.com = 185.199.108.153 (TTL=3600s). Cached. Moving on." [See full DNS deep dive →]
IP RESOLVED Browser now has: 185.199.108.153  ·  Target: port 443 (https)
2
🤝
TCP 3-WAY HANDSHAKE
BROWSER ↔ SERVER
Actor: Browser (client) ↔ Server (185.199.108.153:443)  ·  Protocol: TCP over IP
Before any data is exchanged, a reliable connection must be established. TCP uses a 3-message handshake to synchronize sequence numbers on both sides — so neither party loses a packet without knowing.
🌐 BROWSER (CLIENT)
↔ NETWORK ↔
🖥️ SERVER (185.199.108.153)
① SEND: SYN
seq=1000, SYN=1
"I want to open a connection. My starting sequence number is 1000. Are you there?"
RECEIVE: SYN
Server receives the SYN packet. Allocates a socket, picks its own starting sequence number (5000), and prepares to acknowledge.
RECEIVE: SYN-ACK
Browser checks: ack=1001 (correct, my seq+1). Server seq is 5000. Verified. Connection half-open — one more step to go.
② SEND: SYN-ACK
seq=5000, ack=1001, SYN=1, ACK=1
"Got your SYN! My seq is 5000. I'm expecting byte 1001 from you next. Let's connect."
③ SEND: ACK
ack=5001, ACK=1
"Acknowledged! Expecting your byte 5001 next. TCP handshake complete — starting TLS now."
✅ CONNECTION OPEN
Both sides have synchronized sequence numbers. A reliable, ordered byte-stream is now open. The browser immediately starts the TLS handshake over this connection.
"TCP connection open to 185.199.108.153:443. Round trip: ~12ms. Now starting TLS."
TCP OPEN Reliable byte-stream established  ·  Now negotiate encryption
3
🔒
TLS 1.3 HANDSHAKE
ENCRYPT THE CONNECTION
Actor: Browser ↔ Server  ·  Protocol: TLS 1.3  ·  ~1 round trip (vs 2 in TLS 1.2)
TLS negotiates a shared encryption key without ever transmitting it directly over the wire. TLS 1.3 cuts this to 1 round trip (vs 2 in older TLS 1.2) by sending key exchange data in the very first message.
TLS 1.3 — 1 ROUND TRIP
Browser →ClientHello: TLS version, cipher suites, key_share (public key for ECDH)
Server →ServerHello + Certificate + Finished (encrypted) — all in ONE message
Browser →Verifies cert chain against trusted CA roots → sends Finished → encryption begins
"TLS handshake complete. All future data encrypted with AES-256-GCM. ~18ms. Sending HTTP request now."
TLS DONE Browser → Server: encrypted HTTP/2 GET request
4
📨
HTTP REQUEST
BROWSER SENDS
Actor: Browser  ·  Protocol: HTTP/2 (multiplexed, binary, header compression)
The browser sends an HTTP/2 GET request over the encrypted TLS channel. It includes headers that tell the server who it is, what content it accepts, any cookies, and more.
GET / HTTP/2
Host: devdunia.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X)
Accept: text/html,application/xhtml+xml
Accept-Encoding: gzip, br
Accept-Language: en-US,en;q=0.9
Cookie: session=abc123; theme=dark
; Connection: Keep-Alive (HTTP/2 reuses the connection)
HTTP/2 GET / Encrypted packet arrives at server's public IP: 185.199.108.153
5
⚖️
LOAD BALANCER
TRAFFIC DISTRIBUTION
Actor: Load Balancer (nginx / HAProxy / AWS ALB / Cloudflare)  ·  Terminates TLS, routes to backend
The public IP doesn't go directly to your app — it hits a load balancer first. The LB terminates the TLS connection (decrypts), inspects the HTTP request, and forwards it to one of the available backend servers using a routing algorithm.
LOAD BALANCER DECISIONS
1. Health check: Is backend-1 alive? Last ping: 200ms ago → ✓ healthy
2. Algorithm: Round-robin / Least-connections / IP-hash → picks backend-2
3. Forward: Adds X-Forwarded-For: 203.0.113.45 header (real client IP)
4. Re-encrypt: New TLS session to backend (or plain HTTP on internal network)
⚖️ LOAD BALANCER — ROUTING DECISION
🌐 INCOMING REQUEST
185.199.108.153:443
Encrypted HTTPS request arrives on the public IP. LB terminates TLS, decrypts, and reads the Host header.
health check
round-robin
BACKEND-1 · 10.0.0.11:8080 ✓ HEALTHY
Available, 12 active connections. Not chosen this round.
BACKEND-2 · 10.0.0.12:8080 ⬅ CHOSEN
Round-robin selects this node. Request forwarded with X-Forwarded-For header added.
BACKEND-3 · 10.0.0.13:8080 ✗ UNHEALTHY
Failed last health check 4s ago. Removed from rotation until it recovers.
"Request routed to backend-2 (10.0.0.12:8080). Health check: ✓. Forwarding now."
INTERNAL LB → Backend Server (10.0.0.12:8080) — plain HTTP on private network
6
🖥️
WEB / APP SERVER
NGINX + NODE / DJANGO / RAILS
Actor: nginx (reverse proxy) → App server (Node.js / Python / Go)  ·  Routing, middleware, business logic
The backend server receives the request. nginx (or Apache) handles static files directly, and proxies dynamic requests to the application server. The app server runs your framework's routing engine, executes middleware (auth, logging, rate limiting), and calls the appropriate controller/handler.
APP SERVER PIPELINE
nginx: Is this a static file (CSS/JS/img)? Yes → serve from disk immediately (no app server)
App router: Match GET / → HomeController#index
Middleware: Auth check → Rate limit check → Request logger → CORS headers
Controller: Fetch home page data from DB / cache → render template
"GET / → HomeController. Middleware passed. Checking Redis cache for page data... cache miss. Querying DB."
DB QUERY App Server → Database (10.0.0.50:5432): SELECT * FROM pages WHERE slug = 'home'
7
🗄️
DATABASE
POSTGRES / MYSQL
Actor: PostgreSQL / MySQL  ·  Connection pool → query planner → index scan → buffer pool
The app server picks a connection from the connection pool (pre-opened DB connections — opening a fresh one costs ~50ms). The DB parses the query, checks for a cached execution plan, runs the query optimizer, and executes using the most efficient index available.
-- App sends:
SELECT id, title, content FROM pages
WHERE slug = 'home' AND published = true;

-- Query plan (EXPLAIN):
Index Scan using idx_pages_slug on pages
  Index Cond: (slug = 'home')
  Filter: (published = true)
Planning time: 0.12 ms
Execution time: 0.38 ms
🗄️ SQL QUERY EXECUTION PIPELINE
① PARSER
Tokenize and build a parse tree
Receives the raw SQL string. Breaks it into tokens (SELECT, id, FROM, pages…), checks syntax, and builds an internal parse tree. Rejects queries with syntax errors here before any data is touched.
② PLANNER
Check table statistics and available indexes
Looks at pg_stats for the pages table: 5,000 rows, 95% distinct slug values. Sees that idx_pages_slug exists. Considers possible strategies: sequential scan, index scan, or bitmap heap scan.
③ OPTIMIZER
Pick the cheapest execution plan
Calculates cost of each strategy in arbitrary units. Sequential scan (read all 5,000 rows): cost=89. Index scan (jump directly to matching rows): cost=0.3. Index scan wins. Creates the final execution plan.
④ INDEX SCAN
B-Tree traversal on idx_pages_slug
Traverses the B-Tree index, comparing key="home" down the tree. 3 node comparisons to find the leaf. Gets a pointer (tuple ID = page 42, row 7) pointing to the actual data on disk. Lightning fast — O(log n).
⑤ BUFFER
POOL
Check shared memory before hitting disk
The Buffer Pool (shared_buffers) is PostgreSQL's in-memory page cache. Checks if page 42 is already loaded. Cache hit — the page is in memory. No disk I/O needed. Fetches the row directly from RAM in microseconds.
⑥ RESULT
1 row returned in 0.38ms total
Applies the WHERE filter (published = true), projects the requested columns (id, title, content), and returns the result set to the app server via the connection pool socket.
"Query executed via index scan. 1 row returned in 0.38ms. Sending result back to app server via connection pool."
DB RESULT DB → App Server: 1 row, ~2KB  ·  App server renders HTML template
8
📄
RESPONSE BUILT
APP SERVER → LB → BROWSER
Actor: App server renders HTML → LB forwards → Browser receives
The app server injects the DB result into the HTML template, sets response headers (cache-control, content-type, security headers), and sends the response back through the load balancer to the browser.
HTTP/2 200 OK
Content-Type: text/html; charset=utf-8
Content-Encoding: br (Brotli compressed)
Cache-Control: public, max-age=3600
X-Frame-Options: DENY
Content-Security-Policy: default-src 'self'
Transfer-Encoding: chunked

<!DOCTYPE html>...
RESPONSE BACK App Server → LB → Browser: 200 OK, ~18KB HTML (compressed)
9
🖼️
BROWSER RENDERS
HTML → CSSOM → DOM → PAINT
Actor: Browser rendering engine (Blink/WebKit)  ·  Critical rendering path
The browser doesn't wait for the full response — it streams and parses HTML incrementally. As it finds CSS and JS references it opens new HTTP/2 streams over the same connection to fetch them in parallel.
CRITICAL RENDERING PATH
① Parse HTML → build DOM tree
② Parse CSS → build CSSOM tree (render-blocking!)
③ Combine → Render tree (visible nodes only)
Layout — calculate exact position and size of every element
Paint — draw pixels to layers
Composite — GPU combines layers → display
🖼️ CRITICAL RENDERING PATH
① PARSE HTML
Stream HTML → build the DOM tree incrementally
The browser doesn't wait for the full response. It parses HTML as bytes arrive, building the DOM tree node by node. When it encounters a <link rel="stylesheet"> or <script> it fires a new HTTP/2 stream to fetch it in parallel.
② BUILD
CSSOM
⚠️ RENDER-BLOCKING — nothing paints until this is done
CSS is fully parsed before any rendering starts. The browser builds the CSS Object Model (CSSOM) — a tree of style rules. This blocks painting because a stylesheet loaded at the bottom of the page could change every element's appearance, making earlier paint wasted.
③ RENDER
TREE
Merge DOM + CSSOM → only visible nodes
Combines the DOM and CSSOM. Skips nodes with display:none or in <head>. What remains is a tree of visible boxes that need to be drawn — each with computed styles attached.
④ LAYOUT
Calculate exact pixel positions and sizes
The engine walks the render tree computing the exact geometry of every element — x, y, width, height. Resolves percentages, flexbox calculations, grid placements. A change that triggers re-layout (e.g. changing element width) is called "reflow" and is expensive.
⑤ PAINT
Convert layout to drawing commands per layer
Generates a list of drawing commands (fill rectangle, draw text, render image) for each paint layer. Layers are created for elements with position:fixed, transforms, or opacity — anything the GPU can animate cheaply.
⑥ COMPOSITE
GPU combines layers → pixels on screen
The compositor thread uploads layers to the GPU as textures and combines them in the correct stacking order. This step runs on the GPU and is very fast — which is why CSS transitions on transform and opacity are smooth: they only need compositing, not layout or paint.
"First Contentful Paint: 420ms. Largest Contentful Paint: 680ms. Page interactive: 850ms. Done!"
⏱️ LIVE TIMING BREAKDOWN INTERACTIVE

Enter any URL and see a simulated timing breakdown of every phase. Uses Navigation Timing API to show real browser measurements when available.

Press ANALYZE to see the timing breakdown...
🔒 TLS 1.3 vs TLS 1.2 — Why 1.3 is Faster
TLS 1.2 requires 2 round trips before data can flow. TLS 1.3 cuts this to 1 round trip by merging the key exchange into the ClientHello. For a 50ms round trip time, this saves 50ms per new connection — significant for page load time.
TLS 1.2 — 2 ROUND TRIPS
TLS 1.3 — 1 ROUND TRIP
1. ClientHello
2. ServerHello + Cert
3. Client KeyExchange + ChangeCipherSpec
4. Server Finished ← DONE (2 RTT)
1. ClientHello + key_share
2. ServerHello + Cert + Finished ← DONE (1 RTT)

50ms faster per connection
⚖️ Load Balancer Algorithms Compared
ROUND ROBIN
HowRequest 1→server1, 2→server2, 3→server3, 4→server1…
Best forStateless apps, uniform request cost
ProblemDoesn't account for slow requests piling up
LEAST CONNECTIONS
HowRoute to server with fewest active connections
Best forLong-lived connections (WebSockets, uploads)
ProblemNeeds LB to track connection counts
IP HASH
Howhash(client_ip) % server_count → same server always
Best forSession affinity without shared session store
ProblemUneven distribution behind NAT
🗄️ Connection Pooling — Why It Matters
Opening a fresh PostgreSQL connection takes 40–100ms — TCP handshake + auth + session setup. Under load, this destroys performance. A connection pool (PgBouncer, HikariCP, pg-pool) keeps a set of connections warm and reuses them.
WITHOUT POOL vs WITH POOL
Without Pool:
Request 1: open conn ~60ms + query 1ms = 61ms
Request 2: open conn ~60ms + query 1ms = 61ms
100 req/s = 100 new connections
With Pool (20 conns):
Request 1: borrow conn 0ms + query 1ms = 1ms
Request 2: borrow conn 0ms + query 1ms = 1ms
100 req/s = 20 reused connections
📊 WHAT GOOD TIMING LOOKS LIKE BENCHMARKS
DNS LOOKUP
< 30ms
cached: <1ms · cold: 20–80ms
TCP CONNECT
< 20ms
depends on physical distance to server
TLS HANDSHAKE
< 30ms
TLS 1.3: 1 RTT · TLS 1.2: 2 RTT
TIME TO FIRST BYTE
< 200ms
server processing + DB queries
FIRST CONTENTFUL PAINT
< 1.8s
Google Core Web Vitals: "good"
LARGEST CONTENTFUL PAINT
< 2.5s
Google Core Web Vitals: "good"
📋 CHEAT SHEET — COMMANDS TO INSPECT EACH PHASE REFERENCE
DNS
dig devdunia.comfull DNS response
dig +trace devdunia.comfull resolution chain
nslookup devdunia.comsimple lookup
TCP / TLS
curl -v https://devdunia.comfull handshake log
openssl s_client -connect devdunia.com:443TLS details
traceroute devdunia.comnetwork path
HTTP TIMING
curl -w "@curl-format.txt"per-phase timing
Chrome DevTools → NetworkWaterfall view
Lighthouse → PerformanceCore Web Vitals
DATABASE
EXPLAIN ANALYZE SELECT...query execution plan
pg_stat_statementsslow query log (Postgres)
SHOW PROCESSLISTactive queries (MySQL)