Do them in order on your local TicketFlow. Submit the trimmed outputs and your answers in the chat. Do not look at solutions.md before submitting.
Setup (3 min):
cd ~/dev/ticketflow && source .venv/bin/activate
python manage.py runserver 8000 # terminal 1Exercise 1 — DNS with dig (warm-up)
- Run
dig +short python.organd note which record type it returns. - Run
dig python.organd find the TTL in the ANSWER section. Repeat 10 seconds later: did the TTL go down? Why? dig +trace python.org 2>/dev/null | tail -20: list the chain (root → TLD → authoritative) you saw.- Compare
dig @1.1.1.1 python.orgwithdig @9.9.9.9 python.org: do the answers match? What does that imply for an IP migration?
Exercise 2 — The phase breakdown with curl -w
Create the file curl-format.txt:
dns: %{time_namelookup}s
tcp: %{time_connect}s
tls: %{time_appconnect}s
ttfb: %{time_starttransfer}s
total: %{time_total}s
remote_ip: %{remote_ip}- Measure your local API:
curl -w @curl-format.txt -o /dev/null -s http://127.0.0.1:8000/api/health/. What are DNS and TCP against localhost? Why? - Repeat against
https://django.comandhttps://www.python.org. Which phase accumulates the most? Does it match the physical distance? - Repeat the same external URL 3 times without pausing. Do DNS/TCP/TLS drop on the 2nd and 3rd? Explain which cache acts at each phase.
Submit: the three trimmed tables and your explanation of question 3.
Exercise 3 — Simulate the 502 and fix Nginx
Without installing Nginx yet (we will in Module 9), demonstrate the concept with Gunicorn:
- Install Gunicorn:
pip install gunicornand start it:gunicorn config.wsgi --bind 127.0.0.1:8000. - In another terminal:
curl -i http://127.0.0.1:8000/api/health/. It works. - Kill Gunicorn (Ctrl+C) and repeat the curl. What exact error do you get on the client side? That is, conceptually, the 502 Bad Gateway: the proxy cannot talk to the app.
- Name 2 things you would look at in a real Nginx (
error.log) for that 502.
Exercise 4 — Balancing across two workers
- Start Gunicorn with two workers and verbose logs:
gunicorn config.wsgi --bind 127.0.0.1:8000 --workers 2 --log-level debug. - Fire 6 requests in a row:
for i in $(seq 6); do curl -s -o /dev/null http://127.0.0.1:8000/api/health/; done. - Watch the logs: do two PIDs serve the requests? Which algorithm is Gunicorn using by default? (hint:
--worker-class syncspreads by accepting connections, something different from an HTTP balancer's round robin; explain the difference in your own words). - What happens if one worker blocks for 10 seconds on a slow request and 3 more arrive at that same worker? (reason with "least connections" vs "accept-based").
Exercise 5 — Proxy headers and a 404 that bites
In config/settings.py temporarily add:
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")curl -i http://127.0.0.1:8000/api/health/: what response do you get and why? (this header makes Django believe the proxy already terminated TLS).- Remove the line. Now: which client IP would Django see if the request arrives via Nginx without
X-Forwarded-Forconfigured? And which scheme (request.scheme)? - Write in 2 lines which
proxy_set_headerconfiguration is needed for Django to see the real IP andhttps.
Exercise 6 — Balancer health check (design)
Design (on paper, 5 lines) the balancer-grade /api/health/ endpoint:
- What should it check: just "process alive", or also database and Redis? What is the cost of checking the DB on every check every 5 seconds?
- What code do you return if the DB is slow but responding: 200 or 503? Justify with the effect on the balancer (drop from pool vs keep).
- What should it return: the deployment version, boot time, the PID? Why do those hints help debugging?
Exercise 7 — Reservation latency (senior discussion)
TicketFlow's POST /api/events/42/reserve currently does: validate JSON, 2 SELECTs, 1 reservation INSERT, 1 item INSERT, 1 COMMIT (all local).
- Estimate the added latency if the DB moves from localhost to a different region (~90 ms RTT), knowing there are 5 round trips to the DB. How does a single transaction solve it? (save it: it is Lesson 10).
- The events listing is cached 60 s on a CDN. A buyer sees an seat as "available" that another user just reserved. What is that at the network level, and what business decision do you propose?
Exercise 8 — Route tracing (optional, theoretical)
traceroute -n ticketflow.app(ortracepath): how many hops to the destination? What do the*on some hops mean?- Why is the first hop usually your router and the last one may not answer ICMP?
Submit
Paste the trimmed outputs and your answers in the chat. With this I close Lesson 02 and mark it taught; next up: Lesson 03 — Concurrency and asynchrony (the one that makes it possible for two buyers not to take the same seat without the database saying a word).