Bài 1 — Database Fundamentals

Đối tượng: Software Engineer → Software Architect Mục tiêu: Hiểu bản chất vật lý và logic của mọi database engine, làm nền tảng để hiểu sâu PostgreSQL, MongoDB và ClickHouse ở các chương sau. 1.1. Problem Statement: Vì sao database là bài toán khó? Về mặt bề ngoài, một database chỉ làm hai việc: ghi dữ liệu xuống và đọc dữ liệu lên. Vậy tại sao nhân loại cần hàng chục năm và hàng trăm engine khác nhau để giải bài toán này? ...

July 2, 2026 · 20 min

Bài 0 — Giới Thiệu Series

Database Engineering — từ First Principles đến quyết định kiến trúc production Dành cho Software Engineer, Backend Engineer, Senior Engineer, Tech Lead, Solution Architect và Software Architect. Viết theo tinh thần Principal Engineer hướng dẫn Senior Engineer: mọi kết luận đều trả lời tại sao, đánh đổi điều gì, và điều gì xảy ra nếu làm ngược lại. Mục lục Chương 1 — Database Fundamentals Nền tảng bắt buộc trước khi đọc các chương sau: Disk vs Memory, Page, Buffer Pool, WAL, Transaction & ACID, Isolation Levels, MVCC, B+Tree vs LSM Tree, Query Execution & Optimizer, Replication, Partitioning vs Sharding, CAP & PACELC, Distributed Systems Fundamentals. Kết chương bằng bản đồ tư duy đối chiếu ba database. ...

July 2, 2026 · 3 min

Bài 15 — Kiến Trúc Thực Tế

Kiến trúc thực tế — Chọn Go hay Node cho 11 loại hệ thống Cấu trúc mỗi mục: đặc tính tải → lựa chọn khuyến nghị → vì sao / vì sao không phương án kia → scale & chi phí → rủi ro chính. Mọi khuyến nghị là mặc định hợp lý, bị ghi đè bởi yếu tố con người (chương 14, mục 2.2). 1. API Gateway Tải: connection-heavy, mọi request của hệ thống đi qua; việc chính là route, authn, rate limit, transform nhẹ — I/O-bound nhưng p99 của gateway cộng vào p99 của tất cả. Khuyến nghị: Go (không ngẫu nhiên mà Kong gateway data plane, Traefik, KrakenD, Tyk là Go/nền C). Lý do: tail latency ổn định (GC sub-ms), một process ăn mọi core → ít hop LB nội bộ, memory/connection thấp cho hàng trăm nghìn kết nối keep-alive. Vì sao không Node: một transform plugin viết ẩu block loop là mọi route cùng chậm — blast radius cực đại đúng chỗ nhạy cảm nhất. Vẫn hợp lý khi: gateway nhẹ, đội JS, cần viết plugin logic nghiệp vụ nhanh (BFF-gateway lai). Thực tế trưởng thành: đừng tự viết nếu nhu cầu là chuẩn (authn, rate limit, routing) — dùng Kong/Envoy/Traefik, chỉ viết plugin. Tự viết gateway chỉ đáng khi transform logic là lợi thế cạnh tranh. Rủi ro chính: gateway tự viết trở thành SPOF + nút cổ chai kiến thức của một người. 2. Authentication Service Tải: RPS cao (mọi service hỏi), CPU-bound thật sự ở điểm nóng: bcrypt/argon2 (cố ý đắt ~50-300ms CPU), ký/verify JWT, TLS. Khuyến nghị: Go. Hash password song song trên mọi core tự nhiên; Node phải đẩy bcrypt qua thread pool libuv (4 thread mặc định — 5 login đồng thời là nghẽn, và nghẽn chung với fs/DNS!) hoặc worker pool tự quản. Node vẫn ổn khi: dùng managed auth (Auth0, Cognito, Keycloak) và service chỉ là lớp mỏng OIDC — lúc này là I/O-bound thuần. Rủi ro chính: không phụ thuộc ngôn ngữ — là crypto tự chế. Dùng thư viện chuẩn, mua/dùng open source (Keycloak, Ory — Ory viết bằng Go) trước khi tự viết. 3. Payment Service Tải: RPS thường thấp (nghìn TPS đã là rất lớn), nhưng đòi hỏi đúng đắn tuyệt đối: idempotency, transaction, audit, reconciliation. I/O-bound (chờ ngân hàng/PSP hàng trăm ms). Khuyến nghị: hòa về hiệu năng — quyết định bằng tính đúng đắn. Go có lợi thế: static typing chặt + kiểu số rõ ràng (và kỷ luật dùng số nguyên minor unit / thư viện decimal — không bao giờ float cho tiền, đúng ở mọi ngôn ngữ; JS Number toàn float nên nguy cơ trượt tay cao hơn, phải ép kỷ luật BigInt/decimal.js). Node có lợi thế: tích hợp PSP (Stripe SDK v.v.) tốt. Kiến trúc quan trọng hơn ngôn ngữ: outbox + idempotency key (chương 6) + saga cho flow nhiều bước + ledger append-only. Một team viết Node kỷ luật ăn đứt team Go cẩu thả ở domain này. Rủi ro chính: xử lý double-charge/mất tiền do retry không idempotent — lỗi thiết kế, không lỗi runtime. 4. Notification Service (email/push/SMS) Tải: consumer từ queue, fan-out lớn, I/O-bound gần tuyệt đối (chờ FCM/APNs/SMTP), bursty (chiến dịch marketing bắn 10M push). Khuyến nghị: cả hai đều tốt — chọn theo đội. Node tự nhiên với template render + SDK provider phong phú. Go tự nhiên với worker pool + rate limit per-provider (chương 3, 6) và tốn ít máy hơn khi burst chục triệu. Điểm kiến trúc quyết định: rate limit theo từng provider (APNs/FCM có quota), retry với DLQ, idempotent theo message ID (gửi trùng notification là lỗi user-facing rất khó chịu), và priority queue (OTP phải vượt mặt marketing). Rủi ro chính: retry storm tự DDoS provider → bị throttle toàn tổ chức. 5. Realtime Chat Tải: connection-heavy (WebSocket giữ lâu), mỗi connection ít việc; fan-out theo room; presence. Khuyến nghị: Go nghiêng nhẹ ở scale lớn. 1M kết nối WebSocket: Go ~vài GB (goroutine + buffer/conn kiểm soát được); Node cần nhiều process/pod hơn đáng kể và GC pressure từ churn message object. Ở scale <50K connection: Node + socket.io năng suất hơn (reconnect, room, fallback có sẵn — Go phải tự ghép từ gorilla/nhold + redis pub/sub). Kiến trúc quan trọng hơn: tách connection layer (stateful, scale theo connection) khỏi business layer (stateless, scale theo RPS); pub/sub (Redis/NATS) nối các node connection; sticky theo user chỉ ở tầng connection. Rủi ro chính: thundering herd khi deploy/restart node connection (100K client reconnect cùng lúc) — cần jitter reconnect phía client + connection draining. 6. Social Network (feed, profile, graph) Tải: read-heavy chênh lệch lớn (đọc:ghi ~100:1), fan-out feed là bài toán riêng, cache là trung tâm kiến trúc. Khuyến nghị: đa ngôn ngữ theo tầng — mẫu hình ngành: API/BFF bằng Node (tốc độ ra tính năng, đội full-stack), tầng nền bằng Go/Java/C++ (feed ranking, fanout worker, media processing). Cãi nhau “một ngôn ngữ cho cả social network” là đặt sai câu hỏi khi hệ đủ lớn. Chi phí: ở scale này hạ tầng » lương — Go ở các đường nóng (feed API đọc 100K RPS) hoàn vốn nhanh. Rủi ro chính: cache stampede khi key nóng hết hạn (singleflight — chương 3/12) và hot partition (celebrity problem) — thiết kế fanout hybrid push/pull. 7. E-commerce Tải: hỗn hợp điển hình: catalog read-heavy + cache tốt; checkout write + transactional; search; ảnh; flash sale = burst ×100. Khuyến nghị: storefront/BFF: Node (SSR Next.js, đội frontend chủ động end-to-end); inventory/checkout/pricing: Go (concurrency có kỷ luật quanh stock — trừ kho chính xác dưới burst là bài toán lock/atomic đúng sở trường; và flash sale cần headroom + load shedding của chương 3). Vì sao không thuần một thứ: thuần Node → flash sale và tính tồn kho là hai điểm run tay; thuần Go → đội frontend bị tách khỏi backend BFF, vòng lặp tính năng chậm. Rủi ro chính: oversell khi flash sale (giải bằng reserve stock atomic — Redis Lua/DB, không giải bằng “hy vọng”). 8. FinTech (core banking, trading, ledger) Tải & yêu cầu: đúng đắn > tất cả; audit; p99 chặt (trading); compliance. Khuyến nghị: Go là mặc định tốt cho core (ledger, matching, risk): static typing, latency ổn, một binary dễ kiểm soát môi trường, ecosystem fintech Go dày (nhiều ngân hàng số lớn xây core bằng Go). Node hợp ở rìa: onboarding flow, dashboard, tích hợp đối tác. Ranh giới cứng: matching engine HFT độ trễ µs → cả Go GC lẫn Node đều không đủ — C++/Rust/FPGA. Biết giới hạn trên của công cụ là năng lực architect. Rủi ro chính: dùng float cho tiền (nhắc lần hai vì nó vẫn cứ xảy ra); và eventual consistency đặt nhầm chỗ — số dư khả dụng không được “eventual”. 9. Streaming Platform (video/audio) Tách bài toán đúng trước khi chọn: (a) serve video = bài toán của CDN + nginx/object storage, không viết tay; (b) transcode = FFmpeg/GPU do worker điều phối; (c) control plane (playlist, DRM license, analytics ingest) = API thường. Khuyến nghị: worker điều phối transcode: Go (quản lý process, pipeline, tận CPU máy worker); control plane: Go hoặc Node đều ổn (I/O-bound); analytics ingest số lớn: Go/Kafka. Rủi ro chính: tự viết cái mà nginx/CDN/FFmpeg đã giải — chi phí cơ hội khổng lồ. 10. Blockchain Infrastructure Thực tế ngành: node/client blockchain lớn viết bằng Go (go-ethereum, Cosmos SDK, nhiều client Bitcoin alt) và Rust (Solana, Polkadot) — cần: crypto CPU-heavy, P2P networking hàng nghìn peer, kiểm soát binary. Node.js không phù hợp cho tầng node/consensus (CPU + GC không kiểm soát đủ). Node.js đúng chỗ ở: tầng ứng dụng — indexer nhẹ, API đọc chain, bot, dApp backend (ecosystem ethers.js/web3.js mạnh nhất ở JS). Rủi ro chính: bug trong code chạm tiền là không thể vá hồi tố; audit + test property-based nặng hơn mọi domain khác. 11. AI Platform Tách tầng: training/inference = Python/C++/CUDA (không bàn); serving & orchestration mới là chỗ Go/Node cạnh tranh. Khuyến nghị: model gateway / inference router (batching, quota, streaming token, A/B model): Go — throughput + streaming + đếm token CPU nhẹ nhưng RPS lớn. Application layer trên LLM (chatbot product, RAG glue, tool-calling): Node/TS rất mạnh — vòng lặp sản phẩm nhanh, SDK AI hạng nhất, streaming SSE/WebSocket tự nhiên với async iterator. Đặc thù đáng chú ý: workload LLM là I/O-bound kiểu mới (chờ GPU service hàng giây, giữ hàng nghìn stream mở) — cả hai đều hợp; Go tiết kiệm connection overhead, Node ghép UX nhanh. Rủi ro chính: capacity planning theo token chứ không theo request — sai đơn vị đo là sai hết autoscale/cost model. Tổng kết mẫu hình (pattern of patterns) Nhìn lại 11 hệ thống, các quy luật lặp lại: ...

July 1, 2026 · 8 min

Bài 14 — So Sánh Golang vs Node.js

So sánh toàn diện Golang vs Node.js Nguyên tắc của chương này: so sánh cơ chế → hệ quả → điều kiện áp dụng, không tuyên bố kẻ thắng chung cuộc. Mọi con số là bậc độ lớn (order of magnitude) để định hướng — con số thật phụ thuộc workload, phải tự benchmark. 1. Bảng tổng hợp Tiêu chí Golang Node.js Ghi chú cơ chế Runtime Model AOT compile → machine code; runtime (GC, scheduler) nhúng trong binary JIT (V8) + event loop (libuv); cần Node runtime cài sẵn AOT = hiệu năng ổn định từ giây đầu; JIT = cần warm-up, peak cao ở hot path ổn định Concurrency Goroutine M:N, preemptive; hàng triệu task; blocking-style; race có thể xảy ra (có race detector) Event loop đơn luồng + async/await; không data race trong JS; CPU đồng thời cần worker/cluster Go: concurrency + parallelism hợp nhất. Node: concurrency dễ, parallelism là tiện ích gắn thêm Throughput (API I/O-bound) Rất cao; 1 process ăn mọi core Cao trên 1 core; cần N process cho N core Cùng workload JSON API, Go thường gấp ~2-5x/instance; Fastify thu hẹp đáng kể so với Express Latency p99 rất ổn (GC sub-ms, không JIT, preemption) p50 tốt; p99 dễ nhiễu (GC pause, deopt, event loop block) Khác biệt lộ rõ nhất ở tail latency dưới tải hỗn hợp Memory Usage Baseline ~5-20MB/process; 1 heap cho mọi core Baseline ~50-150MB/process; ×N process khi cluster Chênh lệch 5-10x là bình thường — thành tiền thật khi chạy trăm instance CPU-bound Tốt (song song thật, gần C trong tầm 10-30%) Yếu — điểm yếu cấu trúc; worker_threads là vá, không phải nền Khác biệt lớn nhất giữa hai nền tảng I/O-bound Rất tốt (netpoller + goroutine) Rất tốt (sở trường nguyên thủy) Ở tiêu chí này hai bên gần nhau nhất — quyết định nên dựa tiêu chí khác Startup Time ~vài ms — chục ms ~50-500ms (tùy lượng require + JIT) Quan trọng với serverless/cold start, CLI, autoscale gấp Scalability Dọc tốt (thêm core là ăn) + ngang tốt Chủ yếu ngang (nhiều process/pod) Cùng đạt đích; Go cần ít instance hơn cho cùng tải Developer Productivity Tốt cho backend thuần; ceremony ít nhưng verbose (err check) Rất cao cho full-stack, prototype, API JSON Node thắng ở tốc độ khởi đầu; khoảng cách hẹp dần theo tuổi codebase Learning Curve Nông và ngắn (ngôn ngữ nhỏ, một cách làm) Vào cửa dễ (JS phổ cập) nhưng master sâu khó (event loop, quirks JS, chọn lựa ecosystem) Nghịch lý: Node dễ bắt đầu hơn, Go dễ giỏi hơn Ecosystem Chuẩn, chất lượng đều, stdlib mạnh; hẹp ở ngoài backend/infra Lớn nhất thế giới, mọi thứ đều có; chất lượng thượng vàng hạ cám, supply-chain risk Go: ít lựa chọn, dễ chọn. Node: nhiều lựa chọn, chọn là kỹ năng Deployment 1 static binary; image từ scratch ~10-20MB Node + node_modules; image ~100-300MB; cần build step (TS) Go đơn giản hơn rõ; Node đã cải thiện (multi-stage, standalone bundle) Operational Complexity Thấp: ít knob, metric runtime sẵn, ít bất ngờ Trung bình: heap limit, thread pool size, event loop cần giám sát riêng, leak thường gặp hơn Chi phí “trông trẻ” dài hạn của Node cao hơn 2. Phân tích sâu các trục quan trọng 2.1. Trục quyết định nhất: bản chất workload CPU-bound thuần ──────────────► Go (hoặc Rust), không bàn cãi CPU + I/O trộn ──────────────► Go thoải mái hơn hẳn (không sợ block) I/O-bound thuần ──────────────► Cả hai tốt → quyết định bằng con người & hệ sinh thái Realtime connection-heavy ────► Cả hai tốt (Go tiết kiệm RAM hơn ở scale rất lớn) Glue code / BFF / tích hợp ───► Node thường nhanh gọn hơnNhầm lẫn phổ biến cần đính chính: “Node nhanh” và “Go nhanh” đều đúng — ở tiêu chí khác nhau. Node đủ nhanh cho tuyệt đại đa số API I/O-bound; Go nhanh hơn ở cùng chi phí hạ tầng và ổn định hơn ở đuôi latency. Với dịch vụ 500 RPS, khác biệt này thường không đáng tiền so với chi phí con người; với 50.000 RPS, nó là hàng chục nghìn USD/tháng. ...

June 29, 2026 · 6 min

Bài 13 — Production Engineering với Node

Production Engineering với Node.js — Error Handling, Graceful Shutdown, Logging, Scaling, Architecture Các pattern chung nền tảng (retry/backoff, circuit breaker, rate limit, idempotency, distributed lock) đã phân tích sâu ở chương 6 — nguyên lý y hệt cho Node (thư viện tương ứng: cockatiel, opossum, rate-limiter-flexible). Chương này tập trung vào phần đặc thù Node. 1. Error Handling — nơi Node khác Go nhiều nhất 1.1. Bản đồ các loại lỗi và đường đi của chúng Loại Đường đi Chính sách đúng Lỗi nghiệp vụ dự kiến (validate fail, not found) throw/reject → catch ở handler Bắt, map sang HTTP status, không log như error Lỗi vận hành (DB timeout, ECONNREFUSED) reject → catch Retry/breaker/fallback + log + metric Programmer error (undefined.foo, assert fail) uncaughtException / unhandledRejection Log rồi crash. Không gượng sống Lỗi trong callback event emitter event 'error' Emitter có thể lỗi PHẢI có listener 'error' — thiếu là crash Vì sao “crash on programmer error” là chính sách đúng (chứ không phải khắt khe): sau một exception không bắt được, process ở trạng thái không xác định — connection dở dang, lock chưa nhả, biến toàn cục hỏng. Gượng chạy tiếp là phục vụ các request sau bằng một process hỏng ngầm. Crash + restart bởi orchestrator = trở về trạng thái sạch trong vài giây. Đây là điểm Node giống Erlang “let it crash” — với điều kiện có process manager và nhiều instance (crash 1/10 pod là non-event; crash pod duy nhất là outage — đừng chạy Node production một instance). ...

June 27, 2026 · 6 min

Bài 12 — Performance Engineering trong Node

Performance Engineering trong Node.js 1. Đặc thù của bài toán hiệu năng Node Nguyên tắc số 0 giống Go (đo trước, tối ưu sau — xem chương 5), nhưng thứ tự nghi phạm khác hẳn. Với Node, khi service chậm, xác suất theo kinh nghiệm: Event loop bị block (CPU trên main thread) — nghi phạm số 1, đặc sản Node. I/O không song song hóa (await tuần tự) hoặc N+1 query. GC pressure / heap gần trần (GC chạy điên cuồng). Thread pool libuv nghẹt (fs/DNS/crypto — chương 8). Code JS thật sự chậm (deopt, megamorphic) — hiếm hơn nhiều so với 4 cái trên. Tối ưu Node hiệu quả = chẩn đoán đúng tầng trước khi sờ vào code. ...

June 25, 2026 · 6 min

Bài 11 — Memory & Garbage Collection trong Node

Memory trong Node.js — V8 Heap, Garbage Collection, Memory Leak 1. Cấu trúc bộ nhớ V8 Process Node ├── V8 Heap ← object JS sống ở đây, GC quản lý │ ├── New Space (1-16MB × 2 semispace) ← object MỚI sinh │ ├── Old Space ← object sống sót 2 lần GC nhỏ │ ├── Large Object Space ← object > ~256KB │ └── Code Space ← machine code JIT ├── External / Off-heap │ └── Buffer! ← Buffer của Node nằm NGOÀI V8 heap ├── Stack (call stack JS, ~1MB) └── libuv, C++ addon, V8 metadataHai điều bất ngờ với người mới vận hành Node: ...

June 23, 2026 · 6 min

Bài 10 — Concurrency & Stream trong Node

Concurrency trong Node — Worker Threads, Cluster, Child Process, Stream & Backpressure 1. Problem Statement Node đơn luồng JS → hai bài toán không tự giải được: Tận dụng nhiều core: máy 16 core, một process Node dùng ~1 core cho JS. 15 core còn lại đứng nhìn. Việc CPU nặng không được chặn loop (chương 9). Node cung cấp ba công cụ với ba mục đích khác nhau — chọn nhầm công cụ là mô típ sai lầm phổ biến: ...

June 21, 2026 · 7 min

Bài 9 — Event Loop Internals

Event Loop Internals — Phases, Microtask/Macrotask, Callback → Promise → Async/Await 1. Problem Statement Event loop là scheduler duy nhất của Node. Trong Go, không hiểu scheduler bạn vẫn sống ổn (runtime che chắn); trong Node, không hiểu event loop thì không debug nổi các sự cố dạng: “setTimeout(100) chạy sau 2 giây”, “một request chậm kéo mọi request chậm”, “process ăn 100% CPU mà không throughput”. Vì mọi thứ chạy trên một luồng, sự công bằng giữa các tác vụ hoàn toàn phụ thuộc việc code của bạn có nhả loop đúng lúc không. ...

June 19, 2026 · 8 min

Bài 8 — Node.js Nền Tảng & V8 Engine

Node.js — Tại sao tồn tại, V8 Engine, Runtime Architecture 1. Problem Statement Bối cảnh 2009: bài toán mà thread không giải nổi Web server thời đó (Apache + PHP, Java servlet) dùng mô hình 1 request = 1 thread/process. Với ứng dụng web hiện đại, mỗi request phần lớn thời gian là chờ: chờ DB (10ms), chờ API ngoài (100ms), chờ client mạng chậm nhả từng KB. Thread bị block khi chờ — nghĩa là: ...

June 17, 2026 · 8 min

Bài 7 — Software Architecture với Go

Software Architecture với Go — DI, Clean/Hexagonal Architecture, DDD, Event-driven 1. Problem Statement Code Go dễ viết — nhưng codebase Go 300.000 dòng sau 3 năm với 20 engineer thì không. Các vấn đề kiến trúc không xuất hiện ở tháng đầu, chúng xuất hiện khi: Đổi Postgres sang DynamoDB phải sửa 200 file — vì logic nghiệp vụ gọi thẳng SQL. Không unit test được vì mọi hàm đều chạm DB thật. Hai team sửa cùng một package “common” và block lẫn nhau hàng tuần. Kiến trúc là câu trả lời cho câu hỏi: thay đổi nào sẽ đến, và ta trả giá bao nhiêu khi nó đến? Mọi pattern dưới đây đều là biến thể của một nguyên lý duy nhất: tách cái thay đổi thường xuyên (chi tiết hạ tầng) khỏi cái ổn định (logic nghiệp vụ), qua ranh giới là interface. ...

June 15, 2026 · 8 min

Bài 6 — Production Engineering với Go

Production Engineering với Go — Resilience Patterns Graceful Shutdown, Retry, Circuit Breaker, Rate Limiter, Idempotency, Distributed Lock 1. Problem Statement Code chạy đúng trên máy dev chỉ là 30% công việc. Production khác ở chỗ: mọi thứ đều fail — network đứt giữa chừng, downstream chậm, pod bị kill bất kỳ lúc nào, request bị gửi hai lần. Các pattern trong chương này tồn tại vì một sự thật: hệ thống phân tán không thể tránh lỗi, chỉ có thể thiết kế để lỗi không lan rộng. ...

June 13, 2026 · 9 min

Bài 5 — Performance Engineering trong Go

Performance Engineering trong Go — Benchmark, pprof, Optimization 1. Problem Statement — và nguyên tắc số 0 Nguyên tắc số 0: không đo thì không tối ưu. Trực giác về hiệu năng sai nhiều hơn đúng, kể cả với engineer giỏi. Chi phí của tối ưu mù: code khó đọc hơn để “tối ưu” một đoạn chiếm 0.1% thời gian, trong khi 80% thời gian nằm ở một query N+1 không ai nhìn. ...

June 11, 2026 · 6 min

Bài 4 — Go Runtime Internals

Go Runtime Internals — Memory Allocator, GC Internals, Netpoller, Sysmon Chương này đào sâu phần “bên dưới mui xe” đã phác thảo ở chương 1-2. Đối tượng: senior+ cần debug các vấn đề hiệu năng mà tài liệu bề mặt không giải thích được. 1. Vì sao phải hiểu runtime Ba loại sự cố production chỉ giải thích được khi hiểu runtime: “Service dùng 2GB RSS nhưng heap profile chỉ thấy 800MB” → allocator giữ memory, page chưa trả OS, fragmentation. “p99 tăng vọt định kỳ mỗi 2 phút” → GC cycle, hoặc forcegcperiod (GC cưỡng bức mỗi 2 phút khi nhàn rỗi). “CPU 30% nhưng throughput không tăng khi thêm load” → GC assist, lock contention trong runtime, netpoller. 2. Memory Allocator 2.1. Thiết kế: phỏng theo TCMalloc Bài toán: malloc toàn cục có lock → N core tranh nhau một lock → allocation trở thành điểm nghẽn. Go giải bằng phân cấp 3 tầng: ...

June 9, 2026 · 7 min

Bài 3 — Concurrency Patterns

Concurrency Patterns — Worker Pool, Pipeline, Fan-in/Fan-out, Backpressure 1. Problem Statement Goroutine và channel là nguyên liệu, không phải kiến trúc. Bài toán thực tế của mọi hệ thống concurrent: Giới hạn tài nguyên: downstream (DB, API bên thứ ba) chỉ chịu được N kết nối đồng thời. Spawn goroutine tự do = tự DDoS downstream của chính mình. Chênh lệch tốc độ: producer sinh 10K item/s, consumer xử lý 2K item/s. Không xử lý → memory phình → OOM. Điều phối kết quả: chia việc cho N worker rồi gom kết quả, xử lý lỗi từng phần, hủy toàn bộ khi một phần fail. Các pattern trong chương này là câu trả lời chuẩn hóa cho ba bài toán trên. ...

June 7, 2026 · 8 min

Bài 2 — Goroutine, Scheduler & GPM Model

Concurrency — Goroutine, Scheduler và GPM Model 1. Problem Statement Bài toán C10K → C10M Một server hiện đại cần phục vụ 10.000 → 10.000.000 kết nối đồng thời. Với mô hình “1 kết nối = 1 OS thread”: Memory: 10.000 thread × 8MB stack (mặc định Linux) = 80GB chỉ cho stack. Bất khả thi. Context switch: chuyển đổi thread qua kernel tốn 1-10µs (lưu/khôi phục register, TLB flush, cache pollution). Với 10K thread active, CPU dành phần lớn thời gian cho việc chuyển đổi thay vì làm việc. Scheduler kernel không biết gì về ngữ nghĩa ứng dụng — nó schedule công bằng theo time-slice, không theo “goroutine này đang chờ I/O”. Các giải pháp cũ và giới hạn Giải pháp Cách hoạt động Giới hạn Thread pool (Java cổ điển) Số thread cố định + queue Thread bị block bởi I/O vẫn chiếm chỗ; sizing pool là nghệ thuật đen Callback / event loop (Node.js, nginx) 1 thread + non-blocking I/O Callback hell; 1 tính toán nặng chặn toàn bộ; code bất đồng bộ “lây nhiễm” (colored functions) Async/await (C#, Rust, JS) Compiler biến đổi thành state machine Vẫn chia thế giới thành sync/async (“function coloring”); ecosystem phải async hết Go chọn hướng thứ ba: viết code tuần tự, blocking như bình thường — runtime lo việc biến blocking thành non-blocking bên dưới. Không có async/await, không có màu hàm. Đây là điểm bán hàng lớn nhất của Go. ...

June 5, 2026 · 10 min

Bài 1 — Triết Lý Go & Nền Tảng Ngôn Ngữ

Triết lý thiết kế Go và nền tảng ngôn ngữ Level 1 → Level 4: Từ “tại sao Go tồn tại” đến quyết định kiến trúc production. 1. Problem Statement Bài toán Go giải quyết Năm 2007, Google đối mặt với ba vấn đề mà không ngôn ngữ nào lúc đó giải quyết trọn vẹn: Thời gian compile quá lâu. Codebase C++ hàng chục triệu dòng của Google mất 45 phút đến vài giờ để build. Nguyên nhân kỹ thuật: mô hình #include của C/C++ khiến mỗi translation unit phải parse lại hàng trăm nghìn dòng header. Một file header có thể bị include hàng nghìn lần trong một lần build. ...

June 3, 2026 · 13 min

Bài 0 — Giới Thiệu

Golang & Node.js — Tài liệu chuyên sâu cho Backend Engineer Bộ tài liệu 15 chương, viết theo tư duy Problem → Tại sao tồn tại → Cơ chế bên trong → Trade-off → Production → Anti-pattern → Khi nào không dùng. Mục tiêu không phải dạy cú pháp — mà giúp bạn hiểu bản chất, phân tích bottleneck và tự đưa ra quyết định kỹ thuật có cơ sở. ...

June 1, 2026 · 4 min

Bài 11 (Nâng cao) — Cơ chế lưu trữ: vì sao Kafka nhanh

Tốc độ Kafka đến từ bốn quyết định thiết kế cộng hưởng, không phải một mẹo. 1. Ghi tuần tự (sequential write) Trực giác sai: “ghi đĩa chậm, phải dùng RAM”. Thực tế: ghi đĩa tuần tự nhanh hơn ghi RAM ngẫu nhiên. Random access (DB cập nhật chỗ này chỗ kia): tốn seek, chậm cả HDD lẫn SSD. Sequential write: chỉ ghi nối tiếp vào cuối file, không seek — HDD ~100s MB/s, SSD cao hơn. Kafka chọn log append-only: message mới luôn ghi vào cuối, không sửa/chèn. Đây là lý do sâu xa Kafka không cho xóa/sửa message — ràng buộc có chủ đích để đổi lấy tốc độ. ...

May 25, 2026 · 4 min

Bài 10 — Kiến trúc thực chiến

Outbox Pattern — giải bài toán “dual write” Service order phải ghi vào DB và bắn event lên Kafka. Làm tuần tự thì luôn có khe hở: ghi DB OK -> bắn Kafka FAIL => DB có đơn, hệ thống khác không biết (lệch) bắn Kafka OK -> ghi DB FAIL => event bắn rồi nhưng đơn không tồn tại (ma)Hai hệ thống khác nhau không chia sẻ transaction → không thể “cùng thành công hoặc cùng thất bại” trực tiếp. ...

May 23, 2026 · 4 min

Bài 9 — Performance Tuning

Mọi tinh chỉnh là đánh đổi giữa throughput (số lượng/giây), latency (độ trễ mỗi message), và durability. Không có nút “nhanh hơn” miễn phí. Phía Producer — gom để nhanh linger.ms + batch.size (cặp quan trọng nhất). Mặc định linger.ms=0 = gửi ngay từng message → nhiều request nhỏ. Tăng linger.ms (5–20ms) cho producer chờ gom batch rồi gửi một lần. batch.size (mặc định 16KB) là trần mỗi batch. ...

May 20, 2026 · 3 min

Bài 8 — Schema Registry & Schema Evolution

Vấn đề Topic được nhiều team ghi/đọc. Một ngày team A đổi cấu trúc message (đổi tên field, đổi kiểu, bỏ field) → consumer của B, C, D vỡ ngay, thường lúc 2h sáng. JSON tự do có 3 điểm yếu ở quy mô lớn: không có schema bắt buộc, tốn băng thông (lặp tên field), không kiểm soát thay đổi. Schema Registry hoạt động thế nào Service riêng lưu và đánh version cho schema: ...

May 17, 2026 · 3 min

Bài 7 — Retry, Backoff & Dead Letter Queue

Triết lý: fail gracefully, đừng fail catastrophically. Một message hỏng là chuyện bình thường; mục tiêu là nó không kéo cả partition/hệ thống sập. Hai loại lỗi — phân biệt được mới xử lý đúng Tạm thời (transient): DB timeout, API phụ thuộc down, mạng chập chờn. → retry sẽ qua. Vĩnh viễn (permanent / “poison”): sai format, thiếu field, vi phạm nghiệp vụ. → retry vô ích, đẩy đi chỗ khác. Ánh xạ HTTP status hữu ích: ...

May 14, 2026 · 3 min

Bài 6 — Idempotent Producer & Transactions (Exactly-Once)

Vấn đề 1 — Duplicate do PRODUCER retry Producer gửi message, broker đã ghi thành công nhưng gói ACK rớt trên đường về. Producer tưởng thất bại → retry → broker ghi lần hai. Message nằm 2 lần trong log dù bạn làm đúng mọi thứ. (Khác với duplicate phía consumer ở Bài 4.) Giải pháp — Idempotent Producer enable.idempotence=true: broker cấp mỗi producer một PID và mỗi message một sequence number theo partition. Retry mang sequence đã thấy → broker lặng lẽ bỏ qua. Thêm bảo đảm thứ tự không bị đảo khi retry. ...

May 12, 2026 · 3 min

Bài 5 — Replication, acks & ISR

Replica, Leader, Follower Mỗi partition có N bản sao (replication-factor): 1 leader: phục vụ mọi đọc/ghi của client. N-1 follower: liên tục kéo dữ liệu từ leader để bắt kịp. Ẩn với client. Client chỉ làm việc với leader. Follower chỉ để chống mất dữ liệu khi broker chứa leader chết. ISR (In-Sync Replicas) Tập các replica đang theo kịp leader (gồm cả leader). Follower tụt quá replica.lag.time.max.ms (~30s) bị đá khỏi ISR. Khi leader chết, Kafka chỉ bầu leader mới từ trong ISR (vì chỉ chúng chắc chắn có đủ dữ liệu đã xác nhận). ...

May 10, 2026 · 3 min

Bài 4 — Offset & Delivery Semantics

Bài quan trọng nhất về độ tin cậy. Hầu hết bug “mất message” / “xử lý trùng” ở production nằm ở đây. Offset commit là gì? Consumer cần báo cho Kafka biết đã xử lý đến đâu, để lần sau (hoặc consumer khác sau rebalance) đọc tiếp đúng chỗ. Việc đó gọi là commit offset, lưu trong topic nội bộ __consumer_offsets. Mấu chốt sinh ra mọi vấn đề: xử lý message và commit offset là hai hành động riêng biệt, giữa chúng consumer có thể crash. Thứ tự hai hành động này quyết định “delivery semantics”. ...

May 8, 2026 · 3 min

Bài 3 — Partitioning & Đảm bảo thứ tự

Producer chọn partition thế nào? Có key: partition = hash(key) % số_partition. Cùng key + số partition không đổi → luôn cùng partition → giữ thứ tự. Không key: rải đều (sticky/round-robin), tối ưu throughput nhưng không đảm bảo thứ tự. Ba cạm bẫy quanh partitioning Bẫy 1 — Hàm hash khác nhau giữa các client Client Java dùng murmur2; librdkafka (confluent-kafka-go, Python, C…) mặc định dùng thuật toán khác (consistent_random, nền CRC32). → Cùng một key, producer Java và producer Go có thể đẩy vào hai partition khác nhau! ...

May 6, 2026 · 3 min

Bài 2 — Consumer Group & Rebalance

Khái niệm Consumer group là tập các consumer cùng group.id, hợp sức tiêu thụ một topic. Kafka tự chia partition cho các thành viên — gọi là partition assignment. Quan hệ then chốt: Trong 1 group: 1 partition -> tối đa 1 consumer (KHÔNG phải 1-n) 1 consumer -> có thể N partition Trên nhiều group: 1 partition -> N consumer (mỗi group 1 đứa) (pub/sub)Hệ quả thực chiến: số consumer hữu ích tối đa trong một group = số partition. Topic 3 partition, group 5 consumer → 3 đứa làm việc, 2 đứa ngồi không. Partition là trần scale của consumer — muốn xử lý song song hơn phải tăng partition trước. ...

May 3, 2026 · 3 min

4 Nguyên Tắc Vàng Khi Dùng Index Mà Mình Ước Gì Biết Sớm Hơn

Chào mọi người, trong quá trình làm việc với database, mình thấy rất nhiều trường hợp không hiểu rõ bản chất của index dẫn đến không sử dụng index một cách hiệu quả. Hôm nay, tranh thủ dịp lễ 1/5 không được đi chơi, ở nhà thôi thì ngồi viết một bài blog về vấn đề này vậy. (Lưu ý: Bài viết mình có tham khảo AI sau khi viết để chỉnh chu lại văn phong và ngữ pháp cũng như là verify lại kiến thức của mình nhé, nào gét gô) ...

May 1, 2026 · 13 min

Bài 1 — Topic, Partition, Producer & Consumer

Khái niệm nền tảng Topic là một “kênh” logic chứa message, ví dụ orders chứa các sự kiện đơn hàng. Partition là điểm mấu chốt phân biệt Kafka với hàng đợi thường. Mỗi topic được chia thành nhiều partition. Mỗi partition là một log ghi nối tiếp (append-only): message có thứ tự tuyệt đối trong partition, và mỗi message mang một số thứ tự gọi là offset (0, 1, 2, …). ...

May 1, 2026 · 3 min