Skip to content

HARRY-29 — GĐ01 — Systems Foundation: OS, Linux, Database Internals & Networking

GĐ01 — Systems Foundation: OS, Linux, Database Internals & Networking ​

Backend chạy trên operating system, nói chuyện qua network và lưu dữ liệu bằng database. Giai đoạn này xây mental model đủ dùng để debug production; không nhằm đào tạo kernel, network hay database engineer.

Học song song với GĐ02–05. Mỗi khái niệm phải gắn với một quan sát hoặc thí nghiệm trên máy.


Phần A — Operating System ​

1. Process và Thread ​

Process có không gian địa chỉ và tài nguyên riêng. Thread chia sẻ bộ nhớ của process nhưng có stack và trạng thái thực thi riêng.

Trong Node.js:

  • JavaScript thường chạy trên một main thread.
  • libuv dùng thread pool cho một số tác vụ như filesystem, DNS và crypto.
  • worker_threads phù hợp CPU-bound JavaScript.
  • child_process tạo process riêng, isolation mạnh hơn nhưng giao tiếp đắt hơn.

Cần trả lời được:

  • Vì sao một vòng lặp CPU-bound chặn mọi request trên Node process?
  • Khi nào tăng số worker giúp, khi nào chỉ làm database quá tải?
  • Process crash ảnh hưởng gì tới memory và connection đang giữ?

2. Scheduling và Context Switch ​

OS scheduler chia CPU time cho thread/process có thể chạy. Context switch lưu trạng thái tác vụ hiện tại và khôi phục tác vụ khác.

Nhiều concurrency không đồng nghĩa nhiều throughput:

  • Quá nhiều runnable thread làm tăng context switch.
  • Quá nhiều request đồng thời làm queue dài và tăng latency.
  • CPU utilization cao kéo dài thường làm tail latency tăng mạnh.

Thực hành: chạy một workload CPU-bound bằng một process, nhiều process và worker thread; đo throughput, p95 và CPU thay vì chỉ cảm nhận.


3. Virtual Memory, Stack và Heap ​

Virtual memory cho mỗi process một không gian địa chỉ riêng, được OS ánh xạ sang physical memory.

  • Stack: call frame, biến cục bộ, lifetime theo lời gọi hàm.
  • Heap: object có lifetime linh hoạt, do runtime/garbage collector quản lý.
  • RSS: lượng memory process đang giữ trong RAM, không đồng nhất với V8 heap.
  • Swap: có thể tránh crash tức thì nhưng latency rất xấu cho server.

Trong Node, cần quan sát:

  • process.memoryUsage().
  • Heap growth qua thời gian.
  • Object bị giữ tham chiếu ngoài ý muốn.
  • Buffer/native memory không xuất hiện đầy đủ trong heapUsed.

4. System Call và File Descriptor ​

Application vào kernel qua system call để đọc file, mở socket, tạo process và thực hiện I/O.

File descriptor là handle số đại diện cho file, socket, pipe và nhiều tài nguyên I/O khác.

Backend có thể cạn file descriptor vì:

  • Không đóng file/socket.
  • Connection pool đặt quá lớn.
  • Client không reuse connection.
  • Server giữ quá nhiều connection nhàn rỗi.

Khi gặp EMFILE hoặc too many open files, tăng limit chỉ là chữa triệu chứng nếu code đang leak descriptor.


Phần B — Linux Fundamentals ​

5. Filesystem, Permission và User ​

Cần nắm:

  • Absolute vs relative path.
  • File, directory, symbolic link.
  • Owner/group/other và quyền read/write/execute.
  • chmod, chown, umask ở mức dùng được.
  • Vì sao container/app không nên chạy bằng root.
  • Log và uploaded file cần owner/permission phù hợp.

Không commit secret hoặc .env; permission không thay thế secret manager.


6. Process, Signal và Exit Code ​

Các signal quan trọng:

  • SIGTERM: yêu cầu shutdown có kiểm soát.
  • SIGINT: thường từ Ctrl+C.
  • SIGKILL: dừng ngay, process không cleanup được.
  • SIGHUP: truyền thống dùng cho reload terminal/config tuỳ application.

Exit code 0 nghĩa là thành công; khác 0 báo lỗi cho shell, CI và orchestrator.

Backend nhận SIGTERM phải:

  1. Ngừng nhận request mới.
  2. Chờ request/job đang chạy trong giới hạn thời gian.
  3. Đóng DB, queue và telemetry connection.
  4. Thoát với code phù hợp.

7. Pipe, Redirect và Shell ​

Shell là công cụ quan sát production, không chỉ nơi chạy npm start.

Cần dùng được:

  • Pipe output giữa các command.
  • Redirect stdout/stderr.
  • Environment variable theo process.
  • Tìm process và port đang lắng nghe.
  • Đọc log lớn bằng filter thay vì mở cả file.
  • Hiểu quoting để tránh command injection trong script.

Nguyên tắc: production automation cần script có set -euo pipefail, input được quote, exit code rõ và không in secret.


Phần C — Database Internals ​

8. Page, B-tree và Index ​

Database đọc/ghi theo page, không theo từng field độc lập. B-tree giảm số page cần đọc bằng một cây thấp, nhiều nhánh.

Cần hiểu:

  • Index lookup thường gần O(log n) nhưng cost thật phụ thuộc I/O và cache.
  • Composite index tuân theo thứ tự cột.
  • Index-only scan cần dữ liệu phù hợp và visibility information.
  • Nhiều index làm write chậm hơn và tốn storage.
  • Low-cardinality column đứng một mình thường không chọn lọc tốt.

Luôn kiểm bằng EXPLAIN (ANALYZE, BUFFERS), không phỏng đoán từ tên index.


9. WAL và Durability ​

Write-Ahead Log ghi mô tả thay đổi bền vững trước khi data page được flush.

WAL giúp:

  • Recovery sau crash.
  • Replication.
  • Point-in-time recovery khi kết hợp base backup và archive phù hợp.

COMMIT thành công không nhất thiết nghĩa mọi data page đã được ghi xuống vị trí cuối; nó nghĩa durability contract của database/WAL đã được đáp ứng theo cấu hình.


10. MVCC và Vacuum ​

MVCC cho transaction đọc snapshot mà không chặn mọi writer. PostgreSQL tạo version mới của row khi update thay vì sửa tại chỗ theo cách đơn giản.

Hệ quả:

  • Transaction giữ quá lâu giữ lại old row versions.
  • Dead tuples làm table/index phình.
  • Autovacuum là thành phần correctness và operations quan trọng, không phải housekeeping tuỳ chọn.
  • “Không có lock ở application” không nghĩa database không quản lý concurrency.

11. Locking, Isolation và Anomaly ​

Cần phân biệt:

  • Row lock, table lock, advisory lock.
  • Read Committed, Repeatable Read, Serializable.
  • Dirty read, non-repeatable read, phantom, lost update, write skew.
  • Blocking vs deadlock.

Khi deadlock xảy ra, database huỷ một transaction để phá vòng chờ. Application phải rollback và retry có giới hạn nếu operation an toàn để retry.

Thực hành: mở hai session PostgreSQL, cố ý tạo lost update và deadlock; quan sát lock và sửa bằng transaction/locking phù hợp.


Phần D — Networking sâu hơn ​

12. Socket Lifecycle ​

Một TCP server đi qua các bước chính:

  1. Tạo socket.
  2. Bind địa chỉ/port.
  3. Listen.
  4. Accept connection.
  5. Read/write.
  6. Close.

TCP connection được nhận diện bởi source IP/port và destination IP/port. Sau khi đóng, trạng thái như TIME_WAIT tồn tại để xử lý packet trễ và tránh nhầm connection cũ.


13. Connection Pool và Connection Exhaustion ​

Mỗi connection có chi phí ở client, server, proxy và database.

Nếu mỗi application instance mở pool 20 connection và scale lên 20 instance, database có thể nhận tới 400 connection chưa tính worker và migration.

Nguyên tắc:

  • Budget connection trên toàn hệ thống, không riêng từng process.
  • Queue có giới hạn tốt hơn mở connection vô hạn.
  • Đặt acquire timeout.
  • Theo dõi active, idle, waiting và saturation.
  • Scale app phải xem lại DB pool.

14. Keep-Alive và Timeout Budget ​

Keep-alive tái sử dụng connection, giảm chi phí TCP/TLS handshake. Nhưng connection nhàn rỗi cũng giữ tài nguyên.

Các timeout phải phối hợp:

  • Client request timeout.
  • Load balancer/proxy timeout.
  • Server timeout.
  • Database acquire/query timeout.
  • Upstream API timeout.

Timeout ngoài nên lớn hơn timeout trong vừa đủ để tầng trong có cơ hội fail rõ ràng; không đặt mọi tầng cùng một con số.


15. HTTP/1.1, HTTP/2 và HTTP/3 ​

HTTP/1.1 ​

  • Nhiều request có thể reuse connection với keep-alive.
  • Browser thường mở nhiều connection để tăng song song.
  • Pipelining ít được dùng rộng rãi vì head-of-line blocking và complexity.

HTTP/2 ​

  • Multiplex nhiều stream trên một TCP connection.
  • Header compression.
  • TCP packet loss vẫn có thể ảnh hưởng mọi stream trên connection.

HTTP/3 ​

  • Chạy trên QUIC/UDP.
  • Stream độc lập hơn khi packet loss.
  • Connection migration hữu ích khi client đổi mạng.
  • Deployment, proxy và observability phức tạp hơn.

Đối với application engineer, hiểu trade-off và kiểm tra support của CDN/load balancer là đủ; không cần tự triển khai protocol.


Thực hành bắt buộc ​

  1. Viết Node server xử lý SIGTERM và chứng minh request đang chạy hoàn tất.
  2. Quan sát PID, port, open file/socket và memory của server.
  3. Cố ý tạo CPU-bound route, đo ảnh hưởng lên request khác, sau đó chuyển sang worker thread.
  4. Benchmark HTTP request với keep-alive bật và tắt.
  5. Làm cạn một connection pool có giới hạn, quan sát waiting và timeout.
  6. Tạo index PostgreSQL, so sánh EXPLAIN (ANALYZE, BUFFERS) trước/sau.
  7. Tạo deadlock bằng hai transaction và ghi lại thứ tự lock đúng để tránh nó.
  8. Quan sát một long-running transaction ảnh hưởng vacuum/dead tuples trong môi trường local.

Done khi ​

  • [ ] Giải thích được process, thread, event loop và worker thread khác nhau thế nào.
  • [ ] Phân biệt stack, V8 heap, native memory và RSS.
  • [ ] Chẩn đoán được process giữ port hoặc cạn file descriptor.
  • [ ] Dùng được signal để graceful shutdown.
  • [ ] Đọc được permission Linux và không chạy application bằng root khi không cần.
  • [ ] Giải thích B-tree, WAL và MVCC bằng ngôn ngữ của mình.
  • [ ] Tạo và sửa được deadlock/lost update trong PostgreSQL local.
  • [ ] Tính được connection budget khi application scale nhiều instance.
  • [ ] Giải thích keep-alive và timeout budget.
  • [ ] So sánh HTTP/1.1, HTTP/2 và HTTP/3 theo vấn đề chúng giải quyết.
  • [ ] Hoàn thành tám thí nghiệm và lưu code/kết quả đo.

Câu hỏi mở / chưa giải quyết ​

  • Production target chính là PaaS, container trên VPS hay Kubernetes?
  • Database managed đang dùng giới hạn connection và backup/WAL như thế nào?

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

Đồng bộ tiến độ

Đăng nhập trực tiếp để tiếp tục học trên desktop và PWA.