Nhật ký công việc cho nhiệm vụ "Phát triển và ra mắt trang web"
Tiến độ: Kiểm tra kiến trúc haih.site trên dự án thực tế
Chúng tôi đã nghiên cứu việc triển khai hiện tại của haih.site và làm rõ tiêu chí tối giản về mặt kiến trúc. Một kết luận quan trọng: tính tối giản không thể đo lường bằng số lượng công nghệ hoặc phụ thuộc. Cần phải giảm thiểu tổng mức ma sát (friction) của quá trình phát triển, vận hành và các thay đổi trong tương lai, bao gồm cả vòng lặp phản hồi (feedback loop) giữa con người và AI.
Development stack
Bản thân React/Vite không phải là sự dư thừa ngay cả đối với một trang web chủ yếu phục vụ nội dung tĩnh ở môi trường production.
Vite được biện minh bởi yêu cầu thực tế trong phát triển: vòng lặp phản hồi edit → HMR → browser nhanh chóng mà không cần các thao tác tải lại/xóa bộ nhớ đệm (reload/cache-reset) thủ công từ người dùng.
React có thể được biện minh bởi mô hình component và tính cục bộ của các thay đổi đối với UI đang phát triển. Đối với AI, điều này cũng cung cấp một cấu trúc mã nguồn dễ dự đoán và quen thuộc.
Nguyên tắc đã được làm rõ:
Không giảm thiểu số lượng phụ thuộc. Hãy giảm thiểu ma sát. Mỗi công nghệ được thêm vào phải làm giảm tổng độ phức tạp nhiều hơn mức mà nó làm tăng lên.
Production serving: baseline
Sau khi build, npm run start đã được khởi chạy và một bài kiểm tra tổng hợp (synthetic benchmark) được thực hiện:
ab -c 1000 -n 100000 http://localhost:3000/
Kết quả:
- 100.000 yêu cầu;
- mức độ đồng thời (concurrency) 1000;
- 0 yêu cầu thất bại;
- 11.515,51 req/s;
- p50 27 ms;
- p95 37 ms;
- p99 67 ms;
- tối đa 8658 ms.
Độ trễ (latency) điển hình ở mức thấp, nhưng có sự xuất hiện của các giá trị ngoại lai lớn đơn lẻ (outliers).
Docker + Traefik + Varnish
Sau đó, cùng khối lượng công việc (workload) đó được chạy qua chuỗi giống như production:
client → Traefik → Varnish → app
Benchmark:
ab -c 1000 -n 100000 http://localhost:8088/
Kết quả:
- 100.000 yêu cầu;
- mức độ đồng thời (concurrency) 1000;
- 0 yêu cầu thất bại;
- 15.871,02 req/s;
- p50 62 ms;
- p95 74 ms;
- p99 88 ms;
- tối đa 129 ms.
So với app-server trực tiếp, thông lượng (throughput) đã tăng khoảng 38%, và sự phân phối độ trễ trở nên ổn định hơn đáng kể: phần đuôi kéo dài nhiều giây đã biến mất.
Hành vi của origin
docker stats cho thấy trong quá trình thực hiện 100k yêu cầu, ứng dụng hầu như không tham gia vào việc xử lý các yêu cầu lặp lại.
Trước khi kiểm tra, ứng dụng có khoảng:
NET I/O 9.56kB / 126B
MEM 25.73 MiB
Sau khi kiểm tra:
NET I/O 10.9kB / 3.68kB
MEM 26.16 MiB
Trong đó Traefik đã xử lý khoảng 370MB / 394MB, Varnish — 41MB / 328MB.
Điều này xác nhận một ranh giới kiến trúc hữu ích: tải phân phối có thể lưu trữ bộ nhớ đệm (cacheable delivery) kết thúc ở Varnish và hầu như không đi đến tầng ứng dụng (application layer).
Tại sao dự án cần Varnish
Yêu cầu cực kỳ quan trọng: trang web sử dụng tính năng thay đổi kích thước/xử lý hình ảnh động thông qua Node + Sharp.
Nếu không có bộ nhớ đệm (cache), một yêu cầu lặp lại cho cùng một biến thể hình ảnh có khả năng sẽ lặp lại chu kỳ tốn kém:
request
→ Node
→ Sharp
→ decode
→ resize
→ encode
→ response
Với Varnish, việc tính toán được thực hiện khi xảy ra cache miss, sau đó cùng một biến thể có thể được phục vụ từ cache mà Sharp không phải làm việc lại:
request
→ Varnish
├─ HIT → response
└─ MISS → Node → Sharp → result → cache → response
Do đó, Varnish được biện minh không chỉ đơn thuần là tăng RPS của HTML tĩnh, mà là cô lập tầng ứng dụng (application layer) và lưu trữ bộ nhớ đệm kết quả của các phép tính lặp lại tốn kém.
Kết luận kiến trúc
Stack hiện tại không nên được đánh giá như một danh sách công nghệ đơn thuần:
React + Vite + Node + Sharp + Varnish + Traefik + Docker
mà là tập hợp các giải pháp cho các yêu cầu cụ thể:
- Vite → vòng lặp phản hồi phát triển nhanh chóng;
- React → sự kết hợp và tính cục bộ của các thay đổi UI;
- Node + Sharp → chuẩn bị hình ảnh theo yêu cầu (on-demand) với kích thước/định dạng phù hợp;
- Varnish → không lặp lại quá trình xử lý tốn kém và không để tải có thể cache lọt đến origin;
- Traefik → ranh giới định tuyến/triển khai (routing/deployment boundary);
- Docker → runtime/deployment có thể tái tạo (reproducible).
Một tiêu chí tốt cho mỗi thành phần kiến trúc:
Thành phần này làm giảm chi phí đo lường được nào và liệu lợi ích thu được có vượt quá chi phí của chính thành phần đó không?
Cần sử dụng sự làm rõ này khi phát triển showcase và các best practices của HAIH: không áp đặt số lượng công nghệ tối thiểu, mà là hiển thị tổng chi phí giải pháp tối thiểu đủ dùng kèm theo bằng chứng (evidence).
Kiểm tra hữu ích tiếp theo
Để so sánh sạch tầng cache, sẽ hợp lý hơn nếu sau này lấy điểm dữ liệu thứ ba (third datapoint):
A. app
B. Traefik → app
C. Traefik → Varnish → app
với cùng một khối lượng công việc (workload) và một metric riêng biệt về số lượng yêu cầu thực sự đến được origin.
Bài viết marketing dựa trên những kết luận này sẽ không được xuất bản trong worklog này — sẽ được trình bày riêng sau.
Nhiệm vụ: Phát triển và ra mắt trang web
Phát triển và ra mắt haih.site làm trang web sản phẩm cho triết lý mới của HAIH: kiến trúc tối thiểu phát triển cùng các yêu cầu, cùng các bản showcase trực quan từ trang web tĩnh, API độc lập cho đến một cửa hàng trực tuyến hoàn chỉnh.