Skip to content

HARRY-20 — Checklist tổng — tự chấm toàn lộ trình

Checklist tổng — tự chấm toàn lộ trình

Một trang để tự đánh giá. Đây là bản rút gọn — mỗi giai đoạn chỉ giữ những mục có tín hiệu cao nhất. Checklist đầy đủ nằm ở mục "Done khi" trong từng file.

Cách chấm trung thực: một mục chỉ được tick khi bạn giải thích cho người khác mà không mở tài liệu, hoặc chỉ vào đoạn code mình đã viết. "Đọc rồi, thấy hiểu" không tính — đó là ảo giác quen thuộc (recognition), không phải kiến thức có thể gọi ra khi cần (recall). Trong phỏng vấn và lúc prod cháy, chỉ cái thứ hai mới dùng được.

Quy ước: ✅ tự tin giải thích · 🟡 hiểu nhưng phải tra lại · ⬜ chưa học


GĐ1 — TypeScript & toolchain → chi tiết

  • [ ] Phân biệt type-check vs transpile; biết vì sao build bằng esbuild bắt buộc phải có tsc --noEmit riêng
  • [ ] Nhìn package.json + đuôi file là biết module đó ESM hay CJS
  • [ ] Giải thích vì sao ESM+TS phải viết import './x.js' cho file x.ts
  • [ ] Path alias hoạt động sau khi build, không chỉ lúc dev
  • [ ] npm ci vs npm install; vì sao Dockerfile luôn dùng ci
  • [ ] Validate process.env bằng zod tại một file duy nhất, fail-fast lúc boot
  • [ ] Bật no-floating-promises; giải thích bug mà rule này chặn

GĐ2 — HTTP / Node / async → chi tiết

  • [ ] Chọn đúng status code cho mọi case CRUD và giải thích được vì sao
  • [ ] Giải thích idempotency; thiết kế POST an toàn với retry
  • [ ] Vẽ được event loop; dự đoán đúng thứ tự setTimeout vs Promise
  • [ ] Phân biệt I/O-bound vs CPU-bound; biết vì sao CPU-bound treo cả server
  • [ ] Dùng stream + pipe cho file lớn; hiểu backpressure
  • [ ] Cấu hình CORS + preflight; biết bẫy * + credentials
  • [ ] Viết được HTTP server thuần không framework

GĐ3 — Express → chi tiết

  • [ ] Giải thích middleware chain + next(); biết vì sao error handler phải cuối cùng
  • [ ] Validate mọi input bằng Zod ở biên
  • [ ] Error handling tập trung: AppError + asyncHandler + global handler
  • [ ] Tách project theo layer (route → controller → service → repository)
  • [ ] Giữ raw body cho webhook trong khi vẫn dùng express.json()
  • [ ] DA1 deploy công khai, có URL truy cập được

GĐ4 — Database (Postgres) → chi tiết

  • [ ] Thiết kế 1-1 / 1-n / n-n; biết đặt FK ở đâu
  • [ ] INNER vs LEFT JOIN; biết WHERE ở bảng phải âm thầm biến LEFT thành INNER
  • [ ] Đọc EXPLAIN ANALYZE; phân biệt Seq Scan vs Index Scan
  • [ ] Biết index giúp gì và tốn gì (ghi chậm hơn, tốn dung lượng)
  • [ ] Giải thích ACID; biết khi nào cần transaction
  • [ ] Nhận ra và fix N+1 — có số liệu đo được
  • [ ] Migration versioned commit vào git; không bao giờ sửa DB bằng tay
  • [ ] Connection pool: biết vì sao không mở connection vô hạn
  • [ ] Full-text search bằng tsvector + GIN; biết khi nào chưa cần vector search

GĐ5 — MongoDB & NoSQL → chi tiết

  • [ ] Kể được 4 họ NoSQL và một use case đúng cho mỗi họ
  • [ ] Bảo vệ được lựa chọn Postgres-vs-Mongo cho một bài toán cụ thể
  • [ ] Quy tắc embed-vs-reference; biết giới hạn 16 MB và bẫy mảng không chặn trên
  • [ ] Compound index theo quy tắc ESR; đọc được explain("executionStats")
  • [ ] TTL index; biết vì sao nó không chính xác tới giây
  • [ ] Aggregation pipeline; biết vì sao $match phải sớm và nhiều $lookup là tín hiệu chọn sai DB
  • [ ] Transaction Mongo cần replica set, timeout 60s, và nên hiếm khi cần
  • [ ] Phân biệt write concern / read concern / read preference
  • [ ] Change stream có lưu resume token; biết nó không thay thế queue
  • [ ] Chặn được NoSQL injection bằng validate ở biên

GĐ6 — NestJS & Auth → chi tiết

  • [ ] Giải thích DI và vì sao nó làm test dễ hơn
  • [ ] Phân biệt Guard / Pipe / Interceptor / Filter — cái nào chạy lúc nào
  • [ ] Auth đầy đủ: argon2, access ngắn hạn + refresh có rotation, refresh lưu hash
  • [ ] RBAC bằng guard + decorator
  • [ ] Giải thích Authorization Code Flow của OAuth 2.0 và vai trò của PKCE
  • [ ] Swagger tự sinh, có addBearerAuth

GĐ7 — Clean Architecture → chi tiết

  • [ ] Phát biểu Dependency Rule trong một câu
  • [ ] Giải thích Dependency Inversion: tầng trong sở hữu interface, tầng ngoài hiện thực nó
  • [ ] Nối được: DI của NestJS chính là cơ chế thực thi kiến trúc này
  • [ ] Định vị dự án trên thang 0–5 và bảo vệ được vị trí đó
  • [ ] Tổ chức thư mục theo tính năng, không theo loại file
  • [ ] Phân biệt anemic vs rich domain model; viết được Value Object tự validate
  • [ ] Quy tắc một transaction = một aggregate
  • [ ] Domain ném DomainErrorcode; chỉ một chỗ ở biên dịch sang HTTP status
  • [ ] Test domain không cần DB; dùng fake thay vì mock từng phương thức
  • [ ] Có lint rule chặn vi phạm quy tắc phụ thuộc trong CI
  • [ ] Nêu được ít nhất 3 chi phí thật — không chỉ lợi ích

GĐ8 — API thực chiến → chi tiết

  • [ ] JWT vs session: nêu đánh đổi, biết vì sao JWT khó thu hồi
  • [ ] Chốt được token lưu ở cookie httpOnly hay localStorage — và bảo vệ được lựa chọn
  • [ ] Phân biệt CORS vs CSRF; biết khi nào CSRF là vấn đề
  • [ ] Rate limit theo danh tính, không chỉ theo IP
  • [ ] Cache-aside + TTL; biết cache stampede và cách chống
  • [ ] Cursor vs offset; viết đúng sort ghép + tie-breaker bằng row-value comparison
  • [ ] Mã hoá + validate cursor (nó là input người dùng); kẹp trần limit
  • [ ] Biết chi phí thật của COUNT(*) và 4 cách thay thế
  • [ ] Idempotency key cho POST; webhook verify HMAC bằng timingSafeEqual
  • [ ] Structured logging + request id xuyên suốt
  • [ ] Chọn được kiểu API (REST/GraphQL/gRPC/tRPC) và nêu đánh đổi

GĐ9 — Queues, Jobs, Workers, Cron → chi tiết

  • [ ] Phân biệt queue vs log (BullMQ vs Kafka); biết khi nào cần replay
  • [ ] Giải thích vì sao exactly-once không tồn tại; đạt hiệu quả đó bằng idempotency
  • [ ] Viết được handler idempotent; có test gọi hai lần
  • [ ] Outbox: vì sao queue.add() trong transaction sai theo cả hai chiều
  • [ ] Hiểu FOR UPDATE SKIP LOCKED và vì sao nó cho phép nhiều relay song song
  • [ ] Payload nhỏ, có v; không để PII trong Redis
  • [ ] Retry có backoff + jitter; phân biệt lỗi tạm thời / vĩnh viễn
  • [ ] Biết quan hệ job time < grace period < lockDuration
  • [ ] Worker process riêng, xử lý SIGTERM, chờ job đang chạy xong
  • [ ] Tính được concurrency tối đa từ connection pool
  • [ ] Có DLQ; theo dõi queue deptholdest job age, không chỉ failure rate
  • [ ] Cron chạy đúng một lần trên N instance; biết giới hạn của khoá Redis
  • [ ] Cron có tz tường minh; xử lý được missed run
  • [ ] Thiết kế job theo con trỏ dữ liệu thay vì theo thời điểm
  • [ ] Chống noisy neighbor giữa các tenant

GĐ10 — Search & Elasticsearch → chi tiết

  • [ ] Nêu 4 lý do LIKE '%x%' không phải search; định vị mình trên thang 3 bậc
  • [ ] Vẽ được inverted index; giải thích chuỗi analysis
  • [ ] Biết index và query phải cùng analyzer; biết english phá tiếng Việt
  • [ ] Giải thích BM25 qua TF / IDF / field length
  • [ ] Boost theo field + function score theo tín hiệu nghiệp vụ
  • [ ] Đo bằng zero-result rate / CTR@k, không chỉnh boost theo cảm tính
  • [ ] Phân biệt query vs filter context; text vs keyword
  • [ ] Biết vì sao from + size chặn ở 10.000; dùng search_after + tie-breaker
  • [ ] Coi index là bản sao phái sinh; đồng bộ bằng outbox + external version
  • [ ] Có job đối soát và metric drift
  • [ ] Dùng alias; biết quy trình reindex không downtime
  • [ ] Không để ES ra Internet; không cho client gửi query DSL
  • [ ] Giải thích hybrid search và RRF

GĐ11 — Upload & Email → chi tiết

  • [ ] Phân biệt memory / disk / stream khi nhận file; biết khi nào bắt buộc stream
  • [ ] Validate bằng magic bytes + allowlist; không tin Content-Typefilename
  • [ ] Giải thích vì sao user content phải phục vụ từ domain khác app
  • [ ] Presigned URL 3 bước có HeadObject xác minh; ghim size/type vào chữ ký
  • [ ] Bật lifecycle abort multipart; biết khoản phí ẩn nếu quên
  • [ ] Chống orphan file cả hai chiều (DB-first, xoá mềm, job đối soát)
  • [ ] SPF + DKIM + DMARC pass thật khi kiểm tra "Show original" ở Gmail
  • [ ] Gửi mail qua outbox + queue, có idempotencyKey, có suppression list
  • [ ] Chống user enumeration ở reset password; token hash trước khi lưu

GĐ12 — Testing → chi tiết

  • [ ] Giải thích vì sao không mock DB ở backend CRUD
  • [ ] Testcontainers chạy migration thật; có chốt chặn DB không phải local
  • [ ] Ma trận authorization — và đã chứng minh nó bắt được lỗi bằng cách gỡ một guard
  • [ ] Test tenant isolation và transaction rollback
  • [ ] Mock dịch vụ ngoài có onUnhandledRequest: 'error', có test cho đường lỗi
  • [ ] Không có sleep cố định; dùng fake timers hoặc polling có timeout
  • [ ] Chạy k6, đọc được p95/p99; biết vì sao không nhìn trung bình
  • [ ] TZ=UTC cho test và CI
  • [ ] Không dùng retry để che flaky; biết 6 nguyên nhân flaky

GĐ13 — Vòng đời dữ liệu → chi tiết

  • [ ] Soft delete có partial index + partial unique; lọc tự động không dựa vào kỷ luật
  • [ ] Audit log trong DB, bất biến ở tầng quyền, ghi trong cùng transaction, allowlist field
  • [ ] TIMESTAMPTZ vs TIMESTAMP vs DATE; vì sao TIMESTAMP gần như luôn sai
  • [ ] Tính "hôm nay" đúng theo múi giờ người dùng; lịch tái diễn lưu tz name không lưu offset
  • [ ] Tiền bằng số nguyên minor unit + currency; xử lý đúng bài toán chia có dư
  • [ ] CHECK/UNIQUE/FK thi hành bất biến nghiệp vụ ở tầng DB
  • [ ] Retention + ẩn danh hoá; lập được bản đồ mọi nơi PII tồn tại
  • [ ] Backup 3-2-1 + dead man's switch
  • [ ] Đã thực hiện một restore drill thật và biết con số RTO
  • [ ] Expand-migrate-contract; CREATE INDEX CONCURRENTLY, NOT VALID, lock_timeout

GĐ14 — DevOps → chi tiết

  • [ ] Dockerfile multi-stage; .dockerignore loại node_modules/.env/.git
  • [ ] docker-compose dựng cả stack bằng một lệnh
  • [ ] CI/CD GitHub Actions: test → build → deploy
  • [ ] Deploy được lên PaaS hiểu đường VPS/ECS
  • [ ] HTTPS + reverse proxy; health check; zero-downtime
  • [ ] Đã deploy thủ công thành công ít nhất một lần trước khi chạm K8s

GĐ15 — Kubernetes → chi tiết

  • [ ] Giải thích declarative + reconciliation loop; hiểu vì sao xoá pod không "xoá" được
  • [ ] Phân biệt Pod / Deployment / Service / Ingress
  • [ ] Phân biệt ba probe; giải thích vì sao liveness không được kiểm tra DB
  • [ ] Mô tả trình tự shutdown 5 bước và vì sao cần preStop sleep
  • [ ] preStop + job dài nhất < terminationGracePeriodSeconds
  • [ ] requests vs limits; OOMKilled (memory) vs throttle (CPU)
  • [ ] Đặt --max-old-space-size khớp memory limit và giải thích vì sao
  • [ ] Biết Secret của K8s chỉ là base64; nêu 2 cách xử lý đúng
  • [ ] Biết HPA đòi hỏi app stateless; vì sao CPU thường là metric sai
  • [ ] Chẩn đoán CrashLoopBackOff / ImagePullBackOff / Pending / OOMKilled
  • [ ] Đã chạy rollout dưới tải k6 với 0 request lỗi
  • [ ] Trả lời được: dự án của tôi có cần K8s không, vì sao?

GĐ16 — Distributed Systems → chi tiết

  • [ ] Kể được ít nhất 5 trong 8 ngộ nhận, kèm hậu quả thật
  • [ ] Giải thích vì sao "không biết chắc request đã xảy ra hay chưa" là gốc của idempotency
  • [ ] Phát biểu CAP đúng; giải thích PACELC và phân loại chức năng của mình theo C hay L
  • [ ] Sắp được thang nhất quán; giải thích read-your-writes và 3 cách đạt được
  • [ ] Không dùng wall clock để sắp thứ tự giữa các máy; dùng monotonic để đo khoảng
  • [ ] Giải thích quorum, split-brain, và vì sao cluster số lẻ
  • [ ] Kể 6 chế độ hỏng; giải thích vì sao gray failure khó nhất
  • [ ] Giải thích fencing token và retry amplification
  • [ ] Vì sao 2PC không dùng ở microservice; thiết kế được saga có bồi hoàn
  • [ ] Phân biệt logs / metrics / traces; truyền trace context xuyên qua queue
  • [ ] Đã cố ý làm hỏng hệ thống của mình và ghi lại kết quả

GĐ17 — Microservices → chi tiết

  • [ ] Nhận ra monolith phân tán và vì sao nó tệ hơn cả hai lựa chọn
  • [ ] Phát biểu Luật Conway và hệ quả cho việc chọn ranh giới
  • [ ] Liệt kê ≥5 chi phí thật, gồm mất transactiondebug xuyên service
  • [ ] Kể 6 thứ hạ tầng bắt buộc trước service thứ hai
  • [ ] Xây được modular monolith với ranh giới thực thi bằng lint
  • [ ] Dùng Bounded Context; áp dụng được 4 phép thử ranh giới
  • [ ] Vì sao availability nhân nhau trong chuỗi gọi đồng bộ
  • [ ] Event tên thì quá khứ, có eventId/version; consumer idempotent
  • [ ] Vì sao không service nào đọc bảng của service khác; 3 cách thay JOIN
  • [ ] Contract testing bắt lỗi trước khi deploy; phân loại thay đổi API phá vỡ
  • [ ] Mô tả Strangler Fig và 7 bước tách service
  • [ ] Trả lời được "khi nào anh tách microservice?" — bắt đầu bằng "hầu hết là không"

GĐ18 — System Design → chi tiết

  • [ ] Áp dụng khung 6 bước cho đề lạ trong 45 phút, có bấm giờ
  • [ ] Thuộc bảng độ trễ; ước lượng QPS/dung lượng và rút ra kết luận kiến trúc
  • [ ] Nêu 5 chỗ hay giấu trạng thái phá vỡ scale ngang
  • [ ] Graceful shutdown 4 bước, chứng minh deploy không mất request
  • [ ] Thứ tự mở rộng tầng dữ liệu; vì sao sharding là cuối cùng
  • [ ] Bẫy read-after-write với replica
  • [ ] Timeout + retry có jitter + circuit breaker; vì sao chỉ retry ở một tầng
  • [ ] Load shedding; vì sao từ chối bớt lại tốt hơn nhận hết
  • [ ] Chẩn đoán "API chậm" có hệ thống; vì sao không cảnh báo theo CPU
  • [ ] Chạy k6 tới điểm gãy và tìm ra nút thắt thật

GĐ19 — LLM APIs → chi tiết

  • [ ] Message roles + stateless conversation; tự quản lịch sử hội thoại
  • [ ] Token, context window, cost — ước lượng được chi phí mỗi request
  • [ ] Streaming SSE + abort
  • [ ] Structured output validate bằng Zod; xử lý khi model trả JSON hỏng
  • [ ] Error handling: 429, timeout, retry có backoff
  • [ ] Prompt injection & guardrails
  • [ ] Caching để giảm chi phí; eval cơ bản để đo chất lượng

GĐ20 — Embeddings & RAG → chi tiết

  • [ ] Giải thích embedding và cosine similarity
  • [ ] Chiến lược chunking và ảnh hưởng của nó tới chất lượng truy hồi
  • [ ] pgvector: tạo bảng, index HNSW vs IVFFlat, truy vấn có lọc tenant
  • [ ] Pipeline RAG đầy đủ; hybrid search; reranking
  • [ ] Cập nhật/xoá tài liệu và re-index
  • [ ] Đánh giá RAG bằng số, không bằng cảm tính

GĐ21 — Agents & MCP → chi tiết

  • [ ] Tool calling: khai báo schema, thực thi, trả kết quả về model
  • [ ] Agent loop / ReAct; giới hạn số vòng lặp
  • [ ] Thiết kế tool tốt; guardrails cho tool có tác dụng phụ
  • [ ] MCP primitives + transport; viết được một MCP server đơn giản
  • [ ] Observability cho agent (trace từng bước)

GĐ22 — Capstone → chi tiết

  • [ ] Multi-tenancy + data isolation có test chứng minh
  • [ ] Auth + billing Stripe + webhook idempotent
  • [ ] Usage metering + quota theo gói; cost tracking + model routing
  • [ ] Streaming UX đầu-cuối, có nút Stop
  • [ ] Safety: moderation, chống prompt injection, redact PII trong log
  • [ ] Observability production + alert
  • [ ] Deploy thật, có người dùng thật

Năm cột mốc lớn

Không phải mọi mục đều nặng như nhau. Đây là năm ngưỡng thực sự đổi cấp độ:

1. Ngưỡng "hết ảo giác về database" — hoàn tất GĐ4, tự tìm và fix được N+1 bằng EXPLAIN. Đây là lúc bạn thôi coi DB là "chỗ lưu JSON".

2. Ngưỡng "hết ảo giác về bảo mật" — hoàn tất GĐ8 + ma trận authz ở GĐ12. Cụ thể: khoảnh khắc bạn gỡ một guard ra và thấy test đỏ. Trước đó bạn tin code mình an toàn; sau đó bạn biết.

3. Ngưỡng "hết ảo giác về bất đồng bộ" — GĐ9, khi bạn giết worker giữa lúc chạy 10.000 job và chứng minh được không mất, không trùng. Trước đó bạn nghĩ queue là "gọi hàm chậm hơn"; sau đó bạn hiểu at-least-once.

4. Ngưỡng "hết ảo giác về vận hành" — restore drill ở GĐ13 + rollout dưới tải k6 ở GĐ15 với 0 lỗi. Đây là lúc bạn phân biệt được "chạy được" và "chạy được lúc 3 giờ sáng khi có sự cố".

5. Ngưỡng "tư duy hệ thống" — GĐ16 khi bạn cố ý làm hỏng hệ thống và thấy nó hỏng khác cách bạn đoán, rồi GĐ18 khi bạn chạy k6 tới điểm gãy và nút thắt không phải cái bạn nghĩ. Ai cũng đoán sai lần đầu. Đó chính là bài học.


Tự chấm định kỳ

Ba tháng một lần, quay lại trang này và làm bài kiểm tra thật:

  1. Chọn ngẫu nhiên 5 mục đã tick ✅.
  2. Giải thích thành tiếng, ghi âm lại, hoặc viết ra giấy — không mở tài liệu.
  3. Mục nào ấp úng → hạ xuống 🟡 và đọc lại phần đó.

Kiến thức phai nhanh hơn ta tưởng, và mục "đã học rồi" là chỗ dễ tự lừa mình nhất. Việc hạ một mục từ ✅ xuống 🟡 không phải thụt lùi — đó là checklist đang làm đúng việc của nó.

Học bằng cách build. Chứng minh, đừng tin.