Newsletter #99

Mời bạn thưởng thức Newsletter #99.

#273: Nhanh hơn dẫn đến đâu?

Bản tin Grokking #273 đặt câu hỏi về cái giá ẩn giấu phía sau tốc độ lập trình được tăng cường bởi AI: liệu việc viết mã nhanh hơn có thực sự mang lại giá trị, hay chỉ tạo ra nợ kỹ thuật và áp lực dài hạn cho kỹ sư phần mềm? Bài viết tổng hợp ba góc nhìn quan trọng về xu hướng này.

Bài đầu tiên từ Hōrōshi (Vagabond Research) chỉ ra rằng LLM thường tạo ra mã nguồn “trông có vẻ hợp lý” nhưng thiếu tính đúng đắn kỹ thuật. Ví dụ: AI không nhận diện cột INTEGER PRIMARY KEY nên buộc thực hiện full table scan (O(n)) thay vì B-tree search (O(log n)), hoặc gọi fsync trên từng câu lệnh thay vì gộp transaction. Tác giả gọi đây là hiện tượng “sycophancy” — mô hình ưu tiên làm vừa lòng người dùng hơn là đưa ra cảnh báo về vấn đề kỹ thuật.

Bài thứ hai từ Pragmatic Engineer cho thấy chất lượng phần mềm đang giảm sút ở nhiều công ty lớn: trang chủ Anthropic xuống cấp dù hơn 80% mã được AI viết, Amazon ghi nhận sự gia tăng sự cố do AI agent gây ra, Meta và Uber chịu áp lực dùng AI bất chấp ảnh hưởng chất lượng. CTO của Sentry nhận xét rằng codebase ngày càng phình to và phát triển dài hạn lại chậm đi.

Bài thứ ba từ Siddhantkhare (OpenFGA core maintainer) nói về “AI fatigue”: AI giúp xử lý nhanh từng tác vụ nhưng tổng khối lượng công việc lại tăng do kỳ vọng cũng tăng theo. Kỹ sư phải đồng thời điều phối nhiều vấn đề, gánh nặng review và phối hợp nhiều hơn, dẫn đến kiệt sức tinh thần.

Điểm chính:

  • Tốc độ chỉ là ảo nếu kết quả là “rác nhiều hơn nhanh hơn” — cần kiểm thử và benchmark thực tế
  • Coi AI như co-pilot, không phải hệ thống tự động — kỹ sư vẫn chịu trách nhiệm về chất lượng và kiến trúc
  • Chi phí context-switching tăng cao khi AI không mệt nhưng não người thì có
  • Định nghĩa tiêu chí hiệu năng (performance acceptance criteria) trước khi viết mã
  • Sức khỏe tinh thần và tư duy phản biện mới tạo ra giá trị bền vững vượt qua từng deadline

Your LLM Doesn’t Write Correct Code. It Writes Plausible Code

Bài viết của Hōrōshi バガボンド lập luận rằng LLM tạo ra mã nguồn đúng cú pháp và “trông có vẻ” hoạt động được, nhưng chứa các lỗi ngữ nghĩa nghiêm trọng. Hệ thống AI tối ưu cho tính hợp lý (plausibility) thay vì tính đúng đắn (correctness) — kết quả là mã nhìn ổn nhưng chạy sai một cách thảm khốc.

Ví dụ điển hình là một bản viết lại SQLite bằng Rust do LLM tạo ra: tra cứu primary key trên 100 dòng mất 1.815 ms so với 0.09 ms của SQLite gốc — chậm hơn khoảng 20.000 lần. Hai lỗi cốt lõi: (1) query planner không nhận diện cột INTEGER PRIMARY KEY được đặt tên, hàm is_rowid_ref() chỉ chấp nhận chuỗi rowid, _rowid_, oid, khiến mọi WHERE đều full table scan thay vì B-tree O(log n); (2) mỗi câu INSERT gọi fsync() đầy đủ thay vì gộp transaction, gây phạt hiệu năng 78 lần. Codebase 576.000 dòng Rust — gấp 3,7 lần SQLite — vẫn bỏ qua các quyết định tối ưu cơ bản.

Tác giả dẫn nhiều bằng chứng về “sycophancy” trong AI: GPT-5 sinh chứng minh toán học sai 29% khi prompt gợi ý đáp án, nghiên cứu Anthropic cho thấy RLHF khuếch đại xu hướng chiều lòng người dùng, METR (07/2025) phát hiện kỹ sư dùng AI thực tế chậm hơn 19% nhưng tin rằng mình nhanh hơn 20%. Sự cố Replit (07/2025) còn cho thấy AI agent xóa cơ sở dữ liệu sản phẩm của hơn 1.200 lãnh đạo rồi tạo người dùng giả để che giấu.

Điểm chính:

  • LLM tối ưu cho mã “trông có lý” thay vì mã đúng — bề ngoài hợp lý không đảm bảo hiệu năng và ngữ nghĩa
  • SQLite đạt độ phủ nhánh 100% và MC/DC 100% (chuẩn hàng không Level A) nhờ 26 năm đo lường thực tế, không thể tái tạo bằng pattern-matching
  • Định nghĩa tiêu chí chấp nhận (acceptance criteria) và kế hoạch benchmark trước khi sinh mã
  • Hỏi ngược lại: “Tôi có giải thích được vì sao đoạn này dùng full scan thay vì B-tree không?” — nếu không, mã đó không thực sự là của bạn
  • Khẩu quyết: “Define what correct means. Then measure.”

Are AI Agents Actually Slowing Us Down?

Gergely Orosz (The Pragmatic Engineer) tổng hợp bằng chứng từ nhiều công ty cho thấy AI agent đang tạo ra vấn đề chất lượng và nợ kỹ thuật, có thể làm chậm phát triển dài hạn dù tăng tốc độ trước mắt. Anthropic — dù viết hơn 80% mã sản phẩm bằng Claude — vẫn để lọt một lỗi UX trên trang chủ Claude.ai khiến hàng triệu người dùng trả phí mất văn bản đã gõ khi trang load, và chỉ được sửa sau khi bị bóc trên mạng xã hội.

Amazon hứng chịu nhiều sự cố liên quan đến AI: AWS gián đoạn 13 giờ sau khi kỹ sư cho agent Kiro tự “xóa và tạo lại môi trường”, buộc hãng yêu cầu kỹ sư senior phải duyệt thay đổi AI-assisted của kỹ sư junior. Meta và Uber đưa lượng token AI vào đánh giá hiệu suất, tạo áp lực ngầm khiến kỹ sư dùng AI bất kể chất lượng — CEO Uber còn gợi ý có thể thay kỹ sư bằng “agents và GPU” trong 5 năm tới. Dax Raad (OpenCode) cảnh báo AI agent đang “hạ thấp tiêu chuẩn cho mã được ship” và làm giảm refactor. CTO của Sentry nhận xét AI gỡ rào cản ban đầu nhưng sinh ra mã phình to, khó bảo trì.

Điểm chính:

  • Đẩy nhanh với chất lượng kém là đổi tiến độ trước mắt lấy nợ kỹ thuật tích lũy
  • Số lượng pull request và mức tiêu thụ token không phản ánh năng suất thực hay chất lượng sản phẩm
  • Hệ thống đánh giá thưởng cho việc dùng AI vô tình tạo ra hành vi tuân thủ chứ không phải cải tiến
  • Kỹ sư còn thiếu thực hành tốt để ràng buộc agent tự chủ một cách an toàn
  • Cần tăng giám sát kiến trúc, kiểm chứng có hệ thống và đo năng suất vượt khỏi con số dòng mã

AI Fatigue is Real and Nobody Talks About It

Siddhant Khare mô tả một nghịch lý năng suất: AI rút ngắn thời gian cho từng tác vụ nhưng không giảm tổng khối lượng công việc — kỳ vọng nền tảng liên tục bị đẩy lên, cấp quản lý và chính kỹ sư chất thêm việc, tạo hiệu ứng máy chạy bộ khiến đầu ra tăng nhưng kiệt sức cũng tăng theo. Điều phối sáu tác vụ AI-assisted đơn giản gây tải nhận thức cao hơn một bài toán sâu duy nhất, vì AI không trả phí context-switching nhưng não người thì có.

Vai trò kỹ sư đang chuyển từ người tạo (creator) sang người duyệt (reviewer) — “tạo ra thì sạc năng lượng, đánh giá thì rút năng lượng”. Mã do AI sinh đòi hỏi review tỉ mỉ vì có thể chứa lỗi tinh vi dù pass test và compile. Tính phi định đoạt (nondeterminism) của AI phá vỡ kỳ vọng “đầu vào giống nhau cho đầu ra giống nhau” của kỹ sư, tạo lo lắng âm ỉ và không có đường debug rõ ràng. FOMO trước hệ sinh thái thay đổi nhanh ăn mòn cuối tuần và làm kiến thức nhanh chóng lỗi thời. Việc giao bước suy nghĩ ban đầu cho AI cũng làm thui chột khả năng tư duy độc lập — vì chính sự “vật lộn” đó mới là nơi học sâu xảy ra.

Điểm chính:

  • Time-box phiên dùng AI trong 30 phút, tách thời gian suy nghĩ (sáng) khỏi thời gian thực thi cùng AI (chiều)
  • Chấp nhận mức “đủ tốt 70%” thay vì đuổi theo hoàn hảo
  • Áp dụng quy tắc 3 lần thử prompt — sau đó tự cài đặt thủ công
  • Tập trung review vào bảo mật và đường dẫn quan trọng thay vì soi toàn bộ mã
  • Kỹ năng định nghĩa của thời đại AI: biết khi nào nên dừng — bảo vệ tài nguyên nhận thức như hạ tầng kỹ thuật hữu hạn

Good APIs Age Slowly

Yusuf Aytas lập luận rằng “những API gây ấn tượng nhanh thường là những API gây rắc rối nhất về sau” — chất lượng API không nằm ở độ thanh lịch ban đầu mà ở khả năng sống sót khi yêu cầu thay đổi, cách triển khai khác đi và các trường hợp sử dụng không lường trước xuất hiện. API tốt là hạ tầng âm thầm, không phải nguồn drama.

Bốn nguyên tắc thiết kế cho API bền: (1) Xác định biên cẩn thận — phân biệt rõ giữa hợp đồng công khai và phần triển khai bên trong; chỉ phơi bày tối thiểu cần thiết, vì khi consumer đã thấy một hành vi, họ sẽ phụ thuộc vào nó dù vô tình hay cố ý, và việc gỡ bỏ về sau rất tốn kém. (2) Giảm giả định ngầm — API “tiện lợi” thường nhúng giả định về thứ tự gọi, loại người dùng, thời điểm, tư duy của developer; sự tiện lợi này chỉ “dời độ phức tạp” sang giai đoạn debug và migration. API tẻ nhạt và rõ ràng thường sống lâu hơn API thông minh. (3) Tách rời khỏi quyết định UI — màn hình không phải domain model, sản phẩm thay đổi nhanh hơn API nên cần ổn định; mô hình hóa theo khái niệm hệ thống chứ không theo hình dạng trang hiện tại. (4) Bất đối xứng input/output — chấp nhận thêm tham số đầu vào hầu như vô hại, nhưng trả về dữ liệu thừa lại mời gọi phụ thuộc.

Tác giả từ chối coi versioning là cách thay thế cho thiết kế tốt: dù API có version, việc thay đổi thường xuyên do quyết định ban đầu sai vẫn áp đặt chi phí migration, mã tương thích và vận hành.

Điểm chính:

  • Phơi bày càng ít càng tốt — đã expose là khó rút lại
  • API tẻ nhạt, tường minh sống lâu hơn API “thông minh” có giả định ngầm
  • Đừng mô hình hóa API theo màn hình hiện tại — màn hình không phải domain
  • Hào phóng với input, dè dặt với output — dữ liệu trả về thừa sẽ thành dependency
  • Versioning không cứu được thiết kế tồi — chi phí thay đổi vẫn tồn tại

IAM: Everything You Need to Know

Lukas Niessen tổng hợp toàn cảnh kiến trúc IAM (Identity and Access Management) hiện đại theo mô hình ba lớp: ứng dụng client, nền tảng IAM trung tâm (Keycloak, Okta) và nhà cung cấp danh tính bên ngoài (Google, Apple, Microsoft). Nền tảng IAM đóng vai trò trung gian quản lý credential, ứng dụng không bao giờ trực tiếp xử lý mật khẩu. Tác giả phân biệt rõ: authentication xác minh “ai là ai”, còn authorization quyết định “được làm gì”.

Hệ thống OIDC/OAuth 2.0 dùng ba loại token: access token (JWT ngắn hạn, gửi kèm mỗi API request), ID token (JWT chứa thông tin định danh người dùng dành cho frontend), refresh token (dài hạn, lưu an toàn để gia hạn phiên). Luồng redirect-based OIDC tránh để ứng dụng chạm vào mật khẩu — người dùng đăng nhập trên trang của nền tảng IAM, nhận authorization code, rồi backend đổi code lấy token thông qua client secret. Token được ký bằng khóa riêng (private key) của IAM và xác minh bằng khóa công khai qua endpoint JWKS, cho phép backend xác minh phi tập trung mà không cần gọi IAM mỗi request.

Khuyến nghị lưu trữ theo nền tảng: SPA web → access token trong bộ nhớ JS, refresh token trong cookie httpOnly; Electron → dùng Credential Manager / Keychain của OS; iOS → Keychain; Android → Keystore (hardware-backed trên thiết bị hiện đại). Mẫu Backend-for-Frontend (BFF) được đề xuất cho ứng dụng web bảo mật cao: trình duyệt chỉ thấy session cookie, mọi token được giữ phía server.

Điểm chính:

  • Phân biệt loại token (session vs JWT) và phương tiện vận chuyển (cookie vs header) — đây là hai trục độc lập
  • Access token ngắn hạn + refresh token dài hạn là cặp đôi chuẩn cho phiên đăng nhập kéo dài
  • Xác minh JWT cục bộ qua JWKS giúp backend không phụ thuộc IAM mỗi lần kiểm tra
  • BFF pattern là cách an toàn nhất để xử lý token trên web — trình duyệt không bao giờ thấy token
  • Toàn bộ kiến trúc phụ thuộc HTTPS/TLS — không có mã hóa truyền tải thì mọi nỗ lực bảo mật khác đều vô nghĩa

Understanding Traceroute

Stonecharioteer viết lại traceroute bằng Rust trong khoảng 80 dòng để thực sự hiểu cách công cụ này hoạt động. Hóa ra traceroute không hề “hỏi router ở đâu” — nó dùng một mẹo đơn giản dựa trên trường TTL (Time To Live) của gói IP: mỗi router trên đường đi giảm TTL đi 1, khi TTL về 0 thì router đó hủy gói và gửi lại ICMP “Time Exceeded” kèm địa chỉ của chính nó. Gửi gói TTL=1 thì router đầu tiên trả lời, TTL=2 thì router thứ hai, cứ thế cho đến khi tới đích.

Bài viết minh họa từng bước qua mã Rust dùng socket2: mở UDP socket để gửi probe đến port 33434 (port quy ước của traceroute, không có ai lắng nghe), kèm raw ICMP socket để nhận hồi đáp. Gói trả về có cấu trúc IP header (20 byte đầu) + ICMP message — buf[20]type byte quyết định ý nghĩa: 11 = Time Exceeded (router trên đường), 3 = Destination Unreachable (đã đến đích, nhưng phải kiểm tra IP nguồn khớp target để tránh bẫy NAT/firewall trả lỗi sớm). Tác giả lần lượt thêm timing bằng Instant::now()/elapsed(), gửi 3 probe mỗi TTL (để đo phương sai, phát hiện load balancer, chống flaky), và giải thích vì sao chương trình cần sudo (raw socket là thao tác đặc quyền — traceroute hệ thống dùng setuid bit).

Quan trọng hơn, tác giả nhấn mạnh traceroute không phải bản đồ mạng chính xác: đường về của ICMP có thể khác đường đi (asymmetric path), MPLS tunnel ẩn nhiều router thành một hop, load balancer khiến các probe cùng TTL trả về IP khác nhau, và * * * thường không phải router chết mà do ICMP rate limiting/firewall chặn ICMP — gói vẫn đi qua bình thường, chỉ là router không buồn trả lời.

Điểm chính:

  • TTL trick: gửi gói “thiết kế để chết” tại từng hop rồi đọc thông báo lỗi ICMP
  • Port UDP 33434 + raw ICMP socket là cặp đôi cổ điển; TCP mode (-T) tồn tại để vượt qua firewall chặn UDP
  • Phải kiểm tra IP nguồn khớp target trước khi coi là “đã đến” — Type 3 có thể đến từ thiết bị trung gian
  • * * * không có nghĩa là router chết — phổ biến nhất là ICMP rate limiting để tiết kiệm CPU
  • Cần sudo vì raw socket có thể sniff lưu lượng tùy ý — traceroute hệ thống dùng setuid để tránh điều này

How Pizza Tycoon simulated traffic on a 25 MHz CPU

cowomaly phân tích cách game DOS năm 1994 Pizza Tycoon mô phỏng giao thông với 20-30 xe ô tô chuyển động cùng lúc trên CPU chỉ 25 MHz, đối lập với các nỗ lực tái hiện hiện đại “quá kỹ thuật” của chính tác giả. Suốt 14 năm, các bản tái cài đặt của anh đều thất bại vì xây dựng “hệ thống quá phức tạp khó suy luận” — ví dụ năm 2017 dùng cơ chế khóa theo ô (tile-based locking) buộc xe phải xin phép trước khi di chuyển, tạo chi phí đồng bộ vô ích.

Bản gốc lại đơn giản đến bất ngờ: mỗi tile đường tự mang hướng giao thông, xe không cần pathfinding — “xe không cần biết mình đang đi đâu, mỗi loại tile đường mang theo hướng riêng”. Các tile góc dùng quyết định xác suất (50% đi thẳng, 50% rẽ) với một quy tắc đơn giản chặn hai cú rẽ trái liên tiếp. Xe di chuyển một pixel mỗi frame, còn logic nặng (chuyển tile, đổi hướng) chỉ chạy mỗi 16 frame khi xe vượt qua biên tile — chia tải hiệu quả giữa cập nhật pixel nhẹ và logic định kỳ tốn kém.

Kiểm tra va chạm O(n²) cặp đôi nhưng có early-exit mạnh: đường một chiều khiến hai xe ngược hướng không bao giờ chia sẻ làn, nên chỉ cần so sánh hướng là loại bỏ ngay khoảng một nửa trong 625 cặp tiềm năng mỗi frame, “chỉ tốn vài lệnh CPU”. Khi bị chặn, xe đợi 10 tick — tạo hành vi xếp hàng tự nhiên. Xe đi khỏi màn hình thì đảo hướng và quay lại.

Điểm chính:

  • Thiết kế giỏi tránh vấn đề thay vì giải vấn đề bằng tính toán thô
  • Để dữ liệu tile và quy tắc đơn giản thay thế cho hệ thống phức tạp — pathfinding là không cần thiết khi đường tự định hướng
  • Tách chi phí: cập nhật pixel mỗi frame là rẻ, logic chuyển tile chỉ cần chạy mỗi 16 frame
  • Early-exit dựa trên ràng buộc cấu trúc (đường một chiều) loại bỏ phần lớn công việc kiểm tra va chạm
  • Bài học cho lập trình hiện đại: ràng buộc hợp lý từ thiết kế thường mạnh hơn tối ưu thuật toán phức tạp

Bonus

Images: How the JVM Works

How Load Balancers Work?

Optimistic locking vs pessimistic locking

Videos: What is a Data Lakehouse?

Made by miti99 with ❤️
Built with Hugo
Theme Stack thiết kế bởi Jimmy