<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>miti99</title><link>https://miti99.com/</link><description>Recent content on miti99</description><generator>Hugo</generator><language>vi</language><lastBuildDate>Wed, 04 Nov 2026 07:02:32 +0700</lastBuildDate><atom:link href="https://miti99.com/index.xml" rel="self" type="application/rss+xml"/><item><title>Góc làm việc cuối 2026: DeskMini ở nhà, code-server trên cloud</title><link>https://miti99.com/post/2026/11/04/</link><pubDate>Wed, 04 Nov 2026 00:00:00 +0700</pubDate><guid>https://miti99.com/post/2026/11/04/</guid><description>&lt;p&gt;Mình muốn mở chỗ làm việc của mình từ bất kỳ thiết bị nào, để coding agent gánh ngày càng nhiều việc, và tận dụng những free tier miễn phí lâu dài. Sau nhiều lần cài thử rồi gỡ, góc làm việc cuối năm 2026 của mình còn lại ba phần:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Trên cloud:&lt;/strong&gt; code-server chạy trong container trên OCI Always Free ARM, được Coolify triển khai từ &lt;a href="https://github.com/tiennm99/composes" rel="noopener noreferrer"&gt;tiennm99/composes&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ở nhà:&lt;/strong&gt; DeskMini X600 chạy Ubuntu, gánh việc biên dịch, chạy container và coding agent để mình truy cập từ xa.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mang theo:&lt;/strong&gt; ThinkPad giữ nhẹ như một thin client, dùng để nối vào hai nơi kia qua SSH, Tailscale hoặc code-server.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Để đi tới đây, mình đã thử đủ thứ: đổi image code-server, tìm terminal chạy trong trình duyệt, rồi mất hai tuần loay hoay với desktop từ xa. Có thứ hợp, có thứ chỉ nằm lại trong thư mục archive.&lt;/p&gt;
&lt;p&gt;Bài này được AI hỗ trợ tổng hợp từ lịch sử Git, kế hoạch và ghi chú, sau đó mình bổ sung lý do cho các lựa chọn. Mình không kiểm tra trực tiếp DeskMini lúc viết; những gì chưa xác minh hoặc chưa nhớ rõ lý do sẽ được nói rõ ở phần tương ứng.&lt;/p&gt;
&lt;h2 id="điểm-xuất-phát-code-server-trong-một-container"&gt;Điểm xuất phát: code-server trong một container&lt;/h2&gt;&lt;p&gt;Mình đã tự host Coolify từ ít nhất tháng 05/2025. Đến 14/08/2026, khi tổ chức lại repo composes, mình thêm code-server bằng image LinuxServer, gọi tắt là lsio. Go, Node và Python được cài qua &lt;code&gt;DOCKER_MODS&lt;/code&gt;, các gói bổ sung qua &lt;code&gt;INSTALL_PACKAGES&lt;/code&gt;. Thư mục home của người dùng &lt;code&gt;abc&lt;/code&gt; nằm ở &lt;code&gt;/config&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Chạy image này dưới Coolify có vài điểm bất tiện. Mỗi lần triển khai lại, container được tạo mới, các mod tải lại và apt phải giải quyết lại toàn bộ danh sách &lt;code&gt;INSTALL_PACKAGES&lt;/code&gt;. Mình đã phải rút gọn danh sách gói. Nếu một mod cài lỗi, lỗi chỉ được ghi vào log rồi bỏ qua: trình soạn thảo vẫn mở được nhưng thiếu công cụ. Volume workspace lúc mới tạo còn thuộc root, nên mình phải xử lý quyền bằng Dockerfile.&lt;/p&gt;
&lt;p&gt;Đó là những vướng mắc mình gặp với lsio. Lý do mình chọn chuyển sang image chính thức của Coder là muốn dùng bản sát upstream hơn. Mình thêm &lt;code&gt;codercom/code-server&lt;/code&gt; cùng Dockerfile riêng ngày 06/10, rồi gỡ và lưu bản lsio vào archive ngày 12/10.&lt;/p&gt;
&lt;p&gt;Dockerfile mới gom sẵn các công cụ cần dùng sau khi triển khai: bộ công cụ biên dịch, bubblewrap, các CLI mạng, tìm kiếm, kiểm tra mã và cơ sở dữ liệu, cùng gh, Docker CLI, Compose, Buildx, glab và gt. Nhờ vậy, các lệnh hỗ trợ và tính năng tự hoàn thành cũng có sẵn. Mình dùng &lt;code&gt;FROM codercom/code-server:latest&lt;/code&gt; vì image không có tag theo phiên bản lớn.&lt;/p&gt;
&lt;p&gt;Để agent-browser chạy không cần giao diện trên ARM64, image có Chromium từ Debian và các phông chữ cần thiết; mình cũng chỉnh bộ nhớ chia sẻ để Chromium tránh bị văng. GTK/WebKitGTK phục vụ việc biên dịch và kiểm thử Tauri không có màn hình.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Riêng toolchain ngôn ngữ, mình giữ trong home, ngoài image:&lt;/strong&gt; fnm, uv, rustup, Go từ tarball và SDKMAN. Cài thêm một bản vào image chỉ làm quá trình build nặng hơn, trong khi bản đó có thể bị bản trong home che khuất. Tách riêng như vậy cũng tránh việc triển khai lại container âm thầm nâng phiên bản công cụ mình đang dùng.&lt;/p&gt;
&lt;p&gt;Một vướng mắc còn lại là gõ tiếng Việt trong terminal trình duyệt khi dùng Claude Code. Tới lần ghi nhận ngày 18/10, mình chưa có cách sửa được hỗ trợ chính thức. Phím Backspace và quá trình ghép dấu phải đi qua cả chuỗi trình duyệt, xterm.js, WebSocket rồi PTY. Mình tạm soạn bên ngoài rồi dán vào.&lt;/p&gt;
&lt;h2 id="những-thứ-khảo-sát-thử-rồi-gỡ-trên-máy-chủ"&gt;Những thứ khảo sát, thử rồi gỡ trên máy chủ&lt;/h2&gt;&lt;p&gt;Song song với việc đổi image, mình tìm cách dùng terminal và coding agent ngay trong trình duyệt. Từ tháng 9 đến đầu tháng 11, vài cái tên lần lượt xuất hiện rồi rời khỏi máy chủ:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;OpenHands:&lt;/strong&gt; thử từ 18 đến 19/09, khoảng một ngày.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;opencode web:&lt;/strong&gt; thử từ 18/09 đến 05/10 để có shell trong trình duyệt.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Paseo:&lt;/strong&gt; dùng từ 16/09 đến 03/11, lần chỉnh cuối là 06/10. Daemon này từng có vẻ hợp, nhưng mỗi agent CLI cần cài khoảng 200 MB lúc khởi động. Mình bỏ vì Orca và T3 Code có tính năng và trải nghiệm tốt hơn.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Với OpenHands và opencode web, mình không ghi lại lý do gỡ.&lt;/p&gt;
&lt;p&gt;Zellij web client là thứ mình thực sự muốn dùng. Mình chạy nó trong container từ 21/10 đến 03/11, nhưng vướng ngay ở phím tắt: trình duyệt giữ Ctrl+T, Ctrl+W, Ctrl+N và Ctrl+Tab, đúng những tổ hợp mình cần cho các chế độ của Zellij. Với cấu hình Unlock-First, Ctrl+G lại mở thanh tìm kiếm của Firefox; đổi sang Alt+G mới dùng được. Mỗi lần mở đường dẫn gốc, Zellij còn tạo một phiên mới mà mình không tự xóa được. Cuối cùng, chuyện phím tắt là lý do khiến mình bỏ nó.&lt;/p&gt;
&lt;p&gt;Trong lúc dựng, mình từng dùng socat làm relay và gặp cả trường hợp Zellij thoát không báo gì khi thiếu tệp cấu hình.&lt;/p&gt;
&lt;p&gt;Gõ cũng có lúc bị trễ, nhưng mình chưa chứng minh được đó là lỗi của Zellij. Container gần như không tải; biến &lt;code&gt;TERM&lt;/code&gt; sai, bộ gõ tiếng Việt qua trình duyệt hay độ trễ mạng đều có thể góp phần. Riêng lỗi &lt;code&gt;TERM=unknown&lt;/code&gt; làm Backspace chạy sang phải thì mình đã sửa.&lt;/p&gt;
&lt;p&gt;Hướng thay thế mình khảo sát ngày 24/10 là tmux qua ttyd. tmux dùng tiền tố Ctrl+B, nên cấu hình mặc định ít đụng phím trình duyệt hơn cách chia chế độ của Zellij. Nó vẫn phải làm việc trong giới hạn phím mà trình duyệt cho phép.&lt;/p&gt;
&lt;p&gt;Mình thêm template ttyd nhưng chưa thực sự dùng thử. Sau đó, Orca và T3 Code đã đáp ứng nhu cầu nên mình bỏ luôn template. Một terminal đơn thuần vẫn thiếu với mình: chỉ muốn xem một tệp thôi mà cũng phải nhờ Claude thì hơi vòng vèo.&lt;/p&gt;
&lt;p&gt;Ngày 03/11, mình đưa Paseo, Zellij và template ttyd vào archive cùng một commit. Bản Zellij container đã được gỡ; phần terminal mình còn dùng là SSH qua trình duyệt trên DeskMini và terminal trong code-server.&lt;/p&gt;
&lt;h2 id="chiếc-deskmini-ở-nhà"&gt;Chiếc DeskMini ở nhà&lt;/h2&gt;&lt;p&gt;Con máy ở nhà là ASRock DeskMini X600: bo X600M-STX, Ryzen 7 8700G với 8 nhân, 16 luồng, đồ họa tích hợp Radeon 780M và 32 GB RAM. Máy có hai ổ NVMe 500 GB, một cho hệ điều hành, một cho workspace. Ở lần kiểm tra ngày 21/10, nó chạy Ubuntu 26.04 desktop với GNOME trên Wayland.&lt;/p&gt;
&lt;p&gt;Mình chọn DeskMini vì nó mạnh và có GPU, hợp làm &lt;strong&gt;máy Linux ở nhà để gánh việc nặng và truy cập từ xa&lt;/strong&gt;. Desktop Windows của mình còn mạnh hơn, nhưng mình không chọn nó cho vai trò chạy 24/7 vì nó dùng Windows. Khi ở nhà, mình vẫn ngồi desktop Windows; ở chỗ làm, lúc cần homelab mạnh, mình mở Orca trên DeskMini qua Tailscale.&lt;/p&gt;
&lt;p&gt;ThinkPad vì thế có thể giữ nhẹ: việc biên dịch, chạy container, toolchain và tác vụ dài đều chuyển sang DeskMini hoặc máy chủ. DeskMini không chạy Coolify; các dịch vụ ở đây dùng Docker Compose thông thường và systemd. Truy cập qua trình duyệt đi qua Cloudflare Tunnel và Access.&lt;/p&gt;
&lt;p&gt;Toolchain được bổ sung dần từ cuối tháng 10: Node qua fnm, Python qua uv, Go từ tarball chính thức với &lt;code&gt;GOTOOLCHAIN=auto&lt;/code&gt;, Rust qua rustup và Java 21 qua SDKMAN. Máy cũng có gcc, git, gh, Docker, glab và sau đó là gt. Một điểm dễ quên khi viết tác vụ tự động: fnm và SDKMAN được nạp từ cấu hình zsh tương tác, nên cron và systemd không tự thấy chúng.&lt;/p&gt;
&lt;p&gt;Từ 22/10, DeskMini đã có ghi chú riêng trong workspace repo. Lúc đó, vai trò của nó còn gắn với một thứ mình rất muốn làm cho ổn: desktop Linux mở ngay trong trình duyệt bằng Guacamole.&lt;/p&gt;
&lt;h2 id="hai-tuần-đi-tìm-desktop-trong-trình-duyệt"&gt;Hai tuần đi tìm desktop trong trình duyệt&lt;/h2&gt;&lt;p&gt;Mục tiêu lúc đó rất cụ thể: mở desktop Linux từ bất kỳ đâu bằng trình duyệt, không cần cài gì trên máy đang ngồi. Vì thế, mình tìm đường qua Cloudflare Tunnel và Access, thay vì yêu cầu máy truy cập phải có Tailscale.&lt;/p&gt;
&lt;p&gt;Trong các lựa chọn mình khảo sát ngày 19/10, Guacamole là phương án miễn phí, tự host, không cần cài ứng dụng phía truy cập và hỗ trợ RDP cho cả Linux lẫn Windows. Cloudflare browser RDP đứng sau trong bản so sánh, nhưng mình lại thử nó trước. Ngày 21/10, GNOME Remote Desktop trả về lỗi:&lt;/p&gt;
&lt;blockquote&gt;
 &lt;p&gt;Client did not advertise support for the Graphics Pipeline, closing connection&lt;/p&gt;

&lt;/blockquote&gt;
&lt;p&gt;GNOME yêu cầu RDP Graphics Pipeline, còn IronRDP phía Cloudflare lúc đó không quảng bá khả năng hỗ trợ nên GNOME đóng kết nối. Tài liệu Cloudflare mình đọc cũng chỉ liệt kê máy Windows là được hỗ trợ. Đường truy cập này vì thế không dùng được với GNOME trong lần thử của mình.&lt;/p&gt;
&lt;p&gt;VNC cũng nằm trong phần khảo sát, nhưng mình chưa cài thử. Với GNOME trên Ubuntu 26.04 trong cấu hình này, mình chỉ còn Wayland và GNOME Remote Desktop phục vụ RDP. Các phương án VNC mình xem cần X11 hoặc wlroots; những hướng dẫn có nút bật VNC trong GNOME không còn khớp với bản đang dùng.&lt;/p&gt;
&lt;p&gt;Mình chuyển sang Guacamole, dựng bằng Docker trên DeskMini trong hai ngày 21–22/10, gồm guacamole, guacd và PostgreSQL. Mình ghim bản 1.6.0 vì đây là bản phát hành đầu hỗ trợ Graphics Pipeline; tag &lt;code&gt;latest&lt;/code&gt; lúc đó lại là bản phát triển chỉ có amd64.&lt;/p&gt;
&lt;p&gt;Kết nối lên được, nhưng phiên làm việc lại rớt giữa chừng. Lỗi lặp lại dưới cả QUIC lẫn HTTP/2, còn cloudflared không ghi gì tương ứng với những lần rớt. Vì vậy, đổi giao thức tunnel không giải quyết được vấn đề. Trong chuỗi log ghi nhận được, guacd báo &lt;code&gt;User is not responding.&lt;/code&gt; sau 15 giây im lặng rồi đóng RDP. Các ngưỡng chờ 15 giây này được biên dịch sẵn, không chỉnh qua cấu hình được.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Mình vẫn chưa xác định được nguyên nhân.&lt;/strong&gt; Ban đầu mình nghi trình duyệt hạn chế bộ đếm giờ của tab nền, nhưng tab đang mở trước mặt cũng rớt. Giả thuyết phù hợp nhất lúc đó là các lỗi phát sinh ở Guacamole 1.6.0, gồm GUACAMOLE-2118, các báo cáo trùng và GUACAMOLE-2081. Các lỗi này đã được sửa trên nhánh 1.6.1 nhưng bản đó chưa phát hành tại thời điểm khảo sát.&lt;/p&gt;
&lt;p&gt;Dù vậy, mình chưa có kết quả kiểm thử qua LAN để bỏ tunnel ra khỏi đường kết nối. Có lần SSH và RDP cùng đông cứng dù chạy ở hai tiến trình guacd riêng, cũng chưa giải thích gọn được bằng giả thuyết trên.&lt;/p&gt;
&lt;p&gt;Với SSH qua Guacamole, hậu quả còn khó chịu hơn: shell gắn vào kết nối trình duyệt, nên một lần rớt có thể làm mất lệnh đang chạy và trạng thái chưa lưu. Mình muốn chuyển phần terminal ra khỏi Guacamole vì thế. Template Zellij đã có từ 21/10, trước lần chẩn đoán này; trải nghiệm rớt SSH càng cho thấy mình cần phiên terminal tồn tại độc lập với tab trình duyệt.&lt;/p&gt;
&lt;p&gt;Ban đầu, mình chọn giữ 1.6.0 để chờ 1.6.1, chỉ hạ về 1.5.5 nếu rớt quá thường xuyên. Phương án hạ bản cần sao lưu PostgreSQL và chuẩn bị lược đồ 1.5.5 vì Guacamole không có migration ngược.&lt;/p&gt;
&lt;p&gt;Đến 03/11, mình ngừng dùng Guacamole và đưa vào archive vì kết nối thiếu ổn định. SSH qua trình duyệt của Cloudflare đã đủ cho nhu cầu terminal; khi cần desktop, mình dùng Tailscale cùng ứng dụng Windows RDP chính thức. Mục tiêu ban đầu là mọi thứ đều mở bằng trình duyệt, nhưng cuối cùng mình chấp nhận dùng ứng dụng RDP riêng cho phần này.&lt;/p&gt;
&lt;h2 id="những-gì-đang-chạy-trên-deskmini-bây-giờ"&gt;Những gì đang chạy trên DeskMini bây giờ&lt;/h2&gt;&lt;p&gt;Theo trạng thái được ghi lại lúc viết bài, SSH qua trình duyệt, code-server, T3 Code và Orca đều có đường truy cập qua &lt;strong&gt;Cloudflare Tunnel và Access&lt;/strong&gt;. Tailscale nối các thiết bị của mình với nhau.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;code-server&lt;/strong&gt; được cài bằng gói &lt;code&gt;.deb&lt;/code&gt;, chạy qua systemd và đã có trong ghi chú từ 22/10. Vì không dùng được VS Code Settings Sync, mình dự định giữ cấu hình và danh sách tiện ích mở rộng — Claude Code cùng một theme — trong dotfiles, rồi áp dụng bằng một script chạy tay. Phần dotfiles này chưa được áp dụng lên DeskMini.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Orca và T3 Code&lt;/strong&gt; vừa được chuyển từ cách mở ứng dụng desktop sang chạy bằng systemd user service. Mình cài Orca (&lt;code&gt;stablyai/orca&lt;/code&gt;) từ gói &lt;code&gt;.deb&lt;/code&gt; ngày 27/10; ngày cài T3 Code thì không còn dấu vết. Theo ghi chú, mục tiêu của lần chuyển đầu tháng 11 là để chúng lên khi khởi động máy mà không cần mở ứng dụng desktop. Riêng Orca, với &lt;code&gt;--supervise&lt;/code&gt;, dịch vụ sẽ chờ nếu một Orca khác đang phục vụ cùng profile, nên mở ứng dụng desktop trước có thể khiến dịch vụ phải chờ.&lt;/p&gt;
&lt;p&gt;Trong bản so sánh ngày 02/11, Orca được mô tả là workspace desktop xoay quanh Git worktree, chạy agent CLI trực tiếp trong terminal. T3 Code tổ chức công việc theo luồng hội thoại, điều khiển agent qua adapter và có giao diện truy cập mỏng. Bản so sánh chỉ dựa trên tài liệu, chưa phải kiểm thử hai bên, và đề nghị giữ Orca, thử T3 khi cần điều khiển agent trên máy chủ từ điện thoại hoặc trình duyệt. Hiện cả hai đều chạy dạng dịch vụ, còn Orca là ứng dụng mình dùng nhiều nhất.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tailscale và cloudflared&lt;/strong&gt; chia việc với nhau: Tunnel cùng Access phục vụ đăng nhập từ xa và giao diện web, còn Tailscale lo các kết nối khác giữa thiết bị của mình. Mình chọn cách này vì thấy đơn giản hơn WARP-to-WARP. Với GNOME Remote Desktop, phần khảo sát phân biệt đăng nhập từ xa ở màn hình đăng nhập với chia sẻ desktop sau khi đã đăng nhập; mình chưa có kết quả kiểm thử được ghi lại cho phần này.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Gitea cùng gitea-mirror&lt;/strong&gt; chạy từ repo composes, làm nơi sao lưu GitHub trên DeskMini. Chuyện crawler ghé vào Gitea mình đã kể trong &lt;a href="https://miti99.com/post/2026/10/29/"&gt;bài ngày 29/10/2026&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Phần sao lưu cũng từng vướng: ngưỡng chờ mặc định của Gitea cắt ngang các lần clone nhiều GB, còn Bun fetch ngắt kết nối không có dữ liệu trong năm phút, để lại repo làm dở. Mình tăng ngưỡng chờ để xử lý. Health check gọi GitHub API không có token quá thường xuyên cũng chạm giới hạn yêu cầu ẩn danh, nên mình giảm tần suất. CLI tea được thay bằng gt khi tea bị archive ở upstream và gt lên 1.0.0. Còn lý do chuyển chỗ sao lưu về nhà thì mình không ghi lại, nên không thể quy nó cho một sự cố cụ thể.&lt;/p&gt;
&lt;h2 id="bật-máy-từ-xa-từ-rtc-đến-ổ-cắm-thông-minh"&gt;Bật máy từ xa: từ RTC đến ổ cắm thông minh&lt;/h2&gt;&lt;p&gt;Ban đầu, bật máy từ xa nằm ngoài phạm vi mình định làm. Phần khảo sát có gợi ý ESP32 nhận lệnh Telegram để đánh thức PC, nhưng mình chưa triển khai.&lt;/p&gt;
&lt;p&gt;Từ 28/10, mình dùng RTC wake với &lt;code&gt;rtcwake&lt;/code&gt; qua systemd để hẹn DeskMini tự bật. Điểm yếu là nếu mất điện hoặc bị tắt cứng, máy có thể bỏ qua bước đặt lịch đánh thức.&lt;/p&gt;
&lt;p&gt;Đến 03/11, mình gỡ lịch RTC phía hệ điều hành và chuyển sang hướng dùng ổ cắm thông minh, dựa vào BIOS tự bật máy khi có điện trở lại. Mình không ghi lại lý do bỏ RTC. Quy trình dự kiến là:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Tắt máy từ hệ điều hành trước, không cắt điện khi PC đang chạy.&lt;/li&gt;
&lt;li&gt;Tắt ổ cắm khoảng 30 giây rồi bật lại.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Phần này vẫn chưa hoàn thành.&lt;/strong&gt; Mình còn phải tắt RTC alarm trong BIOS, đặt restore on AC loss thành power on, rồi thử trọn vòng tắt/bật ổ cắm và tình huống mất điện. Vì vậy, chưa thể nói mình đã bật máy từ xa thành công bằng cách này.&lt;/p&gt;
&lt;h2 id="coolify-và-hai-máy-chủ-always-free"&gt;Coolify và hai máy chủ Always Free&lt;/h2&gt;&lt;p&gt;Phía cloud, Coolify quản lý hai máy chủ OCI Always Free chạy ARM và Ubuntu 24.04 Minimal, cùng Traefik v3 và Let&amp;rsquo;s Encrypt. Mình chọn OCI vì muốn dùng free tier miễn phí lâu dài. Còn Coolify thay vì Dokploy là vì mình thấy nó ổn định hơn — đây là cảm nhận cá nhân, chưa có số liệu hay phép thử so sánh. Trong các tệp compose, mình vẫn để sẵn phần cho Dokploy.&lt;/p&gt;
&lt;p&gt;Các cấu hình triển khai chung nằm trong repo composes công khai, dùng giấy phép Apache 2.0. Mỗi dịch vụ có thư mục riêng với &lt;code&gt;compose.yml&lt;/code&gt;, &lt;code&gt;README.md&lt;/code&gt; và tệp biến môi trường mẫu, cùng một ứng dụng Coolify có webhook theo dõi riêng để tránh triển khai lại chéo. Chú thích trong cấu hình giải thích nó làm gì, còn README giữ lý do lựa chọn. Các công cụ mình tự viết thì giữ compose ngay trong repo của chúng.&lt;/p&gt;
&lt;p&gt;Thư mục archive giúp nhìn lại mình đã thử gì và gỡ lúc nào. Điểm còn thiếu là lý do: không phải lần nào mình cũng ghi lại vì sao bỏ một thứ.&lt;/p&gt;
&lt;h2 id="giữ-mọi-thứ-nhất-quán-giữa-các-máy"&gt;Giữ mọi thứ nhất quán giữa các máy&lt;/h2&gt;&lt;p&gt;Thứ giúp mình và agent không lạc giữa chừng ấy máy là một workspace repo được checkout ở nhiều nơi. Repo bắt đầu ngày 25/07, chỉ để chứa vài skill audit GitHub rồi lớn dần. Đến cuối tháng 10, ghi chú theo từng máy được tổ chức lại để agent đọc đúng ngữ cảnh. Claude Code và Codex dùng chung bộ quy tắc; mình dùng Claude Code là chính. AgentKit CLI cài bộ kit vào thư mục home của từng agent.&lt;/p&gt;
&lt;p&gt;Mình chia ngữ cảnh theo cách nó được dùng: quy tắc được nạp mỗi phiên, skill được đọc theo tác vụ, còn memory có một mục lục luôn được nạp để trỏ tới các tệp liên quan. Hook chặn những lỗi tốn kém; ghi chú và kế hoạch giữ việc cần làm; lịch sử Git và tài liệu giữ chuyện đã xảy ra. Memory cũng được theo dõi bằng Git. Điều mình phải nhớ là chỉ quy tắc mới được bảo đảm nạp: để một chỉ dẫn hành vi trong memory thì agent có thể bỏ qua nó.&lt;/p&gt;
&lt;p&gt;Phần đồng bộ cấu hình bằng dotfiles thì &lt;strong&gt;chưa áp dụng lên máy nào&lt;/strong&gt;. Mình chọn chezmoi trong đợt so sánh ngày 29/10: đây là lựa chọn duy nhất trong nhóm khảo sát có một tệp thực thi chạy trực tiếp trên Windows. Nó hỗ trợ mẫu cấu hình theo máy, hỏi thông tin một lần, tích hợp sops khi áp dụng và cho xem trước thay đổi. dotter đứng sau trong bản so sánh; Ansible cần WSL để điều khiển từ Windows và có phần cấu hình nặng hơn cách quản lý tệp trực tiếp cho nhu cầu của mình.&lt;/p&gt;
&lt;p&gt;Kế hoạch toolchain cũng đổi: mình bỏ mise khỏi kế hoạch ngày 30/10, chuyển sang công cụ riêng cho từng hệ — rustup, uv, fnm, SDKMAN hoặc Temurin qua winget, và Go chính thức với &lt;code&gt;GOTOOLCHAIN=auto&lt;/code&gt;. Mình không ghi nhận lỗi cụ thể nào của mise.&lt;/p&gt;
&lt;p&gt;Với shell, mình dự định dùng zsh, zimfw và Starship thay cho oh-my-zsh cùng powerlevel10k. Trong nhóm được so sánh, zimfw phổ biến nhất trong các lựa chọn ổn định và nhanh nhất ở phép thử được ghi lại; khả năng tự hoàn thành có sẵn cũng phủ gần hết nhu cầu. README của powerlevel10k thì nói mức hỗ trợ rất hạn chế. Đây vẫn là kế hoạch, còn oh-my-zsh vẫn chạy trên máy chủ.&lt;/p&gt;
&lt;h2 id="việc-còn-dở-và-bài-học"&gt;Việc còn dở và bài học&lt;/h2&gt;&lt;p&gt;Tính tới 04/11/2026, hai việc còn dở là:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Chỉnh BIOS DeskMini và kiểm thử bật máy qua ổ cắm, kể cả sau khi mất điện.&lt;/li&gt;
&lt;li&gt;Áp dụng dotfiles lên từng máy.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Nhìn lại các lần thay đổi, mình rút ra mấy điều:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Ghi cả vì sao, đừng chỉ ghi đã làm gì.&lt;/strong&gt; Lịch sử Git dựng lại được các lần thay đổi, nhưng không tự giải thích được động cơ. Ghi lý do ngay lúc quyết định sẽ đỡ phải nhớ lại như khi viết bài này.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Desktop qua trình duyệt cần thử với đúng hệ đang dùng.&lt;/strong&gt; Với GNOME Wayland của mình, yêu cầu Graphics Pipeline đã chặn một đường RDP, còn các hướng dẫn VNC cũ không còn phù hợp.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Phiên làm việc nên tồn tại qua một lần đóng tab.&lt;/strong&gt; Buộc nó vào kết nối trình duyệt khiến một lần rớt có thể kéo theo công việc đang chạy.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tách toolchain khỏi image giúp kiểm soát việc nâng phiên bản.&lt;/strong&gt; Triển khai lại container không nên âm thầm đổi công cụ mình đang dùng.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Chia đường truy cập theo nhu cầu giúp mình dễ dùng hơn.&lt;/strong&gt; Tunnel cùng Access cho đăng nhập và giao diện web; Tailscale cho các kết nối khác giữa thiết bị của mình.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Góc làm việc này chắc còn đổi. Hiện tại, mình dùng Orca nhiều nhất, giữ code-server để mở workspace qua trình duyệt, và để DeskMini cùng máy chủ gánh việc nặng. Những thứ đã bỏ giúp mình rõ hơn mình cần gì ở lần cài tiếp theo.&lt;/p&gt;</description></item><item><title>Khi AI crawler đi dạo khắp Gitea tự host của mình</title><link>https://miti99.com/post/2026/10/29/</link><pubDate>Thu, 29 Oct 2026 00:00:00 +0700</pubDate><guid>https://miti99.com/post/2026/10/29/</guid><description>&lt;p&gt;Mình tự host Gitea trên một máy chủ OCI Always Free để lưu bản sao các repo trên GitHub. Cứ nghĩ đây là góc nhỏ chỉ có mình lui tới, cho đến khi phát hiện ba &amp;ldquo;vị khách&amp;rdquo; ghé thăm đều đặn: ClaudeBot, GPTBot và Reflectionbot. Từ vài dấu hiệu bất thường trong log, mình lần ra một repo private bị công khai ngoài ý muốn, rồi tìm cách chặn crawler mà vẫn giữ các ứng dụng hoạt động bình thường. Đây là câu chuyện từ lúc phát hiện đến khi xử lý và theo dõi sau đó.&lt;/p&gt;
&lt;h2 id="phát-hiện"&gt;Phát hiện&lt;/h2&gt;&lt;p&gt;Sáng 29/10/2026, mình thấy máy chủ chạy Gitea ở Nhật bận bất thường. Mở nhật ký truy cập (access log) của Traefik, mình mới biết log xoay vòng nhanh đến mức phần còn giữ lại chỉ bao phủ khoảng 4,5 tiếng, từ 17:55 đến 22:22 UTC ngày 28/10. Chỉ trong khoảng thời gian đó đã có &lt;strong&gt;86.765 request&lt;/strong&gt;.&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;User agent&lt;/th&gt;
 &lt;th&gt;Số request&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;ClaudeBot (Anthropic)&lt;/td&gt;
 &lt;td&gt;39.860&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;GPTBot (OpenAI)&lt;/td&gt;
 &lt;td&gt;26.901&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Reflectionbot (Reflection AI)&lt;/td&gt;
 &lt;td&gt;16.412&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Bun (chính gitea-mirror của mình đang sync)&lt;/td&gt;
 &lt;td&gt;3.587&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Còn lại (DuckDuckBot, OAI-SearchBot)&lt;/td&gt;
 &lt;td&gt;5&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Khoảng 95% phản hồi HTTP 200 đến từ các trang &lt;code&gt;src&lt;/code&gt;, &lt;code&gt;commit&lt;/code&gt;, &lt;code&gt;raw&lt;/code&gt; của 86 repo mirror. Các crawler lần qua &lt;strong&gt;từng commit của từng repo&lt;/strong&gt;, mà mỗi commit lại có vài trang để đọc. Cộng dồn lại, lượng truy cập tăng lên rất nhanh.&lt;/p&gt;
&lt;p&gt;Nhìn lại số liệu cả tháng, mình thấy lưu lượng gần như bằng 0 cho đến ngày 10/10. Từ 11/10, nó tăng vọt và liên tục duy trì ở mức cao:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://miti99.com/post/2026/10/29/requests-by-day.png" alt="Số request theo ngày, từ 30/9 đến 28/10"&gt;&lt;/p&gt;
&lt;p&gt;Tổng cộng là gần &lt;strong&gt;13,9 triệu request&lt;/strong&gt; và &lt;strong&gt;1,48 TB&lt;/strong&gt; băng thông, chỉ để phục vụ một Gitea vốn có mỗi mình sử dụng:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://miti99.com/post/2026/10/29/traffic-overview.png" alt="Tổng quan traffic: request, unique visitor, băng thông, error rate, P95 latency"&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Nếu tự host trên VPS, bạn nên để ý cả chi phí băng thông.&lt;/strong&gt; Lượng dữ liệu bot tải về vẫn tính vào lưu lượng của máy chủ. Tùy nhà cung cấp và gói dịch vụ, vượt hạn mức có thể khiến bạn phải trả thêm phí hoặc bị giới hạn tốc độ. Mình may mắn vì hạn mức băng thông của OCI khá rộng rãi, nên đợt này chưa phát sinh thêm chi phí. Nhưng với một VPS có hạn mức thấp hơn, 1,48 TB chỉ từ crawler có thể trở thành khoản tiền ngoài dự tính.&lt;/p&gt;
&lt;p&gt;Gần như toàn bộ lượt truy cập là từ bot, với nguồn truy cập cũng gần như hoàn toàn ở Mỹ:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://miti99.com/post/2026/10/29/client-types.png" alt="Tỉ lệ loại client: gần như toàn bộ là Bot"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://miti99.com/post/2026/10/29/traffic-by-country.png" alt="Traffic theo quốc gia: United States chiếm 13,51 triệu request"&gt;&lt;/p&gt;
&lt;p&gt;Riêng ClaudeBot đã chiếm 10,82 triệu request và 1,4 TB băng thông. GPTBot cũng đóng góp thêm 2,52 triệu request:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://miti99.com/post/2026/10/29/ai-bots.png" alt="Bảng AI agents &amp;amp; bots: ClaudeBot và GPTBot"&gt;&lt;/p&gt;
&lt;h2 id="đoán-nguyên-nhân"&gt;Đoán nguyên nhân&lt;/h2&gt;&lt;p&gt;Điều đầu tiên mình lo là tài khoản có bị lộ hay không. Kiểm tra lại log, mình thấy:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Toàn bộ request đều ẩn danh, không có request nào đăng nhập.&lt;/li&gt;
&lt;li&gt;Không có ứng dụng bên ngoài nào gửi POST, PATCH, push hay gọi API. Mọi thao tác ghi đều đến từ chính máy chủ, do gitea-mirror đang đồng bộ.&lt;/li&gt;
&lt;li&gt;Cả ba crawler đều có nguồn gốc xác thực: IP của GPTBot nằm trong danh sách OpenAI công bố, ClaudeBot thuộc dải IP của Anthropic, còn Reflectionbot có DNS ngược trỏ về &lt;code&gt;reflection.ai&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://miti99.com/post/2026/10/29/crawler-verification.png" alt="Kiểm chứng ba crawler: Reflectionbot, GPTBot và ClaudeBot đều là bot thật"&gt;&lt;/p&gt;
&lt;p&gt;Những dấu hiệu này không cho thấy tài khoản bị xâm nhập. Các bot đang truy cập những trang mà Gitea công khai cho người dùng ẩn danh: chúng hỏi &lt;code&gt;/robots.txt&lt;/code&gt; trước, nhận về 404 vì không có tệp này, rồi tiếp tục thu thập các trang trả về HTTP 200. Nhìn lại, mình đã để cửa mở mà không nhận ra; vấn đề cần xử lý trước hết nằm ở cấu hình của chính mình.&lt;/p&gt;
&lt;p&gt;Trong lúc kiểm tra, mình phát hiện chuyện đáng lo hơn: &lt;strong&gt;một repo private đã bị lộ&lt;/strong&gt;. Repo gốc trên GitHub là private, nhưng bản mirror trên Gitea lại &lt;strong&gt;public&lt;/strong&gt;, và Reflectionbot đã lấy 48 trang từ đó. Lần theo quá trình đồng bộ, mình tìm được nguyên nhân ở một bản ghi cũ mà gitea-mirror vẫn dùng lại:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Ban đầu, tên này thuộc về một repo public. Khi nhập repo, gitea-mirror lưu cờ &lt;code&gt;is_private = 0&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Sau đó, repo public được chuyển sang namespace khác.&lt;/li&gt;
&lt;li&gt;Mình tạo một repo &lt;strong&gt;private&lt;/strong&gt; mới với tên cũ. Bản ghi trong gitea-mirror vẫn trỏ đến URL ấy và giữ cờ public. Vì coi đây là cùng một repo, công cụ không kiểm tra lại quyền hiển thị.&lt;/li&gt;
&lt;li&gt;Gitea tiếp tục kéo bản mirror từ URL đó bằng GitHub token. Kết quả là nội dung của repo private mới được đồng bộ vào bản mirror public trên Gitea.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Cấu hình &lt;code&gt;visibility: &amp;quot;private&amp;quot;&lt;/code&gt; của gitea-mirror cũng không ngăn được tình huống này. Nó chỉ áp dụng cho các tổ chức mà công cụ tạo ra; quyền hiển thị của từng repo vẫn dựa trên cờ private lấy từ GitHub ở thời điểm nhập ban đầu.&lt;/p&gt;
&lt;p&gt;Mình rà soát toàn bộ và tìm thấy 105 repo có thể đọc mà không cần đăng nhập, tất cả đều thuộc tài khoản cá nhân trên Gitea. Các tổ chức còn lại đều private. Trong 105 repo công khai ấy, chỉ repo vừa phát hiện có nguồn private trên GitHub.&lt;/p&gt;
&lt;p&gt;Còn lý do Cloudflare không chặn các bot thì đơn giản hơn: mọi bản ghi DNS của ứng dụng đều là bản ghi &lt;code&gt;A&lt;/code&gt; ở chế độ &lt;strong&gt;DNS only&lt;/strong&gt; (mây xám). Bot đi thẳng đến máy chủ gốc (origin), nên các request này không hề qua Cloudflare để được lọc.&lt;/p&gt;
&lt;h2 id="xử-lý"&gt;Xử lý&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Việc đầu tiên là ngăn lộ thêm dữ liệu.&lt;/strong&gt; Mình dừng Gitea cùng gitea-mirror, rồi chuyển repo bị lộ về private.&lt;/p&gt;
&lt;p&gt;Ban đầu, mình định đưa toàn bộ ứng dụng ra sau Cloudflare Tunnel và Cloudflare Access. Nhưng phương án này kéo theo khá nhiều việc: đổi địa chỉ trên Coolify sang &lt;code&gt;http://&lt;/code&gt;, cấu hình lại từng ứng dụng, rồi xử lý webhook. Mình tạm để lại cho sau và chọn bước ít thay đổi hơn: &lt;strong&gt;bật proxy (mây cam)&lt;/strong&gt; cho các bản ghi DNS. Chúng vẫn trỏ về IP cũ, Traefik và Coolify vẫn giữ nguyên cách chạy, còn Cloudflare đứng giữa để lọc bot.&lt;/p&gt;
&lt;p&gt;Chiều 29/10, mình triển khai lần lượt như sau:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Chuyển SSL/TLS từ Full sang Full (strict).&lt;/strong&gt; Traefik tiếp tục dùng chứng chỉ Let&amp;rsquo;s Encrypt và gia hạn qua HTTP-01 khi bật proxy. Mình tránh chế độ Flexible vì Traefik đã chuyển hướng HTTP sang HTTPS; kết hợp hai bên sẽ gây vòng lặp chuyển hướng.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Thử trước với một ứng dụng ít rủi ro&lt;/strong&gt;, là một terminal trên web. Mình kiểm tra header &lt;code&gt;server: cloudflare&lt;/code&gt;, khả năng tải trang và kết nối WebSocket.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Thiết lập các lớp chặn bot&lt;/strong&gt; có sẵn trên gói Free:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;AI bot policies&lt;/strong&gt;: đặt Training, Agent và Search thành Block, vì mình không cần các ứng dụng này xuất hiện trong kết quả tìm kiếm của AI.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Bot Preference Sync&lt;/strong&gt; (&lt;code&gt;robots.txt&lt;/code&gt; do Cloudflare quản lý): tự chèn các dòng &lt;code&gt;Disallow&lt;/code&gt; dành cho bot AI vào &lt;code&gt;robots.txt&lt;/code&gt;, hoặc phục vụ tệp này nếu ứng dụng chưa có.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Một quy tắc WAF tùy chỉnh&lt;/strong&gt; để chặn thêm những trường hợp chính sách trên có thể bỏ sót:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(cf.verified_bot_category in {&amp;#34;AI Crawler&amp;#34; &amp;#34;AI Assistant&amp;#34; &amp;#34;AI Search&amp;#34;})
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;or (http.user_agent contains &amp;#34;Reflectionbot&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Bật proxy cho bản ghi wildcard&lt;/strong&gt; &lt;code&gt;*.miti99.dev&lt;/code&gt;. Một lần chuyển là áp dụng cho gần 20 ứng dụng; các ứng dụng mới dùng bản ghi wildcard này cũng được proxy mặc định. Nếu cần quay lại cấu hình cũ, mình chỉ việc tắt proxy.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Bật proxy cho Coolify sau cùng&lt;/strong&gt;, rồi kiểm tra webhook triển khai khi push lên GitHub, số liệu Sentinel gửi từ máy chủ thứ hai, MCP và kết nối WebSocket thời gian thực.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Cấu hình Traefik tin cậy dải IP của Cloudflare&lt;/strong&gt; qua &lt;code&gt;forwardedHeaders.trustedIPs&lt;/code&gt;, để ứng dụng nhận được IP thật của người dùng thay vì chỉ thấy IP của Cloudflare.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Mình cũng chủ động &lt;strong&gt;không bật Bot Fight Mode&lt;/strong&gt;. Trên gói Free, cơ chế thử thách của tính năng này có thể cản các ứng dụng truy cập tự động, trong khi quy tắc WAF không cho phép tạo ngoại lệ để bỏ qua nó. Với hệ thống của mình, bật lên có nguy cơ làm gián đoạn webhook GitHub, Sentinel, MCP, đồng bộ Nextcloud và cả Git.&lt;/p&gt;
&lt;p&gt;Kiểm tra bằng &lt;code&gt;curl&lt;/code&gt;, request mang user agent của trình duyệt nhận HTTP 200, còn request mang user agent GPTBot, ClaudeBot hoặc Reflectionbot nhận HTTP 403 ngay tại Cloudflare. Sau bước này, mình bật lại Gitea phía sau proxy.&lt;/p&gt;
&lt;p&gt;Dù vậy, đưa lưu lượng qua Cloudflare vẫn có những giới hạn cần lưu ý:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Phần thân request vượt 100 MB sẽ bị từ chối. Nextcloud tải lên theo từng phần nhỏ nên không vướng giới hạn này.&lt;/li&gt;
&lt;li&gt;Request chờ phản hồi quá lâu, khoảng 100 giây, có thể gặp lỗi 524. Các lời gọi LLM kéo dài mà không truyền kết quả dần là trường hợp cần chú ý.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;IP máy chủ gốc vẫn truy cập được.&lt;/strong&gt; Bot đã biết IP có thể gọi thẳng vào Traefik. Proxy giúp lọc crawler đi qua Cloudflare, nhưng chưa thay thế được việc kiểm soát quyền truy cập.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="theo-dõi"&gt;Theo dõi&lt;/h2&gt;&lt;p&gt;Log sau khi chuyển đổi cho thấy rõ giới hạn cuối cùng này. Mình bật proxy cho Gitea lúc 09:14 UTC, tức 16:14 giờ Việt Nam, rồi lọc access log trên máy chủ gốc để xem ba bot còn truy cập hay không:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;for&lt;/span&gt; ua in GPTBot ClaudeBot Reflectionbot&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$ua&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;grep -c &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$ua&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; access.log&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ClaudeBot&lt;/strong&gt; dừng hẳn lúc 09:14:20, gần như ngay sau khi đổi bản ghi DNS.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reflectionbot&lt;/strong&gt; tiếp tục truy cập rải rác khoảng 6 phút, có thể do bộ nhớ đệm DNS.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GPTBot&lt;/strong&gt; vẫn gửi thẳng đến IP máy chủ gốc thêm &lt;strong&gt;7.056 request trong một tiếng&lt;/strong&gt;, đến 10:14 mới dừng. Trường &lt;code&gt;ClientAddr&lt;/code&gt; ghi nhận IP của OpenAI thay vì Cloudflare. Mình nghi bot giữ kết quả DNS trong bộ nhớ đệm lâu hơn TTL.&lt;/li&gt;
&lt;li&gt;Từ thời điểm đó đến lúc viết bài, khoảng 4 tiếng, máy chủ gốc ghi nhận &lt;strong&gt;0 request&lt;/strong&gt; từ cả ba bot.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Để biết cấu hình này có tiếp tục hiệu quả hay không, mình sẽ theo dõi thêm:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Security → Events&lt;/strong&gt; trên Cloudflare: kiểm tra bot AI có tiếp tục bị chặn và có user agent lạ nào cần bổ sung vào quy tắc WAF hay không.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Access log của Traefik trên máy chủ gốc&lt;/strong&gt;: lọc theo cách trên. Nếu crawler xuất hiện trở lại với &lt;code&gt;ClientAddr&lt;/code&gt; nằm ngoài dải IP Cloudflare, nó đang truy cập trực tiếp. Khi đó, mình sẽ cân nhắc tường lửa chỉ cho phép Cloudflare kết nối vào, hoặc quay lại phương án tunnel.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;robots.txt&lt;/code&gt; của từng host&lt;/strong&gt;: dùng &lt;code&gt;curl https://&amp;lt;app&amp;gt;.example.com/robots.txt&lt;/code&gt; để kiểm tra các dòng &lt;code&gt;Disallow&lt;/code&gt; dành cho bot AI mà Cloudflare thêm vào.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ứng dụng mới&lt;/strong&gt;: Coolify tạo bản ghi DNS ở chế độ DNS only. Host có bản ghi riêng cần được bật proxy thủ công; nếu bỏ sót, bot lại có đường truy cập trực tiếp.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dải IP của Cloudflare&lt;/strong&gt;: dù ít thay đổi, mình vẫn cần cập nhật &lt;code&gt;trustedIPs&lt;/code&gt; của Traefik khi có thay đổi.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Mình vẫn còn một số việc phải làm: bật &lt;code&gt;REQUIRE_SIGNIN_VIEW&lt;/code&gt; để yêu cầu đăng nhập trước khi xem các trang Gitea, dọn bỏ toàn bộ mirror và thay GitHub token của gitea-mirror. Trong đó, yêu cầu đăng nhập mới trực tiếp giải quyết việc người ngoài đọc được repo; proxy chỉ là lớp bảo vệ bổ sung. Mình cũng đang cân nhắc gửi yêu cầu xóa dữ liệu đã thu thập tới Reflection AI, OpenAI và Anthropic.&lt;/p&gt;
&lt;h2 id="bài-học"&gt;Bài học&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Đã công khai thì phải tính đến chuyện bị thu thập dữ liệu rất kỹ.&lt;/strong&gt; Bot AI có thể lần qua từng commit, chứ không dừng ở trang chủ.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cần kiểm tra lại quyền hiển thị khi đồng bộ giữa các hệ thống.&lt;/strong&gt; Một bản ghi cũ trùng tên đã đủ khiến nội dung repo private xuất hiện trong bản mirror public.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Muốn Cloudflare lọc lưu lượng thì phải đưa lưu lượng qua proxy.&lt;/strong&gt; Với hệ thống tự host nhỏ của mình, đây là bước đầu phù hợp để chặn crawler, trước khi cân nhắc tunnel.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cần xem lại các kết nối tự động trước khi bật Bot Fight Mode.&lt;/strong&gt; Máy chủ còn nhận webhook và phục vụ các ứng dụng ngoài trình duyệt, nên chặn bot cũng phải tính đến những luồng truy cập này.&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Newsletter #139</title><link>https://miti99.com/post/2026/10/07/</link><pubDate>Wed, 07 Oct 2026 00:00:00 +0700</pubDate><guid>https://miti99.com/post/2026/10/07/</guid><description>&lt;p&gt;&lt;em&gt;Mời bạn thưởng thức Newsletter #139.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="what-go-taught-us-about-java-garbage-collection"&gt;&lt;a href="https://dev.to/codenameone/what-go-taught-us-about-java-garbage-collection-25ha" rel="noopener noreferrer"&gt;What Go Taught Us About Java Garbage Collection&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;So sánh ParparVM (VM của Codename One, dịch Java sang C và compile AOT) với Go, tác giả ban đầu nghĩ Go nhanh nhờ stack allocation, nhưng rồi nhận ra runtime của Go còn khác ở cách chọn thời điểm collect và chia sẻ công việc GC. Bước đầu tiên là hạ ngưỡng allocation tối thiểu 24 MB: throughput và p99 gần như không đổi, nhưng RSS giảm từ 98 MB xuống 38 MB. Nhóm thử cho ngưỡng bám theo live set nhưng thất bại vì các counter không đếm đủ object sống (partial page, page của mutator, large object), nên chỉ biến floor thành tùy chọn khi build. Với bài benchmark &lt;code&gt;GcPause&lt;/code&gt;, pause tệ nhất từ 2,2–3,3 giây khi dùng một marker giảm còn 0,3–0,9 giây với bốn marker, còn Go khoảng 20 ms. Họ thêm mutator assistance: thread đang allocate khi chạy quá xa sẽ tự mark một batch object thay vì chờ.&lt;/p&gt;
&lt;p&gt;Phần thứ hai là cache ảnh. Trước đây &lt;code&gt;WeakReference&lt;/code&gt; của ParparVM giữ referent như field thường, nên thực chất là strong reference, còn iOS dùng một bảng strong reference và xóa sạch khi có memory warning. PR mới đưa referent vào &lt;code&gt;Reference&lt;/code&gt;, translator phát sinh reference discovery thay vì trace như cạnh strong, và collector tự quyết định giữ hay xóa soft referent. Ca nguy hiểm là thread gọi &lt;code&gt;get()&lt;/code&gt; trong lúc đang marking, có thể nhận về con trỏ đã bị giải phóng, nên các lần đọc được ghi log qua barrier snapshot-at-the-beginning; sau khi lọc các object đã mark, số lượt termination giảm từ 32 xuống 1. Mỗi lần &lt;code&gt;get()&lt;/code&gt; cập nhật độ &amp;ldquo;mới&amp;rdquo;, giúp ảnh hay dùng sống lâu hơn: ở trần 160 MB, hit rate tăng từ 87,99% lên 97,44% mà footprint còn giảm từ 91 MB xuống 82,1 MB.&lt;/p&gt;
&lt;h2 id="how-we-built-a-real-time-stock-pricing-system-at-scale"&gt;&lt;a href="https://itnext.io/how-we-built-real-time-stock-pricing-at-mofid-brokerage-031015ce1301" rel="noopener noreferrer"&gt;How We Built a Real-Time Stock Pricing System at Scale&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Mofid, công ty môi giới chứng khoán lớn nhất Iran, cần hiển thị giá cổ phiếu và quỹ theo thời gian thực. Dữ liệu từ lõi sàn được đọc bằng C++ rồi đẩy vào Kafka dưới dạng Protobuf, khoảng 1.900 tin nhắn mỗi giây và có thể gấp ba vào ngày biến động. Để phân phối tiếp, nhóm chọn Redis Pub/Sub: giá là dữ liệu có thể thay thế, mất một bản cập nhật thì bản sau bù lại, nên ngữ nghĩa at-most-once là đủ. Redpanda Connect chuyển dữ liệu từ Kafka sang Redis mà không cần viết dịch vụ trung gian. Phía client, nhóm chọn SSE vì chỉ cần đẩy một chiều, chạy trên HTTP thông thường và trình duyệt hỗ trợ sẵn. Họ dùng gói &lt;code&gt;eventsource&lt;/code&gt; để gửi header xác thực, gom cập nhật theo lô để giao diện không vẽ lại liên tục, đóng kết nối khi rời trang, và lấy snapshot giá qua API trước khi áp dụng luồng thay đổi.&lt;/p&gt;
&lt;p&gt;Ở backend .NET, mỗi kênh Redis có một hosted service ghi vào &lt;code&gt;System.Threading.Channels&lt;/code&gt; dạng bounded, sức chứa 100 nghìn tin và bỏ tin cũ nhất khi đầy. Một coordinator tạo kênh riêng cho từng người dùng và chỉ gửi dữ liệu họ cần theo trang đang xem. Nhóm triển khai dần qua feature flag Unleash, từ 30% lên 100% người dùng. Kết quả: sáu pod, mỗi pod khoảng 55 MB RAM và 20% CPU lúc cao điểm, phục vụ hàng chục nghìn kết nối đồng thời, hàng đợi đông nhất chỉ 15 tin. Bài học gồm tắt buffering ở proxy bằng &lt;code&gt;X-Accel-Buffering: no&lt;/code&gt;, giải phóng kênh khi client ngắt kết nối để tránh rò rỉ bộ nhớ, và gửi heartbeat mỗi 20 giây để kết nối không bị đóng.&lt;/p&gt;
&lt;h2 id="saving-another-100tb-of-ram-with-math-and-rust"&gt;&lt;a href="https://blog.cloudflare.com/saving-100-tb-of-ram-with-math/" rel="noopener noreferrer"&gt;Saving another 100TB of RAM with math (and Rust)&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Cloudflare giải phóng thêm hơn 100 TB RAM bằng cách thu nhỏ &lt;code&gt;pingora-ketama&lt;/code&gt;, thư viện consistent hashing mà Pingora Backend Router dùng để định tuyến request theo URL. Vấn đề nằm ở các vòng băm: do quy định tuân thủ và tính năng cache, mỗi tổ hợp tính năng cần một vòng riêng, và có tiến trình tốn tới 6 GB bộ nhớ. Bước đầu là thu gọn từng điểm trên vòng. Struct cũ gồm &lt;code&gt;hash: u32&lt;/code&gt; và &lt;code&gt;index: u32&lt;/code&gt;, tức 8 byte. Đổi &lt;code&gt;index&lt;/code&gt; sang &lt;code&gt;u16&lt;/code&gt; không giúp gì vì quy tắc căn chỉnh của Rust vẫn đệm struct lên bội số của trường lớn nhất, nên nhóm chuyển sang mảng &lt;code&gt;[u8; 6]&lt;/code&gt; kèm hàm đọc, giảm 25% bộ nhớ.&lt;/p&gt;
&lt;p&gt;Bước thứ hai là dùng ít điểm hơn. Nhóm tính công thức chính xác cho độ lệch tải khi mỗi máy chủ có k điểm và thấy lợi ích giảm rất nhanh: với 100 máy chủ, 160 điểm mặc định của NGINX đã hạ độ lệch từ khoảng 99% xuống 8%, còn ở mức 100.000 điểm, 90.000 điểm cuối chỉ giảm thêm 0,7% sai số. Va chạm của băm 32 bit còn làm sai số tăng lại khi có quá nhiều điểm. Kết quả là giảm 90% số điểm mà sai số gần như không đổi. Vì đổi vòng sẽ làm mất phần lớn cache và dồn tải về origin, nhóm giữ cả hai vòng trong bộ nhớ, chọn vòng theo băm của từng request, mở rộng dần theo tỉ lệ traffic và theo nhóm trung tâm dữ liệu, luôn có đường quay lui. Bài học: ở quy mô lớn, vài byte nhân lên thành hàng trăm TB, và mặc định &amp;ldquo;hiển nhiên&amp;rdquo; cũng nên được kiểm tra lại bằng số liệu.&lt;/p&gt;
&lt;h2 id="postgres-delete-can-actualy-scale"&gt;&lt;a href="https://www.dbos.dev/blog/scaling-deletions-in-postgres" rel="noopener noreferrer"&gt;Postgres DELETE Can Actualy Scale&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;code&gt;DELETE&lt;/code&gt; trong PostgreSQL thường bị xem là chậm, nên nhiều hệ thống hiệu năng cao chọn &lt;code&gt;TRUNCATE&lt;/code&gt; hoặc xóa cả partition. Nhóm DBOS lại cần &lt;code&gt;DELETE&lt;/code&gt; vì nó linh hoạt hơn: engine durable workflow của họ chạy hàng chục nghìn workflow và step mỗi giây, và phải xóa dữ liệu theo chính sách lưu giữ. Nguyên nhân khiến &lt;code&gt;DELETE&lt;/code&gt; tốn kém nằm ở MVCC: dòng bị xóa không biến mất ngay mà chỉ được đánh dấu bằng ID của giao dịch xóa, để lại dead tuple chờ autovacuum dọn. Index không mang dấu xóa, nên mỗi lần quét index vẫn phải ghé bảng để biết dòng đã chết chưa.&lt;/p&gt;
&lt;p&gt;Thiết kế ban đầu dùng khóa ngoại với &lt;code&gt;ON DELETE CASCADE&lt;/code&gt;. Cách này giữ toàn vẹn dữ liệu nhưng các step của một workflow nằm rải rác theo thời gian, nên mỗi lần xóa lại chạm một trang khác, đến mức xóa workflow còn chậm hơn chạy chúng. Nhóm bỏ khóa ngoại, xóa bảng workflow trước, rồi xóa theo lô các bảng phụ theo đúng thứ tự tạo để tận dụng tính cục bộ của cache. Một anti-join &lt;code&gt;NOT EXISTS&lt;/code&gt; đảm bảo chỉ xóa dữ liệu của workflow đã bị xóa, tránh đụng vào workflow chạy lâu. Vấn đề tiếp theo là truy vấn tìm mốc cắt trên index &lt;code&gt;created_at&lt;/code&gt; có lúc mất khoảng 20 phút, vì khi autovacuum chậm, hàng triệu dead tuple dồn ở đầu index. Chạy &lt;code&gt;VACUUM&lt;/code&gt; thủ công trước và sau mỗi đợt xóa đã khắc phục điều này. Kết hợp lại, tốc độ xóa nhanh hơn khoảng mười lần. Cái giá là ứng dụng phải tự giữ tính nhất quán giữa các bảng, nhưng đổi lại vẫn hỗ trợ được thời gian lưu giữ khác nhau cho từng loại workflow.&lt;/p&gt;
&lt;h2 id="where-postgresql-stores-row-locks"&gt;&lt;a href="https://boringsql.com/posts/row-locks-on-the-page/" rel="noopener noreferrer"&gt;Where PostgreSQL stores row locks&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;PostgreSQL không có bảng trong bộ nhớ để giữ khóa dòng. Khóa được ghi thẳng vào header của tuple: trường &lt;code&gt;t_xmax&lt;/code&gt;, vốn lưu giao dịch xóa, cũng lưu giao dịch đang khóa, còn các bit trong &lt;code&gt;t_infomask&lt;/code&gt; cho biết chế độ khóa và &lt;code&gt;t_xmax&lt;/code&gt; chỉ là khóa hay không. Vì vậy mọi lần khóa dòng đều là một lần ghi xuống trang dữ liệu. Khi commit, dấu khóa cũng không bị xóa: nó hết hiệu lực vì giao dịch đã kết thúc, rồi VACUUM hoặc người khóa tiếp theo ghi đè. Có bốn mức khóa. Kiểm tra khóa ngoại lấy &lt;code&gt;FOR KEY SHARE&lt;/code&gt;, &lt;code&gt;UPDATE&lt;/code&gt; không đổi cột khóa lấy &lt;code&gt;FOR NO KEY UPDATE&lt;/code&gt;, còn &lt;code&gt;DELETE&lt;/code&gt; và cập nhật cột khóa lấy &lt;code&gt;FOR UPDATE&lt;/code&gt;, xung đột với mọi mức khác. Do đó dùng &lt;code&gt;FOR UPDATE&lt;/code&gt; trên dòng cha sẽ chặn mọi lệnh chèn dòng con tham chiếu tới nó, và &lt;code&gt;FOR NO KEY UPDATE&lt;/code&gt; thường là lựa chọn đúng hơn.&lt;/p&gt;
&lt;p&gt;Phiên phải chờ sẽ chờ khóa &lt;code&gt;transactionid&lt;/code&gt; của giao dịch đang giữ, và lấy thêm khóa &lt;code&gt;tuple&lt;/code&gt; để xếp hàng. &lt;code&gt;pg_locks&lt;/code&gt; chỉ thấy các mục chờ này chứ không thấy ai đang giữ dòng. Khi hai giao dịch cùng khóa một dòng, &lt;code&gt;t_xmax&lt;/code&gt; lưu một MultiXactId, trỏ tới danh sách thành viên trong thư mục &lt;code&gt;pg_multixact&lt;/code&gt;. Ví dụ trong bài: một giao dịch dài giữ khóa trên các dòng cha, trong khi 5.000 lệnh chèn tự commit tạo ra 5.000 multixact riêng. Mỗi thành viên tốn khoảng năm byte, giới hạn 2^32 thành viên tương đương khoảng 20 GB, và khi cạn sẽ gây lỗi &lt;code&gt;multixact &amp;quot;members&amp;quot; limit exceeded&lt;/code&gt;. Cơ sở dữ liệu có thể chạm ngưỡng chống wraparound của multixact dù không có dead tuple. Nên theo dõi &lt;code&gt;mxid_age()&lt;/code&gt;, &lt;code&gt;relminmxid&lt;/code&gt; và &lt;code&gt;next_multi_offset&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="a-40ms-go-gc-pause-caused-by-swap"&gt;&lt;a href="https://frn.sh/go-gc/" rel="noopener noreferrer"&gt;A 40ms Go GC pause caused by swap&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Tác giả bật swap để hấp thụ những đợt tăng bộ nhớ đột ngột, và phát hiện bộ thu gom rác (GC) của Go có lúc dừng toàn bộ chương trình (stop-the-world) tới 40 ms, trong khi trung vị chỉ khoảng 51 µs, tức chậm hơn khoảng 800 lần. Hệ thống gồm một cgroup chứa hai tiến trình: một tiến trình Go đọc dữ liệu bằng &lt;code&gt;io.ReadAll&lt;/code&gt; rồi giải mã bằng &lt;code&gt;proto.Unmarshal&lt;/code&gt;, và một máy chủ HTTP gần như nhàn rỗi. Ban đầu tác giả cho rằng kernel thu hồi bộ nhớ theo từng cgroup nên hai tiến trình sẽ bị đẩy ra swap cùng lúc, khó xảy ra cảnh GC tranh chấp với swap. Giả định này hóa ra sai.&lt;/p&gt;
&lt;p&gt;Dùng BPF để đếm page fault trong lúc chương trình bị dừng, tác giả thấy lần dừng tệ nhất kéo dài 39.902 µs, có 228 lỗi trang, và riêng các lỗi này đã chiếm 39.013 µs. Dịch địa chỉ bằng &lt;code&gt;addr2line&lt;/code&gt; cho thấy chúng nằm trong &lt;code&gt;runtime.(*spanSet).reset&lt;/code&gt; và &lt;code&gt;runtime.nextMarkBitArenaEpoch&lt;/code&gt;, thuộc giai đoạn kết thúc sweep khi GC bắt đầu chu kỳ mới. Nguyên nhân là runtime Go giữ siêu dữ liệu quản lý GC bên ngoài heap, ở những vùng được tái sử dụng nhưng hiếm khi bị chạm tới. Kernel thu hồi trang theo tuổi nên đẩy chúng ra swap. Khi GC dừng mọi goroutine rồi đọc lại vùng này, mỗi lỗi trang phải chờ đọc đĩa, và vì không còn ai chạy nên độ trễ ảnh hưởng toàn cục. Việc cấp phát cũng chậm đi: dựng một thông điệp 511 KiB thường mất 3–5 ms, nhưng lên tới 105 ms trên NVMe và 903 ms trên ổ mạng. Bài học là swap không xấu, nhưng không hợp với GC trong tải kiểu này.&lt;/p&gt;
&lt;h2 id="how-to-optimize-when-you-cant-do-anything"&gt;&lt;a href="https://hdombrovskaya.wordpress.com/2026/08/25/how-to-optimize-when-you-cant-do-anything/" rel="noopener noreferrer"&gt;How to optimize when you can’t do anything!&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Hettie Dombrovskaya kể lại một ca tối ưu truy vấn PostgreSQL mà mọi cách quen thuộc đều bị chặn. Bảng không phân vùng, nặng 750 GB với 16 tỉ dòng. Một truy vấn production đột ngột chậm đi, lúc vài giây, lúc vài phút. Truy vấn tìm các bản ghi có khoảng từ &lt;code&gt;start_date&lt;/code&gt; đến &lt;code&gt;end_date&lt;/code&gt; chứa một ngày cho trước cùng ba điều kiện bằng. Index GiST trên khoảng ngày là lựa chọn lý tưởng, nhưng xây trên 16 tỉ dòng sẽ mất hơn 24 giờ. Index tốt nhất hiện có là &lt;code&gt;(a, start_date, end_date)&lt;/code&gt;, nhưng &lt;code&gt;a&lt;/code&gt; lại là cột kém chọn lọc nhất, còn với một ngày gần đây thì điều kiện trên &lt;code&gt;start_date&lt;/code&gt; khớp gần như mọi dòng.&lt;/p&gt;
&lt;p&gt;Tác giả thử thêm cận dưới cho &lt;code&gt;start_date&lt;/code&gt;, nhưng khách hàng cần mọi bản ghi chứ không chỉ bản ghi gần đây. Lấy &lt;code&gt;start_date&lt;/code&gt; nhỏ nhất theo cột &lt;code&gt;a&lt;/code&gt; cũng không giúp vì giá trị đó có thể rất cũ. Partial index cho nhóm tổ hợp mà khách hàng liệt kê đưa thời gian xuống dưới 100 ms, nhưng chỉ cho nhóm đó. Chìa khóa nằm ở bảng tính của khách hàng: nó ghi ngày đầu tiên mỗi tổ hợp &lt;code&gt;(a, b, c)&lt;/code&gt; xuất hiện. Tác giả nạp danh sách hơn 2.000 dòng này vào một bảng ánh xạ, rồi lấy ngày đó làm cận dưới chính xác cho &lt;code&gt;start_date&lt;/code&gt;. Truy vấn chạy dưới 100 ms. Vì danh sách nằm ở cơ sở dữ liệu khác, bản cuối dùng foreign table chỉ đọc và vẫn nhanh. Bài học là khi không thể xây index mới, một bảng nhỏ đóng vai trò &amp;ldquo;index&amp;rdquo; có thể thay thế, và hiểu dữ liệu thật cùng mục đích của truy vấn là công cụ tối ưu mạnh nhất.&lt;/p&gt;
&lt;h2 id="four-ways-to-do-super-fast-packet-processing-in-go"&gt;&lt;a href="https://toonk.io/packetio/" rel="noopener noreferrer"&gt;Four ways to do super fast packet processing in Go&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Andree Toonk cho rằng xử lý gói tin trong Go là phần dễ, còn phần khó là đưa gói tin giữa card mạng và chương trình thật nhanh. Mỗi cơ chế có mô hình riêng, nên ông viết packetio, một thư viện Go gom bốn backend sau cùng một API. AF_PACKET, socket mà tcpdump dùng, dễ cài và chạy gần như mọi nơi nhưng gói tin đi qua phần lớn đường xử lý của kernel. AF_XDP chặn gói tin sớm trong driver và chuyển vào vùng nhớ dùng chung với ứng dụng, trong khi Linux vẫn quản lý card nên SSH hay ARP vẫn chạy. DPDK là framework userspace lâu đời nhưng khó học, còn mlx5 Direct Verbs cho phép ứng dụng nói chuyện trực tiếp với hàng đợi phần cứng của card NVIDIA ConnectX. Đổi backend chủ yếu chỉ là đổi lời gọi &lt;code&gt;Open&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Bài đo dùng hai máy AMD EPYC với card ConnectX-6 Dx 100G và gói tin 64 byte, tức khoảng 148,8 triệu gói mỗi giây, CPU được đo trên toàn máy kể cả thời gian softirq của kernel. Ở chiều nhận, AF_PACKET chỉ đạt 2,3 triệu gói mỗi giây dù tốn 52,8 lõi, AF_XDP đạt 146,5 triệu với 11,4 lõi, Direct Verbs và DPDK đạt trọn 148,6 triệu với 10 và 8 lõi. Ở chiều gửi, DPDK chỉ cần 3 lõi, còn AF_XDP cần 20 lõi. Khi chuyển tiếp gói tin, Direct Verbs dẫn đầu với 147,4 triệu gói mỗi giây trên 12 lõi. Đáng chú ý, AF_XDP tốn nhiều CPU ở softirq, dễ bỏ sót nếu chỉ đo tiến trình Go. Tác giả khuyên dùng Direct Verbs với card ConnectX, AF_XDP cho phần cứng Linux thông thường, DPDK khi cần hệ sinh thái driver rộng, và AF_PACKET chỉ để quan sát gói tin.&lt;/p&gt;
&lt;h2 id="how-uber-protects-against-retry-storms"&gt;&lt;a href="https://www.uber.com/us/en/blog/protecting-against-retry-storms/" rel="noopener noreferrer"&gt;How Uber Protects Against Retry Storms&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Ở Uber, retry được áp dụng đồng loạt: dịch vụ chỉ kiểm soát được số lần thử lại chứ không biết khi nào nên thử, vì khó phân biệt lỗi do chính dịch vụ gây ra hay chỉ được chuyển tiếp từ bên dưới. Với lỗi thoáng qua thì không sao, nhưng khi một dịch vụ quá tải, retry lại dồn thêm tải lên nó và nhân lên qua từng tầng gọi. Trong chuỗi bảy dịch vụ, mỗi tầng thử lại một lần, tải ở tầng sâu nhất gấp tám lần ban đầu. Retry budget 10% giảm con số này xuống khoảng 1,33 lần, nhưng vẫn không thấy được hiệu ứng khuếch đại giữa các dịch vụ. Retry chỉ có ích khi lỗi độc lập và ngẫu nhiên.&lt;/p&gt;
&lt;p&gt;Giải pháp của Uber là &amp;ldquo;quyền sở hữu lỗi&amp;rdquo;. Nếu lỗi của một dịch vụ bắt nguồn từ lời gọi xuống dưới bị hỏng thì đó chỉ là triệu chứng, còn dịch vụ tự gây lỗi mới là nơi sở hữu lỗi. Thành phần Service Dependency Analysis đối chiếu lỗi vào với lỗi ra để gắn header &lt;code&gt;x-uber-error-claim&lt;/code&gt;. Chỉ cạnh gọi tới nơi sở hữu lỗi được thử lại, và khi chuyển lỗi lên trên, claim bị gỡ bỏ để các tầng trên không thử lại nữa. Cơ chế này nằm trong middleware retry dùng chung, nên các dịch vụ được bảo vệ mà không phải viết thêm mã. Trong sự cố ngày 18/11/2025, một dịch vụ lõi nằm sâu hơn năm tầng trả lỗi hàng loạt. Nếu chỉ có retry budget, lưu lượng tới nó sẽ tăng 46–135%, nhưng hệ thống đã chặn được 9,5 triệu request thừa. Bán kính tối đa của retry storm giảm từ 25 tầng xuống 3, trung bình từ 20 xuống 2.&lt;/p&gt;
&lt;h2 id="the-real-cost-of-a-single-insert-in-postgres"&gt;&lt;a href="https://www.tigerdata.com/blog/real-cost-of-single-insert-in-postgres" rel="noopener noreferrer"&gt;The Real Cost of a Single Insert in Postgres&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Bài viết đo xem một lệnh &lt;code&gt;INSERT&lt;/code&gt; trong PostgreSQL 16 thực sự ghi bao nhiêu byte, so với dữ liệu gửi đi, gọi là hệ số khuếch đại ghi. Bảng thử nghiệm có ba index, mỗi dòng khoảng 40 byte. Riêng phần lưu trữ, mỗi tuple mang header 23 byte để phục vụ MVCC, được đệm lên 24 byte, cộng thêm 4 byte con trỏ dòng trong trang, nên một dòng chiếm 68 byte, gấp 1,7 lần dữ liệu thật. Mọi index đều được cập nhật đồng bộ trong cùng giao dịch: ba index nghĩa là ba lần duyệt B-tree và ba trang lá bị sửa. Khóa thời gian tăng dần dồn mọi lệnh chèn vào trang lá ngoài cùng bên phải, còn index có nhiều giá trị khác nhau rải lệnh chèn khắp cây, khiến trang cần dùng ít khi có sẵn trong cache. Mỗi lần chèn vào heap và vào từng index đều sinh một bản ghi WAL riêng, tức bốn bản ghi mỗi dòng.&lt;/p&gt;
&lt;p&gt;Thí nghiệm chèn 100.000 dòng và đo bằng &lt;code&gt;pg_current_wal_lsn()&lt;/code&gt; cùng &lt;code&gt;pg_stat_wal&lt;/code&gt;. Ngay sau checkpoint, hệ số là 10,74 vì trang đầu tiên bị sửa phải ghi nguyên trang vào WAL. Khi không có checkpoint, hệ số là 8,73, còn khi bỏ hết index chỉ còn 2,44, nghĩa là khoảng 70% WAL đến từ việc duy trì index. Ở trạng thái ổn định, mỗi dòng 40 byte tốn khoảng 345 byte WAL, tức 17 MB/s log cho 50.000 lệnh chèn mỗi giây. Tác giả gợi ý theo thứ tự rủi ro tăng dần: gom dòng bằng &lt;code&gt;COPY&lt;/code&gt; hoặc &lt;code&gt;INSERT&lt;/code&gt; nhiều dòng, xóa index không dùng (xem &lt;code&gt;idx_scan&lt;/code&gt;), bật &lt;code&gt;wal_compression&lt;/code&gt;, rồi tăng &lt;code&gt;max_wal_size&lt;/code&gt; và &lt;code&gt;checkpoint_timeout&lt;/code&gt;. Cuối bài, Tiger Data giới thiệu engine lưu trữ dạng cột của họ.&lt;/p&gt;</description></item><item><title>Newsletter #138</title><link>https://miti99.com/post/2026/10/06/</link><pubDate>Tue, 06 Oct 2026 00:00:00 +0700</pubDate><guid>https://miti99.com/post/2026/10/06/</guid><description>&lt;p&gt;&lt;em&gt;Mời bạn thưởng thức Newsletter #138.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="what-i-believe-about-the-future-of-software-development"&gt;&lt;a href="https://thorstenball.com/blog/2026/09/19/what-i-believe-about-the-future-of-software-development/" rel="noopener noreferrer"&gt;What I believe about the future of software development&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Thorsten Ball đăng lại lên blog loạt dự đoán từng gây bão trên X, để ghi lại những gì tác giả tin vào tháng 9/2026. Theo tác giả, code review gần như đã chết: con người sẽ không còn tìm ra bug trong code do model viết, mà chỉ review hệ thống và cách các thành phần ghép lại. Unit test cũng có thể biến mất, vì model đã viết được 900 dòng Arduino C chạy đúng ngay lần đầu. Kỹ năng viết code sẽ mai một như nghề đóng giày thủ công, nhưng kỹ năng xây dựng phần mềm, tức giải bài toán kinh doanh, biết khi nào ship và lấy feedback, lại quan trọng hơn bao giờ hết. Phần lớn bug sẽ là bug &amp;ldquo;bạn yêu cầu sai thứ&amp;rdquo;, không phải bug viết code; còn tối ưu hiệu năng bởi con người vẫn chỉ là trường hợp hiếm.&lt;/p&gt;
&lt;p&gt;Các nhận định gây tranh cãi hơn: terminal, shell và CLI sẽ không còn dành cho con người; token là mô hình tính toán mới của kỷ nguyên &amp;ldquo;hậu nhị phân&amp;rdquo;. Bộ ba PM/Design/Eng cùng Agile, SCRUM sẽ biến mất, kỹ sư chỉ làm &amp;ldquo;proxy bằng thịt&amp;rdquo; chuyển ticket cho agent sẽ mất giá trị. Khái niệm &amp;ldquo;good code&amp;rdquo; vốn phục vụ con người, nên chưa chắc còn quan trọng khi agent sửa phần lớn code. Người không đủ tiền mua token sẽ bị đẩy ra khỏi cuộc chơi, còn UI có thể được sinh ra tức thì. Dù vậy, tác giả thừa nhận sẽ mất cả một thế hệ để &amp;ldquo;phần mềm mới&amp;rdquo; thay thế &amp;ldquo;phần mềm cũ&amp;rdquo;, và 10 năm nữa vẫn có người được thuê viết code, chỉ là bạn có muốn công việc đó không.&lt;/p&gt;
&lt;h2 id="trying-the-software-factory-pattern"&gt;&lt;a href="https://lethain.com/software-factory-experiment/" rel="noopener noreferrer"&gt;Trying the Software Factory pattern.&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Will Larson nhận xét rằng năm 2026, các pattern AI hiệu quả xuất hiện nhanh hơn tốc độ tác giả kịp áp dụng. Tại Imprint, năm nay: tháng 1 đưa mọi kỹ sư dùng Claude Code mỗi ngày, tháng 3 mở rộng sang mọi bộ phận với Claude Code hoặc Claude Cowork. Tháng 4, vì local development nghẽn ở mô hình worktree, đội tạo khoảng 10 workspace chứa bản checkout riêng của mọi repository để agent tạo pull request xuyên các monorepo. Tháng 6, cả công ty chuyển hẳn từ Jira sang Linear để agent có hệ thống task minh bạch, ít rắc rối phân quyền. Tháng 7, ra mắt harness điều phối nội bộ &amp;ldquo;Agent Fleet&amp;rdquo;, tương tự Minions của Stripe, cho các ticket nhỏ.&lt;/p&gt;
&lt;p&gt;Bước tiếp theo là software factory pattern: lặp trên một mục tiêu rộng, để harness tự thúc đẩy tiến độ. Phiên bản đầu là skill &lt;code&gt;/linear-project-loop&lt;/code&gt;, đọc một Linear project và kiểm tra xem đã có RFC trên Notion mô tả mục tiêu, cách đo và hướng tiếp cận, cùng dashboard Datadog hoặc query Snowflake đo tiến độ hay chưa; nếu thiếu, nó cùng bạn tạo ra. Sau đó nó rà metric và issue, thêm việc mới, rồi làm task không bị chặn như viết pull request, nhắc review, đặt câu hỏi. Khi mô tả project đã cũ, vòng lặp chạy lại từ đầu. Pattern này buộc tác giả nhận ra chỗ vô tình giữ thông tin mục tiêu cho riêng mình, và giúp theo dõi project sau khi release, như phát hiện ngay khi tỷ lệ lỗi của tính năng passkeys tăng. Cuối cùng, các mảnh ghép chỉ cộng hưởng khi có đủ nhau: Datadog MCP, Snowflake, Linear làm nguồn trạng thái duy nhất và một harness chạy độc lập với laptop.&lt;/p&gt;
&lt;h2 id="back-to-coupling-and-cohesion"&gt;&lt;a href="https://bastrich.tech/coupling-and-cohesion/" rel="noopener noreferrer"&gt;Back to Coupling and Cohesion&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Daniil Bastrich cho rằng người mới và những tín đồ clean code thường lạm dụng SOLID, GRASP hay KISS, khiến codebase ấn tượng nhưng khó bảo trì. Theo tác giả, các nguyên tắc này đều quy về việc quản lý coupling và cohesion ở mọi cấp độ. Coupling đo mức các component gắn với nhau đến đâu,, khiến sửa một cái buộc phải sửa cái khác; cohesion đo mức mọi phần trong một component cùng phục vụ một mục đích duy nhất. Để có số liệu, tác giả đề xuất thống kê từ công việc thực tế: với mỗi task, ghi lại effort trên từng component đã chạm vào; với mỗi component, ghi lại effort cho từng concern. Coupling được tính bằng 1 trừ tổng bình phương effort chia cho bình phương tổng effort, còn cohesion chính là tỷ số đó, tương tự chỉ số Gini-Simpson và Herfindahl-Hirschman.&lt;/p&gt;
&lt;p&gt;Ví dụ, nếu rule độ dài mật khẩu được kiểm tra riêng ở form đăng ký (effort 4) và form đổi mật khẩu (effort 6), coupling của thay đổi là 0.48, còn cohesion mỗi form là 1. Cách đo này phản ánh ảnh hưởng thật lên công việc hằng ngày. Low coupling, high cohesion giúp tập trung vào ít thứ hơn, nên ít bug và dễ bảo trì hơn. Tác giả lần lượt quy các nguyên tắc về hai khái niệm này: Single Responsibility là high cohesion, Open/Closed, Liskov, Interface Segregation, Dependency Inversion, DRY và Law of Demeter đều nhằm giảm coupling hoặc tăng cohesion, còn KISS giảm lượng thông tin phải nhớ cùng lúc. Nguyên tắc này có giới hạn với hệ thống nhúng, ứng dụng low-latency hay script dùng một lần; đừng theo bất kỳ nguyên tắc nào chỉ vì chính nó.&lt;/p&gt;
&lt;h2 id="introducing-system-one-models--jev"&gt;&lt;a href="https://typesafe.ai/blog/introducing-system-one-models-and-jev" rel="noopener noreferrer"&gt;Introducing System One Models &amp;amp; Jev&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Diogo Almeida, founder của TypeSafe và từng tham gia nghiên cứu nền tảng cho ChatGPT tại OpenAI, đặt câu hỏi: model đã vượt con người ở khả năng chat nhiều năm nay, vậy tự động hóa ở đâu? Sau hai năm hoạt động bí mật, TypeSafe AI công bố System One Model, lớp frontier model mới đưa ra quyết định nhanh, có cấu trúc để phần mềm dùng trực tiếp. Stack gồm kiến trúc riêng, parallel sampler và phương pháp huấn luyện Reinforcement Learning for Calibrated Decisions (RLCD). Model đầu tiên là Jev, đang early access, được mô tả như một &amp;ldquo;function call trí tuệ frontier&amp;rdquo;: nhận trạng thái phi cấu trúc, trả về các quyết định có kiểu kèm xác suất đã được hiệu chỉnh. Jev từ bỏ việc sinh chuỗi, nên không thể có type error hay hallucinate, sinh mọi output song song trong một query, phản hồi trong 70–500ms và chỉ tốn $0.042 cho mỗi triệu input token, output thì miễn phí.&lt;/p&gt;
&lt;p&gt;Để đánh giá, nhóm xây dựng workflow eval: mọi model chạy cùng một workflow trong code và được so với trung bình dự đoán của GPT-6 Astra và Fable 5.1. Kết quả, Jev nhanh hơn 193.6 lần, rẻ hơn 444.6 lần, dù nhóm thừa nhận con số này thuộc mức cao và có thể thiên lệch. Use case phù hợp là &amp;ldquo;smart if-statement&amp;rdquo; trong workflow, map-reduce trên dữ liệu lớn, ứng dụng real-time và kiểm duyệt output của LLM. Demo gồm một bot chơi Doom với 10 query mỗi giây, tốn khoảng $7/giờ, và Wikiracing giữa các trang Wikipedia. Cái tên System One lấy cảm hứng từ cuốn Thinking, Fast and Slow của Daniel Kahneman, còn Jev đặt theo William Stanley Jevons: trí tuệ càng rẻ, use case càng nhiều.&lt;/p&gt;
&lt;h2 id="system-one-models--explained"&gt;&lt;a href="https://unzip.dev/0x025-system-one-models/" rel="noopener noreferrer"&gt;System One Models 🚦 Explained&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Agam More giải thích System One model, với ví dụ Jev của TypeSafe AI, như một câu lệnh if nhanh, rẻ và biết mình chắc chắn đến mức nào. Bạn đưa vào một trạng thái cùng câu hỏi có kiểu, model trả về JSON kèm xác suất cho từng đáp án, giống một bài thi trắc nghiệm. Có ba loại câu hỏi: Choice chọn một lựa chọn trong tập cho trước, Score chấm điểm theo thang bạn định nghĩa, còn Noul trả về khả năng một mệnh đề là đúng. Ví dụ, với ticket than phiền tích hợp Stripe lỗi ba ngày, model định tuyến sang bộ phận technical với xác suất 0.85 và đánh dấu khẩn cấp. Jev được huấn luyện hai vòng như chat model, nhưng vòng hai dùng RLCD thưởng cho xác suất trung thực, với dữ liệu hoàn toàn synthetic. Model không thể trả giá trị nằm ngoài schema, nhưng vẫn có thể chọn sai.&lt;/p&gt;
&lt;p&gt;Nên dùng Jev khi code hỏi LLM cùng một dạng câu hỏi đóng hàng nghìn lần, như định tuyến ticket hay lọc spam, rồi tự động hóa ca chắc chắn và chuyển phần còn lại sang review. Theo JevBench, Jev rẻ hơn 6 đến 7 lần mỗi quyết định so với GPT-5.6 Luna hay Gemini 3.1 Flash-Lite. Không nên dùng khi đáp án không nằm trong danh sách cố định, khi cần text, hoặc khi độ chính xác quan trọng hơn chi phí; Jev cũng không tự trả lời &amp;ldquo;không cái nào&amp;rdquo; nếu bạn không đưa lựa chọn đó. Đối thủ gồm SemIf, Laya, Kev, GLiNER2 và Outlines. Tác giả dự đoán calibration sẽ là phần khó nhất, và các ông lớn sớm muộn sẽ có model tương tự.&lt;/p&gt;
&lt;h2 id="how-data-is-replicated-in-distributed-systems--raft-from-scratch---part-1"&gt;&lt;a href="https://sushantdhiman.dev/blog/raft/" rel="noopener noreferrer"&gt;How Data Is Replicated In Distributed Systems | Raft From Scratch - Part 1&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Sushant Dhiman mở đầu loạt bài hai phần bằng một bài toán quen thuộc: khi xây phần mềm phân tán có replication như database, bạn cần một hệ thống leader/follower, nhưng làm sao để năm node bầu ra đúng một leader? Câu trả lời là Raft, thuật toán consensus của Diego Ongaro và John Ousterhout, được thiết kế để dễ hiểu và dễ cài đặt hơn Paxos. Consensus giúp các replica đồng thuận về một chuỗi quyết định có thứ tự, tránh việc database này có 10 giao dịch còn database kia chỉ có 7 do network partition. Bài viết chỉ tập trung vào leader election. Raft có ba loại node: leader xử lý request và điều phối log replication, follower nhận RPC từ leader, còn candidate là follower đứng ra tổ chức bầu cử khi hết election timeout. Hai RPC chính là AppendEntries, dùng để replicate log và gửi heartbeat, và RequestVote, dùng để xin phiếu.&lt;/p&gt;
&lt;p&gt;Khi leader chết, follower không nhận được heartbeat; sau một timeout ngẫu nhiên, ví dụ trong khoảng 150–300ms, nó trở thành candidate và thắng nếu được đa số phiếu. Timeout ngẫu nhiên giúp giảm khả năng chia phiếu, còn election term đóng vai trò logical clock: mỗi lần bầu cử, term tăng lên, mỗi node chỉ bầu tối đa một candidate trong một term, và node nào thấy term cao hơn sẽ lùi về làm follower. Nếu không ai thắng do chia phiếu, các candidate chỉ cần thử lại sau timeout mới. Phần cuối là một bản cài đặt học tập bằng Go và gRPC, gồm election loop, RequestVote, heartbeat qua AppendEntries và file proto, chạy ba node trên các port 5001 đến 5003. Bản này cố ý bỏ qua log replication, persistence và thay đổi thành viên cluster.&lt;/p&gt;
&lt;h2 id="jev-means-structured-output-is-interesting-again"&gt;&lt;a href="https://www.seangoedecke.com/jev-means-structured-output-is-interesting-again/" rel="noopener noreferrer"&gt;Jev means structured output is interesting again&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Sean Goedecke vốn không viết về model mới, nhưng phá lệ với Jev, một &amp;ldquo;System One&amp;rdquo; model. LLM thông thường sinh output theo kiểu autoregressive, từng token một, nên rất linh hoạt nhưng chậm và đôi khi kỳ quặc. Jev nhận prompt bằng ngôn ngữ tự nhiên nhưng chỉ trả về structured output, và chính giới hạn đó mang lại đặc tính đáng giá. Jev luôn nhanh, phản hồi từ khoảng 70ms đến tối đa 500ms, vì không cần sinh từng token mà trả lời nhiều câu hỏi song song trong một forward pass. Ví dụ ấn tượng nhất là Jev chơi được Doom theo thời gian thực từ mô tả trạng thái game dạng text. Với tác giả, structured output nhanh có thể là một primitive tính toán mới: khi chèn 100ms trí tuệ siêu rẻ vào các điểm ra quyết định, ta có thể viết những chương trình không còn trông như chatbot.&lt;/p&gt;
&lt;p&gt;Tuy vậy, tác giả cho rằng structured output nhanh vốn đã khả thi. Nó chậm chỉ vì ít ai quan tâm, và người quan tâm thường cần JSON lớn nên dùng grammar-constrained decoding. Với các lựa chọn giới hạn, chỉ cần prefill &lt;code&gt;&amp;quot;choice&amp;quot;: &amp;quot;&lt;/code&gt; rồi sinh đúng một token trong tập lựa chọn; tác giả thử với Qwen2.5-1.5B-Instruct và thấy nhanh gấp 2 đến 3 lần. Vì vậy Jev có lẽ không có &amp;ldquo;moat&amp;rdquo; kỹ thuật lớn, dù fine-tune riêng cho structured output vẫn là lợi thế. Jev cũng khó thông minh bằng frontier LLM vì không dùng được test-time compute, còn tuyên bố miễn nhiễm hallucination chỉ là cách chơi chữ, vì nó vẫn chọn sai được. Dù vậy, tác giả mong Jev thành công và thúc đẩy các lab lớn ra mắt phiên bản model riêng tối ưu cho structured output.&lt;/p&gt;
&lt;h2 id="why-do-we-listen-to-people-with-no-skin-in-the-game"&gt;&lt;a href="https://rednafi.com/zephyr/no-skin-in-the-game/" rel="noopener noreferrer"&gt;Why do we listen to people with no skin in the game?&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Redowan Delowar, trong bối cảnh lập trình công nghiệp, tin rằng nếu không trực tiếp làm, ý kiến của bạn rất dễ sai; có ảnh hưởng không đồng nghĩa với đúng; và nói về code khác hẳn giao phần mềm dưới ràng buộc thực tế. Ví dụ điển hình là một director &amp;ldquo;nghiện AI&amp;rdquo; vọc LLM một buổi chiều, dựng vài ứng dụng dùng một lần rồi tin rằng software engineering đã được giải quyết, sau đó đòi năng suất gấp 10 lần, gạt đi mọi ma sát tổ chức. Tác giả dùng LLM rất nhiều nhưng cũng đụng phải giới hạn của chúng thường xuyên hơn người chỉ one-shot một ứng dụng todo. Bạn vẫn phải nghe theo quản lý nếu không muốn mất việc, nhưng điều đó không khiến họ đúng; muốn đúng, bạn phải là người cầm pager khi phần mềm &amp;ldquo;vibe-slop&amp;rdquo; sập lúc 3 giờ sáng.&lt;/p&gt;
&lt;p&gt;Nhóm thứ hai là các software influencer với những tuyên bố chung chung như &amp;ldquo;viết lại mọi thứ bằng Rust&amp;rdquo;, &amp;ldquo;TypeScript nên chết đi&amp;rdquo; hay &amp;ldquo;Kotlin là Java hàng Temu&amp;rdquo;, vì đó là cách theo kịp thuật toán, và nói về phần mềm thì rất nhiều tiền. Khách mời podcast thường đáng học hỏi, nhưng lời khuyên của host nên nghe có chọn lọc. Những người bán kỹ năng AI hôm nay cũng chính là người từng bán khóa học JavaScript framework rồi nhảy sang blockchain và NFT. Các tuyên bố đó hiếm khi tính đến việc tổ chức chọn ngôn ngữ dựa trên nguồn nhân lực địa phương, tốc độ xây sản phẩm và ngôn ngữ founder biết, và LLM không thay đổi điều đó. Lời khuyên thì rẻ, hãy để ý động cơ của người đưa ra nó.&lt;/p&gt;
&lt;h2 id="you-can-run-git-on-object-storage-if-you-re-make-packfiles"&gt;&lt;a href="https://www.tigrisdata.com/blog/objgit-packfiles/" rel="noopener noreferrer"&gt;You can run git on object storage if you re-make packfiles&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Xe Iaso đang xây objgit, một Git server mã nguồn mở chạy trên object storage của Tigris. Cách tiếp cận đầu tiên dùng một lớp filesystem làm trung gian, nhưng không chịu nổi các repository cỡ thực tế. Git về bản chất là một biển object định danh theo nội dung; repository Linux kernel có khoảng 11 triệu object, nên nếu mỗi lệnh GetObject tốn 10ms thì lấy hết sẽ mất hơn một giờ. Git giải quyết bằng packfile, gom object vào một file kèm index, nhưng format này được thiết kế cho đĩa local và mmap, nơi đọc chỉ tốn vài nano giây thay vì 10ms cho mỗi round trip mạng. Index chỉ ghi kích thước sau giải nén chứ không ghi kích thước nén, nên không thể dựng HTTP Range request để lấy riêng một object.&lt;/p&gt;
&lt;p&gt;Lấy cảm hứng từ cặp file .bin và .cue khi backup đĩa CD, tác giả tự thiết kế một packfile format mới: object nằm nối tiếp nhau trong file .bin tối đa 128MiB, còn metadata nằm riêng trong cue sheet nhị phân, biến kho Git thành columnar store. Cue sheet lưu cả kích thước nén và chưa nén cùng offset, nên dễ dựng Range request, trong khi toàn bộ packfile được tải nền và các object ở cuối được Range-request cho đến khi bản tải về &amp;ldquo;đuổi kịp&amp;rdquo;. Format còn dùng zstd. Tự chế format nghe như phạm luật Chesterton&amp;rsquo;s Fence, nhưng vì Git phân tán nên nếu có sự cố, chỉ cần push lại. Kết quả rất ấn tượng: push repository Xe/x giảm từ 3 phút 29 giây xuống 14.3 giây và từ hơn 9.000 xuống 30 request S3. Dự án vẫn chưa có xác thực và phân quyền, nên chưa nên đưa lên Internet.&lt;/p&gt;
&lt;h2 id="why-cafebabe"&gt;&lt;a href="https://www.artima.com/insidejvm/whyCAFEBABE.html" rel="noopener noreferrer"&gt;Why CAFEBABE?&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Tháng 5/1996, khi đang viết bài &amp;ldquo;The Class File Lifestyle&amp;rdquo; cho JavaWorld, Bill Venners tò mò về magic number của file class Java. Bốn byte đầu tiên của mọi file class đều phải là 0xCAFEBABE, giúp công cụ nhận diện nhanh file class. Tác giả đăng câu hỏi lên newsgroup comp.lang.java, và trang này lưu lại toàn bộ chuỗi thảo luận. Nhiều người đưa ra phỏng đoán vui: số magic 32 bit dễ xử lý và ít trùng lặp, nhóm Java muốn gợi đến ẩn dụ cà phê nhưng hệ hex không có chữ J hay V, nên chọn cụm có chữ CAFE thay vì &amp;ldquo;A FAB CAFE&amp;rdquo;, &amp;ldquo;CAFE FACE&amp;rdquo; hay tệ hơn là &amp;ldquo;A BAD CAFE&amp;rdquo;. Có người còn gợi ý 0x00C0FFEE, đọc số 0 thành chữ O, thành &amp;ldquo;OO COFFEE&amp;rdquo;, tức Object Oriented coffee.&lt;/p&gt;
&lt;p&gt;Chuỗi thảo luận cũng là bảo tàng nhỏ các giá trị hex nổi tiếng: Burroughs B6700 khởi tạo bộ nhớ chưa dùng bằng 0xBADBADBADBAD, các máy IBM cũ và AIX dùng 0xDEADBEEF cho vùng nhớ chưa khởi tạo, Macintosh đặt 0x50FFC001 ở địa chỉ 0 để truy cập con trỏ null là crash ngay, IRIX bắt đầu file arena bằng 0xdeadbabe, còn Solaris debug ghi 0xCAFEFEED vào khối nhớ đã giải phóng. Một người còn nhận ra NeXTSTEP từng dùng 0xCAFEBABE làm magic number cho fat binary, bên cạnh 0xfeedface. Câu trả lời thật đến từ Patrick Naughton, một trong những thành viên đầu tiên của nhóm Java: magic number được chọn từ rất lâu trước khi cái tên Java xuất hiện. Nhóm muốn một giá trị vui, độc đáo, dễ nhớ, và 0xcafebabe thắng ứng viên về nhì là 0xdeadbabe. Việc nó ngầm nhắc đến các barista ở quán Peet&amp;rsquo;s Coffee và báo trước cái tên Java chỉ là tình cờ.&lt;/p&gt;</description></item><item><title>Newsletter #137</title><link>https://miti99.com/post/2026/10/05/</link><pubDate>Mon, 05 Oct 2026 00:00:00 +0700</pubDate><guid>https://miti99.com/post/2026/10/05/</guid><description>&lt;p&gt;&lt;em&gt;Mời bạn thưởng thức Newsletter #137.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="how-to-name-things"&gt;&lt;a href="https://kolemannix.com/blog/how-to-name-things/" rel="noopener noreferrer"&gt;How to name things&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Koleman Nix cho rằng đặt tên là một trong những kỹ năng nền tảng nhất của kỹ sư phần mềm, vì ngôn ngữ rõ ràng gần như luôn đi kèm tư duy rõ ràng. Mỗi lần đặt tên biến, hàm, endpoint hay cột database, ta đang giao tiếp với đồng nghiệp và cả LLM. Model thường làm rất tốt trên codebase mới, gọn gàng, nhưng dần trở nên ì ạch khi tên gọi và thuật ngữ trong code bị &amp;ldquo;slop-fried&amp;rdquo;, nên muốn làm việc bền vững với model thì phải liên tục tinh giản thuật ngữ. DRY hiểu đúng là mỗi sự thật chỉ nằm ở một chỗ, nhưng hai giá trị bằng nhau chưa chắc là cùng một sự thật: độ dài tên tối đa và độ sâu hàng đợi cùng bằng 64 vẫn nên là hai hằng số riêng. Tác giả phản đối các quy tắc đặt tên cứng nhắc, vì đó là cách né tránh suy nghĩ. Điều cần nhất quán là quá trình đặt tên: hiểu bản chất của thứ đang đặt tên và cân nhắc tính đối xứng như &lt;code&gt;src&lt;/code&gt; và &lt;code&gt;dst&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Đặt tên tốt là sự thấu cảm trí tuệ, còn đặt tên theo nhu cầu nhất thời là ích kỷ. Cột &lt;code&gt;created_at&lt;/code&gt; hợp với dữ liệu nội bộ, nhưng một giấy khai sinh được upload năm 2023 lại cần &lt;code&gt;uploaded_at&lt;/code&gt; và &lt;code&gt;record_date&lt;/code&gt; riêng. Hàm &lt;code&gt;mergeTargetValuesIntoSourceTemplate&lt;/code&gt; thực ra chỉ là phép hợp map &lt;code&gt;++&lt;/code&gt;, nhưng cái tên theo nhu cầu đã che mất tính tổng quát. Một trường mơ hồ như &lt;code&gt;documentDate&lt;/code&gt; có thể bị hiểu theo hai nghĩa khác nhau ở hai nơi trong hệ thống và gây bug. Kết luận: hãy đặt tên theo bản chất chứ không theo nhu cầu.&lt;/p&gt;
&lt;h2 id="postgres-locks-explained"&gt;&lt;a href="https://postgreslocksexplained.com/" rel="noopener noreferrer"&gt;Postgres Locks Explained&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Postgres Locks Explained là một trang tài liệu tương tác về lock trong Postgres, &amp;ldquo;từ người mới tới chuyên gia&amp;rdquo;, do Brian (@TheOtherBrian1), một customer reliability engineer chuyên về quản trị và observability Postgres, xây dựng. Đây là bộ tài liệu tác giả ước gì đã có khi mới học về lock. Phần Concept giải thích bằng animation vì sao cần lock: nếu một câu &lt;code&gt;DELETE&lt;/code&gt; đang xóa dở một dòng mà &lt;code&gt;SELECT&lt;/code&gt; chen vào đọc, người đọc có thể nhận về dữ liệu bị xóa một nửa. Phần Locks by Example hướng dẫn tự chạy demo với hai session, chẳng hạn một &lt;code&gt;ALTER TABLE&lt;/code&gt; trong transaction chưa commit giữ lock &lt;code&gt;ACCESS EXCLUSIVE&lt;/code&gt; khiến cả &lt;code&gt;SELECT&lt;/code&gt; ở session khác bị treo, hay lock &lt;code&gt;FOR UPDATE&lt;/code&gt; giữ cho ràng buộc &lt;code&gt;UNIQUE&lt;/code&gt; được bảo toàn khi hai câu &lt;code&gt;UPDATE&lt;/code&gt; cùng ghi một giá trị. Lock Blocking Graph là công cụ tương tác cho biết thao tác SQL nào chặn thao tác nào, từ &lt;code&gt;SELECT&lt;/code&gt;, &lt;code&gt;CREATE INDEX CONCURRENTLY&lt;/code&gt; tới &lt;code&gt;VACUUM FULL&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Phần Troubleshooting đúc kết từ việc xem hàng nghìn database production mỗi năm: nghẽn do lock hiếm hơn query chưa tối ưu hay quản lý connection kém, nhưng chính vì ít gặp nên khi xảy ra, ít developer biết cách phát hiện và xử lý. Các vấn đề được phân tích gồm deadlock, lỗi hết bộ nhớ lock cần tăng &lt;code&gt;max_locks_per_transaction&lt;/code&gt;, hàng đợi lock, và trường hợp bảng bị chặn hoàn toàn. Tác giả khuyên bật &lt;code&gt;log_lock_waits&lt;/code&gt; để ghi lại các query chờ lock lâu hơn &lt;code&gt;deadlock_timeout&lt;/code&gt;. Phần Monitoring chỉ cách kết hợp &lt;code&gt;pg_locks&lt;/code&gt;, &lt;code&gt;pg_stat_activity&lt;/code&gt; và &lt;code&gt;pg_blocking_pids()&lt;/code&gt; để tìm query bị chặn và thủ phạm, đồng thời giới thiệu các truy vấn lock tree mà Datadog và pganalyze sử dụng.&lt;/p&gt;
&lt;h2 id="a-deep-and-fuzzy-dive-into-search"&gt;&lt;a href="https://serenedb.com/blog/fuzzy-search-deep-dive" rel="noopener noreferrer"&gt;A Deep (and Fuzzy) Dive Into Search&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Andrey Abramov, CTO của SereneDB, viết lại bài phân tích về fuzzy search từ sáu năm trước, khi team lần đầu ra mắt tính năng này trong engine IResearch, nay là lõi tìm kiếm của SereneDB. Fuzzy search là tên gọi chung cho các thuật toán so khớp gần đúng, mỗi thuật toán định nghĩa một thước đo độ tương đồng riêng. Thước đo đầu tiên là khoảng cách Levenshtein, số thao tác chèn, xóa, thay ký tự tối thiểu để biến chuỗi này thành chuỗi kia. Thuật toán quy hoạch động Wagner–Fischer tốn thời gian bậc hai cho mỗi cặp từ, quá chậm với từ điển lớn. Giải pháp là Levenshtein automaton của Schulz và Mihov (2002): với một từ và khoảng cách tối đa, dựng trong thời gian tuyến tính một automaton chỉ chấp nhận các chuỗi nằm trong khoảng cách đó, rồi giao với trie hoặc FST của từ điển để cắt bỏ các nhánh không thể khớp. Vì bảng chuyển trạng thái phình rất nhanh, SereneDB giới hạn khoảng cách tối đa là 4 cho Levenshtein và 3 cho Damerau–Levenshtein, biến thể coi việc đổi chỗ hai ký tự liền kề là một thao tác.&lt;/p&gt;
&lt;p&gt;Levenshtein thiên vị theo độ dài từ và không xử lý được từ thừa hay thiếu trong cụm từ, nên SereneDB bổ sung độ tương đồng n-gram dựa trên công trình của Kondrak, tính longest common subsequence trên n-gram thay vì ký tự. Ngưỡng chỉ chuẩn hóa theo query, nên query ngắn vẫn tìm được trường dài. Trong SQL, &lt;code&gt;ts_levenshtein&lt;/code&gt; tự chọn khoảng cách theo độ dài query và hỗ trợ sửa lỗi chính tả, còn &lt;code&gt;ts_ngram&lt;/code&gt; mặc định ngưỡng 0,7. Kết luận là không phương pháp nào thắng tuyệt đối, và nhiều hệ thống thực tế dùng cả hai.&lt;/p&gt;
&lt;h2 id="email-encryption"&gt;&lt;a href="https://computer.rip/2026-07-19-email-encryption.html" rel="noopener noreferrer"&gt;email encryption&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;J. B. Crawford, tác giả newsletter Computers Are Bad, kể lại lịch sử dài của việc mã hóa email. Email ra đời trong bối cảnh tin cậy lẫn nhau từ thập niên 60, nên đến nay vẫn gần như không có cơ chế bảo mật hay toàn vẹn dữ liệu sẵn có. Bộ giao thức OSI từng có X.400 nhưng thua email internet và chỉ còn sống trong những môi trường được quản lý tập trung như quân đội hay hàng không. MIME, mà tác giả gọi là &amp;ldquo;email 2.0&amp;rdquo;, giải quyết vấn đề nội dung 8-bit và mở đường cho attachment. PGP của Phil Zimmermann (1991), sau là GnuPG, phân phối khóa qua web of trust, nhưng mô hình này thất bại vì khó mở rộng, khó dùng. S/MIME (1996) dựa trên PKI với certificate do CA ký, phổ biến trong doanh nghiệp và chính phủ nhờ Microsoft Exchange và Active Directory, nhưng rất khó dùng ngoài một tổ chức.&lt;/p&gt;
&lt;p&gt;Ngày nay, phần lớn email thực ra được mã hóa khi truyền giữa các server, chủ yếu qua STARTTLS. Điểm yếu là STARTTLS mang tính cơ hội: kẻ tấn công đứng giữa có thể xóa lời đề nghị STARTTLS để ép kết nối về plaintext. Các biện pháp bổ sung gồm DANE dựa trên DNSSEC, MTA-STS công bố yêu cầu TLS qua HTTPS, và việc ghim yêu cầu mã hóa với các đối tác lớn. Kết luận của tác giả là mã hóa đầu cuối luôn chỉ là lớp vá gắn thêm cho một hệ thống không được thiết kế cho nó, PGP và S/MIME vẫn không phù hợp với đa số người dùng, và bảo mật thực tế đến từ mã hóa tầng truyền tải do nhà cung cấp cấu hình.&lt;/p&gt;
&lt;h2 id="a-shell-exclamation-mark-is-not-for-yelling-be-lazy"&gt;&lt;a href="https://refp.se/articles/your-shell-and-the-lazy-exclamation-mark" rel="noopener noreferrer"&gt;A shell exclamation mark is not for yelling. Be lazy.&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Filip Roséen nhắc lại một tính năng có từ cuối thập niên 70 nhưng ít người dùng: event designator trong bash, zsh, csh và tcsh. Lập trình viên được dạy áp dụng DRY ở khắp nơi, trừ trên command line, nơi ta vẫn bấm mũi tên lên liên tục để lặp lại lệnh cũ. Ví dụ quen thuộc nhất là &lt;code&gt;sudo !!&lt;/code&gt; để chạy lại lệnh trước với quyền root, hay &lt;code&gt;mkdir /var/mnt/cache&lt;/code&gt; rồi &lt;code&gt;cd !$&lt;/code&gt; để vào thư mục vừa tạo. Cú pháp luôn theo mẫu &lt;code&gt;![event][:word][:modifier]&lt;/code&gt;. Phần event chọn dòng lệnh: &lt;code&gt;!!&lt;/code&gt; là lệnh trước, &lt;code&gt;!-2&lt;/code&gt; là hai lệnh trước, &lt;code&gt;!ssh&lt;/code&gt; là lệnh gần nhất bắt đầu bằng &amp;ldquo;ssh&amp;rdquo;, &lt;code&gt;!?needle?&lt;/code&gt; là lệnh gần nhất chứa &amp;ldquo;needle&amp;rdquo;, còn &lt;code&gt;^ssh^scp^&lt;/code&gt; thay chữ trong lệnh trước. Phần word chọn đối số: &lt;code&gt;!:0&lt;/code&gt; là lệnh, &lt;code&gt;!:1&lt;/code&gt; là đối số đầu, &lt;code&gt;!$&lt;/code&gt; là đối số cuối, &lt;code&gt;!*&lt;/code&gt; là tất cả đối số. Phần modifier biến đổi đối số: &lt;code&gt;:h&lt;/code&gt; lấy thư mục như &lt;code&gt;dirname&lt;/code&gt;, &lt;code&gt;:t&lt;/code&gt; lấy tên file như &lt;code&gt;basename&lt;/code&gt;, &lt;code&gt;:r&lt;/code&gt; bỏ phần mở rộng, &lt;code&gt;:s/a/b/&lt;/code&gt; thay chuỗi, và &lt;code&gt;:p&lt;/code&gt; chỉ in ra mà không chạy.&lt;/p&gt;
&lt;p&gt;Với shell POSIX không hỗ trợ &lt;code&gt;!&lt;/code&gt;, lệnh &lt;code&gt;fc&lt;/code&gt; cho phép mở lệnh cũ trong editor để sửa, hoặc dùng &lt;code&gt;fc -e 'sed -i s/prod-01/test-01/g' -3 -1&lt;/code&gt; để chạy lại ba lệnh gần nhất trên một server khác. Tác giả đề xuất nhóm thiết yếu gồm &lt;code&gt;!!&lt;/code&gt;, &lt;code&gt;!needle&lt;/code&gt;, &lt;code&gt;!?needle?&lt;/code&gt;, &lt;code&gt;!$&lt;/code&gt;, &lt;code&gt;!:0&lt;/code&gt;, &lt;code&gt;!$:h&lt;/code&gt; và &lt;code&gt;!$:t&lt;/code&gt;. Ai chưa tự tin có thể bật &lt;code&gt;histverify&lt;/code&gt; trong bash hay &lt;code&gt;HIST_VERIFY&lt;/code&gt; trong zsh để xem lệnh được mở rộng trước khi chạy. Lịch sử shell lưu lệnh đã mở rộng chứ không lưu designator, và nếu &lt;code&gt;!&lt;/code&gt; gây phiền thì có thể tắt bằng &lt;code&gt;set +H&lt;/code&gt; hoặc đổi sang ký tự khác qua &lt;code&gt;histchars&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="a-coding-agent-is-six-functions-in-a-trenchcoat"&gt;&lt;a href="https://tidydesign.substack.com/p/a-coding-agent-is-six-functions-in" rel="noopener noreferrer"&gt;A coding agent is six functions in a trenchcoat&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Hadley Wickham giải thích điều gì làm nên một coding agent như Claude Code, Cursor hay Codex. Agent là một harness kèm các tool để đọc và ghi, còn coding agent là agent có những tool cho phép LLM khám phá và sửa codebase giống như con người. Hầu hết coding agent cung cấp sáu tool: đọc file, ghi file, sửa file, liệt kê file, tìm kiếm và chạy lệnh shell, kèm một system prompt dài về các thực hành tốt. Để chứng minh, tác giả viết một coding agent tí hon bằng R với thư viện ellmer, bắt đầu chỉ với ba tool &lt;code&gt;read_file&lt;/code&gt;, &lt;code&gt;write_file&lt;/code&gt; và &lt;code&gt;run_command&lt;/code&gt;, mỗi hàm chỉ vài dòng. Đăng ký ba hàm này kèm mô tả rõ ràng và một prompt cơ bản là đã có một agent hoàn chỉnh có thể tìm hàm parse ngày tháng rồi viết unit test cho nó. Tool shell là &amp;ldquo;thẻ thoát hiểm&amp;rdquo; vì làm được mọi thứ, từ &lt;code&gt;ls&lt;/code&gt;, &lt;code&gt;grep&lt;/code&gt; tới &lt;code&gt;git&lt;/code&gt;, nhưng cũng rất nguy hiểm.&lt;/p&gt;
&lt;p&gt;Phụ thuộc hết vào shell có nhiều nhược điểm, nhất là rất khó biết một lệnh shell an toàn hay không. Vì thế tác giả thêm tool liệt kê và tìm kiếm riêng, rồi làm chúng an toàn hơn bằng hàm &lt;code&gt;safe_path&lt;/code&gt; kiểm tra đường dẫn sau khi chuẩn hóa vẫn nằm trong thư mục dự án. &lt;code&gt;list.files()&lt;/code&gt; mặc định bỏ qua dotfile nên &lt;code&gt;.Renviron&lt;/code&gt; chứa API key cũng không bị lộ. Cuối cùng, tool &lt;code&gt;edit_file&lt;/code&gt; thay đúng một đoạn văn bản bằng đoạn khác, giúp model chỉ viết vài dòng thay đổi thay vì chép lại cả file 500 dòng, vừa rẻ vừa an toàn hơn vì một lần sửa sai sẽ báo lỗi rõ ràng thay vì âm thầm làm hỏng file.&lt;/p&gt;
&lt;h2 id="how-well-do-agents-use-test-and-verification-techniques"&gt;&lt;a href="https://danluu.com/agentic-testing/" rel="noopener noreferrer"&gt;How well do agents use test and verification techniques?&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Dan Luu thử nghiệm xem việc chỉ dặn coding agent dùng một kỹ thuật kiểm thử hay thư viện cụ thể có giúp code đúng hơn không. Bài toán là viết Zstd bằng Rust, dùng Codex với GPT-5.6 Sol ở mức effort medium và xhigh, qua 26 điều kiện prompt như mặc định, TDD, &amp;ldquo;Make no mistakes&amp;rdquo;, audit, fuzzing, differential testing, property-based testing, mutation testing và nhiều công cụ formal như TLA+, Kani, Verus, Lean 4. Tác giả còn thử bốn skill. Mỗi điều kiện chạy khoảng 160 lần, chấm theo tỷ lệ vượt toàn bộ hidden test so với chi phí.&lt;/p&gt;
&lt;p&gt;Kết quả là không điều kiện nào vượt trội rõ rệt, và điều kiện mặc định đạt điểm khá cao. Agent thường dùng kỹ thuật một cách hời hợt, viết test thông thường bên trong framework được nhắc tới. Với Verus, agent chứng minh các tính chất trừu tượng hay vô nghĩa thay vì code thật; 159/160 lần viết model TLA+ nhưng không lần nào làm thay đổi code Rust; 135/160 lần &amp;ldquo;differential testing&amp;rdquo; nhưng không lần nào có hai bản hiện thực độc lập. TDD cho kết quả kém, thường bỏ sót trường hợp jump table bốn stream. Fuzzing chủ yếu sinh byte ngẫu nhiên chạm cùng một nhánh từ chối, dù 10 lần dùng input có cấu trúc thì một nửa tìm ra bug thật. Skill Hegel không cải thiện độ đúng mà còn tăng chi phí 26–41% do dung lượng lớn. Agent nhận ra được vùng rủi ro khi được hỏi nhưng không test kỹ chúng. Kết luận: chỉ nêu tên kỹ thuật là không đủ, cần hướng dẫn cụ thể, dựng sẵn cấu trúc test hợp lý rồi điều chỉnh dần theo những gì agent làm ra.&lt;/p&gt;
&lt;h2 id="how-one-connection-kills-a-database"&gt;&lt;a href="https://planetscale.com/blog/debugging-live-database-connections" rel="noopener noreferrer"&gt;How one connection kills a database&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Simeon Griggs của PlanetScale mở đầu bằng một câu hỏi phỏng vấn: mọi query đều đang thất bại, hãy tìm ra nguyên nhân. Ví dụ được nhắc lại là một sự cố ở GitHub: một thay đổi schema MySQL cần lock độc quyền trên bảng, nhưng một session khác đã chạm vào bảng mà không commit, nên transaction đang mở này chặn thay đổi schema, rồi mọi query sau đó lại xếp hàng phía sau thay đổi schema đang chờ. Không có deadlock, không có timeout, và tình trạng kéo dài cho tới khi có người tìm và kill đúng connection ở đầu chuỗi. Tác giả tái hiện kịch bản này trên Postgres: một connection của ứng dụng chạy &lt;code&gt;BEGIN&lt;/code&gt; và &lt;code&gt;SELECT&lt;/code&gt; trên bảng &lt;code&gt;orders&lt;/code&gt; rồi gặp exception chưa được xử lý nên không bao giờ &lt;code&gt;COMMIT&lt;/code&gt; hay &lt;code&gt;ROLLBACK&lt;/code&gt;. Câu &lt;code&gt;SELECT&lt;/code&gt; chỉ giữ lock &lt;code&gt;ACCESS SHARE&lt;/code&gt;, nhưng migration &lt;code&gt;ALTER TABLE orders ADD COLUMN&lt;/code&gt; cần &lt;code&gt;ACCESS EXCLUSIVE&lt;/code&gt; nên phải chờ, và Postgres không cho query mới vượt lên trước migration đang chờ, khiến toàn bộ request vào bảng thất bại hoặc timeout.&lt;/p&gt;
&lt;p&gt;Tất cả bắt nguồn từ một exception chưa được xử lý, và có thể phòng tránh bằng &lt;code&gt;idle_in_transaction_session_timeout&lt;/code&gt;, vốn bị tắt mặc định. Debug thủ công khá vụng về, và vô dụng khi slot connection đã cạn. Vì vậy PlanetScale ra mắt tab Connections dùng một connection quản trị dự phòng, hiển thị process, trạng thái, thời gian và số query bị chặn, kèm ba mức kill: hủy query, kết thúc transaction, hoặc kết thúc connection. Trong ví dụ, &lt;code&gt;SELECT&lt;/code&gt; đã chạy xong nên hủy query không giải phóng được lock. CLI có lệnh &lt;code&gt;pscale branch connections top&lt;/code&gt; hiển thị cây blocker và cập nhật mỗi giây, cùng lệnh &lt;code&gt;show --format json&lt;/code&gt; cho script và agent.&lt;/p&gt;
&lt;h2 id="todos-considered-harmful"&gt;&lt;a href="https://frequal.com/java/TodosConsideredHarmful.html" rel="noopener noreferrer"&gt;TODOs Considered Harmful&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Andrew Oliver cảnh báo rằng trong thời đại coding agent hoạt động với ngày càng ít sự giám sát của con người, các comment TODO đang trở nên nguy hiểm. Chúng thường được viết vội, thiếu ngữ cảnh, và giờ là những quả bom hẹn giờ chờ một agent vô tình gặp phải rồi hành động theo. Trong năm qua, developer đã chuyển từ việc dùng agent như một StackOverflow theo yêu cầu sang giao cho chúng những nhiệm vụ lớn hơn, đôi khi với kỳ vọng agent tự tìm và sửa lỗi. TODO, vốn được coi là nợ kỹ thuật, trở thành mục tiêu dễ thấy cho các đợt dọn dẹp tự động: nếu developer đã để lại ghi chú về thứ cần sửa, sao không để agent làm luôn?&lt;/p&gt;
&lt;p&gt;Vấn đề là TODO hiếm khi được soi xét kỹ như code thực thi. Chúng có thể được viết nhanh, thiếu chi tiết, hoặc trở nên lỗi thời, thậm chí sai, sau những thay đổi code khác, và chắc chắn không bao giờ được kiểm chứng bằng unit test, mutation test hay integration test. Agent có rất ít khả năng đánh giá tính đúng đắn của một TODO, và vì code là một phần ngữ cảnh, chúng có thể coi TODO như sự thật rồi biến một TODO sai thành code thật, gây bug hoặc sự cố. Tác giả khẳng định đã tận mắt thấy điều này xảy ra trên môi trường production. Lời khuyên gồm: hạn chế TODO trong code mới và khi review, quét định kỳ các TODO đã cũ, sửa ngay nếu đủ quan trọng để ghi lại, ghi rõ lý do và ngữ cảnh nếu buộc phải giữ, hoặc chuyển chúng thành story có tiêu chí chấp nhận để theo dõi và ưu tiên.&lt;/p&gt;
&lt;h2 id="git-worktree-gotchas"&gt;&lt;a href="https://www.olafalders.com/2026/09/16/git-worktree-gotchas/" rel="noopener noreferrer"&gt;git worktree gotchas&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Olaf Alders nhận thấy git worktree, vốn từng bị coi là công cụ ngách, giờ đã phổ biến, nhưng vẫn có những cái bẫy cần biết. Mọi checkout git đều là một worktree: thư mục clone ban đầu là main working tree, còn các worktree tạo thêm là linked worktree. Tất cả các bẫy đều bắt nguồn từ một sự thật: trong linked worktree, &lt;code&gt;.git&lt;/code&gt; không phải thư mục mà là một file trỏ tới thư mục git riêng của worktree đó. Vì thế, đoạn code tự đi ngược lên tìm thư mục &lt;code&gt;.git&lt;/code&gt; để xác định gốc repository sẽ vượt qua đỉnh của linked worktree, và nếu linked worktree nằm trong main working tree, nó sẽ dừng ở main working tree, nơi không chứa thay đổi của bạn. Tác giả giữ linked worktree ở một đường dẫn hoàn toàn khác, và khuyên dùng &lt;code&gt;git rev-parse --show-toplevel&lt;/code&gt; để tìm gốc worktree hiện tại. Để tìm main working tree, chẳng hạn khi cần file &lt;code&gt;.env&lt;/code&gt; bị gitignore, dùng &lt;code&gt;git rev-parse --git-common-dir&lt;/code&gt; rồi lùi lên một cấp bằng &lt;code&gt;dirname&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Bẫy thứ ba liên quan tới git hook khi linked worktree chạy trong sandbox như nono, Docker, Landlock hay Seatbelt. Hook là tài nguyên dùng chung nằm trong &lt;code&gt;.git/hooks&lt;/code&gt; của main working tree, mà sandbox lại chặn truy cập vào đó. Giải pháp là đưa hook vào thư mục &lt;code&gt;.githooks&lt;/code&gt; được commit trong repository và cấu hình &lt;code&gt;git config core.hooksPath .githooks&lt;/code&gt; bằng đường dẫn tương đối. Vì git chuyển vào gốc của từng worktree trước khi chạy hook, đường dẫn này luôn trỏ vào bên trong worktree và sandbox, và chỉ cần cấu hình một lần cho mọi worktree. Thông điệp cần nhớ: &lt;code&gt;.git&lt;/code&gt; có thể là thư mục hoặc con trỏ, tùy bạn đang đứng ở đâu.&lt;/p&gt;</description></item><item><title>Newsletter #136</title><link>https://miti99.com/post/2026/10/04/</link><pubDate>Sun, 04 Oct 2026 00:00:00 +0700</pubDate><guid>https://miti99.com/post/2026/10/04/</guid><description>&lt;p&gt;&lt;em&gt;Mời bạn thưởng thức Newsletter #136.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="fast-and-hard-code"&gt;&lt;a href="https://lucumr.pocoo.org/2026/8/22/fast-hard-code/" rel="noopener noreferrer"&gt;Fast and Hard Code&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Theo Armin Ronacher, dù meme &amp;ldquo;lập trình đã được giải quyết&amp;rdquo; đúng tới đâu, thì việc làm quen với một ngôn ngữ không còn quan trọng, vì ma sát với con người không cản trở agent. Vì vậy, LLM khiến lựa chọn ngôn ngữ ít hệ trọng hơn nhiều so với trước. Không thích thì viết lại bằng ngôn ngữ khác, kể cả ngôn ngữ mình chưa từng biết. Hệ quả là người ta chọn ngôn ngữ dựa nhiều hơn vào hình ảnh và marketing của nó. Ông thấy nhiều người giờ ship code Rust dù trước đây sẽ không chọn nó. Ông cho rằng một phần nguyên nhân đến từ hai xu hướng gần đây: người ta nói nhiều hơn về phần mềm nhanh, và về việc LLM rất giỏi tối ưu code mà không làm thay đổi hành vi.&lt;/p&gt;
&lt;p&gt;Những người vốn ám ảnh với hiệu năng như Mitchell Hashimoto, Charlie Marsh, Jarred Sumner, Daniel Lemire đều cởi mở với việc agent viết code, và với công cụ kiểu autoresearch, người khác cũng chỉ cần giao việc tối ưu cho agent. Ngày càng nhiều dự án muốn nhanh và nhỏ chọn &amp;ldquo;ngôn ngữ khó&amp;rdquo;. Không chỉ Rust, ngay cả Zig cũng hưởng lợi, dù cộng đồng cốt lõi khá tiêu cực với AI: dịch vụ Artifacts của Cloudflare dùng engine Git protocol viết hoàn toàn bằng Zig biên dịch ra WebAssembly khoảng 100 KB, còn Vercel ra mắt fx, một coding agent bằng Zig. Người ta cũng làm được những thứ ấn tượng với DWARF, eBPF, network driver tự viết, crypto và phần cứng rất cũ, những lĩnh vực từng ngoài tầm với. Có thể thế giới sẽ nhiều &amp;ldquo;slop&amp;rdquo; hơn, nhưng cũng sẽ có thêm những lập trình viên muốn phần mềm nhanh và nhỏ.&lt;/p&gt;
&lt;h2 id="shipping-video-calling-on-facebook"&gt;&lt;a href="https://molochinations.substack.com/p/shipping-video-calling-on-facebook" rel="noopener noreferrer"&gt;Shipping Video Calling on Facebook&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Philip Su nhớ lại 16 năm trước, khi ông là kỹ sư thứ hai của văn phòng Facebook Seattle, sau mười hai năm làm ở Microsoft. Khi đó Facebook chỉ có khoảng 500 kỹ sư, tương lai còn chưa chắc chắn, và ông chọn làm kỹ sư toàn thời gian duy nhất cho tính năng video calling đầu tiên, sau này có thêm những người như Denise Noyes và Vijaye Raji. Team nhỏ tới mức review sản phẩm trực tiếp với Mark Zuckerberg. Thời đó WebRTC chưa phổ biến, nên Facebook ký hợp đồng với Skype để dùng binary phía client, người dùng phải cài trước khi gọi. Su thẳng thắn chê code Skype rất tệ: tỷ lệ thiết lập cuộc gọi thành công chỉ 82%, và khi ông hỏi số liệu của chính họ, câu trả lời là &amp;ldquo;chúng tôi không có số đó&amp;rdquo;, vì họ chỉ tin vào mức hài lòng do người dùng báo cáo. Ngược lại, việc dùng kênh dữ liệu của Messenger để thiết lập cuộc gọi cũng phơi bày tỷ lệ tin nhắn thất lạc của chính Facebook, buộc công ty cải thiện hạ tầng.&lt;/p&gt;
&lt;p&gt;Sáu tuần sau khi ra mắt là quãng thời gian làm việc cật lực nhất trong sự nghiệp của ông: hotfix nối tiếp hotfix, làm từ 8 giờ sáng tới nửa đêm mỗi ngày, ăn toàn đồ ăn vặt, vậy mà vẫn sụt hơn 10 pound. Một chi tiết thú vị là nhạc chuông cuộc gọi chính là các nốt F-A-C-E. Kết thúc giai đoạn đó, sản phẩm kết nối ổn định hơn, và đó cũng là lần đầu ông nói chuyện video với bố mẹ. Giờ đây, sau COVID và nhờ WebRTC, video calling đã thành chuyện thường ngày.&lt;/p&gt;
&lt;h2 id="ai-is-removing-the-middle-class-of-software-engineering"&gt;&lt;a href="https://blog.florianherrengt.com/ai-removing-middle-class-software-engineering.html" rel="noopener noreferrer"&gt;AI is removing the middle class of software engineering&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Florian Herrengt mở đầu bằng một kịch bản quen thuộc năm 2026: sáng thứ Hai, senior engineer mở máy và thấy bảy PR cần review, cái đầu tiên dài hơn 24 nghìn dòng kèm mô tả do AI viết. Khi một bug lạ xuất hiện lần thứ tư, người viết tính năng không giải thích được dữ liệu đến từ đâu, chỉ biết hỏi lại Claude. Theo tác giả, AI đã gỡ bỏ giới hạn tốc độ: dự án có văn hóa kỹ thuật yếu giờ thất bại nhanh hơn nhiều, và điều bi kịch là với con mắt thiếu kinh nghiệm, mọi thứ trông vẫn chạy. Sửa một quyết định tồi rất khó, chẳng hạn LLM thêm bảng vào database chỉ mất mười phút, nhưng gỡ ra khi đã có dữ liệu thật đòi hỏi kế hoạch migration cẩn thận.&lt;/p&gt;
&lt;p&gt;Kỹ sư tồi luôn tồn tại, nhưng khác biệt nằm ở tốc độ, như đâm xe ở 30 km/h so với 200 km/h. Code review, test và CI vẫn còn đó, chỉ là chúng được thiết kế cho thế giới mà tạo ra lượng thay đổi khổng lồ là điều không thể. Người tạo mười PR một ngày có thể chỉ đang đẩy công việc sang những người khó thay thế nhất trong team. Tác giả dự đoán AI sẽ kéo giãn khoảng cách lương: kỹ sư giỏi đáng giá hơn vì AI giúp họ đi nhanh hơn, còn người thiếu óc phán đoán để đánh giá đề xuất của LLM sẽ khó được tuyển. Ông dùng AI mỗi ngày, nhưng vấn đề nằm ở việc dùng AI thay cho hiểu biết thay vì để xây dựng hiểu biết.&lt;/p&gt;
&lt;h2 id="the-fastest-double-to-string-algorithm-youve-never-heard-of"&gt;&lt;a href="https://vitaut.net/posts/2026/yy-dtoa/" rel="noopener noreferrer"&gt;The fastest double-to-string algorithm you’ve never heard of&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Victor Zverovich, tác giả thư viện Żmij, giới thiệu một trong những thuật toán &lt;code&gt;dtoa&lt;/code&gt;, tức đổi số &lt;code&gt;double&lt;/code&gt; thành chuỗi, nhanh nhất hiện nay nhưng gần như vô danh. Nó nằm trong file &lt;code&gt;yy_double.c&lt;/code&gt; của thư viện JSON yyjson do ibireme viết, không có paper, không có tên riêng ngoài tên file, nên tác giả gọi tắt là &amp;ldquo;yy&amp;rdquo;. Bản thân Żmij ban đầu là một bản port tối ưu của Schubfach, sau đó đã chuyển phần lõi sang yy. Thuật toán thuộc họ Schubfach: tìm số thập phân ngắn nhất mà khi đọc ngược lại vẫn ra đúng giá trị float ban đầu, bằng cách chọn lưới thập phân thô nhất có điểm nằm trong khoảng làm tròn.&lt;/p&gt;
&lt;p&gt;Điểm khác biệt chính là yy chỉ cần một phép nhân với lũy thừa của 10 đã tính sẵn, trong khi Schubfach cổ điển cần hai hoặc ba phép nhân 192-bit. Với mỗi số, yy chọn số mũ thập phân bằng xấp xỉ fixed-point của log10(2), rồi xét bốn ứng viên, hai trên lưới mịn và hai trên lưới thô gồm các bội của 10. Ba predicate quyết định kết quả, ưu tiên ứng viên lưới thô ngắn hơn nếu round-trip được. Độ rộng nửa ulp được suy ra bằng phép dịch bit thay vì nhân thêm, còn trường hợp hòa được xử lý theo round-half-to-even bằng cách kiểm tra bit thấp của significand. Bài viết kèm một công cụ tương tác trên float 8-bit E4M3 để minh họa các trường hợp biên, chẳng hạn giá trị 192 được in thành &amp;ldquo;2e2&amp;rdquo; thay vì &amp;ldquo;19e1&amp;rdquo; nhờ một số hạng hiệu chỉnh. Tác giả kết luận rằng những thuật toán sống trong thư viện JSON hiếm khi nhận được sự ghi nhận như các thuật toán có paper.&lt;/p&gt;
&lt;h2 id="quake-shareware-a-cd-rom-just-a-little-too-full"&gt;&lt;a href="https://fabiensanglard.net/quake_shareware_cd/index.html" rel="noopener noreferrer"&gt;Quake Shareware, a CD-ROM just a little too full&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Fabien Sanglard kể lại câu chuyện chiếc CD shareware của Quake năm 1996. Giữa thập niên 90, CD-ROM 640 MiB có dung lượng vượt xa lượng asset mà nhà phát triển game làm ra được. Bản shareware Quake chỉ dùng 22 MiB, nên id Software nảy ra ý tưởng tận dụng phần còn lại: nhét thêm bản mã hóa của toàn bộ game trong danh mục id. Người chơi mua CD giá 9,95 USD, gọi số 1-800-ID-GAMES, đọc một mã CHALLENGE cho nhân viên, trả tiền rồi nhận về mã SERIAL để mở khóa. CHALLENGE thay đổi mỗi lần chạy để chống replay attack. Công cụ khóa do TestDrive Corp cung cấp, &amp;ldquo;biến tính&amp;rdquo; file EXE bằng cách ghi đè 32 KiB đầu và mã hóa phần header gốc thành file riêng. Thế nhưng chỉ 39 ngày sau khi CD lên kệ, nhóm hacker GNOMON tung ra QCRACK.EXE, tự sinh SERIAL từ CHALLENGE cho mọi game trên đĩa. Theo cuốn Masters of Doom, id bị kẹt lại với gần 150 nghìn đĩa CD trong kho.&lt;/p&gt;
&lt;p&gt;Lỗ hổng nằm ở chỗ SERIAL không chứa bí mật nào, nó chỉ là bằng chứng đã trả tiền. Chương trình mở khóa FLOW.EXE trên đĩa tự sinh được SERIAL từ CHALLENGE và chỉ so sánh với giá trị người dùng nhập, nên toàn bộ cơ chế bảo vệ dựa vào security by obscurity. Ông còn tìm ra nhiều sơ suất khác: Final DOOM không thể mở khóa qua tổng đài vì lỗi chính tả &amp;ldquo;Final&amp;rdquo; viết hoa trong file SKU.17, file mã hóa này lại có sẵn bản plain-text SKU.TXT ngay trên đĩa. Ông lý giải bằng &amp;ldquo;Fab&amp;rsquo;s Razor&amp;rdquo;: đừng quy cho sự ngu ngốc những gì có thể quy cho áp lực thời gian.&lt;/p&gt;
&lt;h2 id="how-instacart-built-a-modern-search-infrastructure-on-postgres"&gt;&lt;a href="https://tech.instacart.com/how-instacart-built-a-modern-search-infrastructure-on-postgres-c528fa601d54" rel="noopener noreferrer"&gt;How Instacart Built a Modern Search Infrastructure on Postgres&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Đội ngũ Instacart kể lại hành trình xây hạ tầng search cho catalog hàng tỷ sản phẩm từ hàng nghìn nhà bán lẻ, với hàng triệu lượt tìm kiếm và hàng tỷ lượt ghi mỗi ngày do giá, tồn kho, khuyến mãi thay đổi liên tục. Ban đầu full-text search chạy trên Elasticsearch, nhưng data model phi chuẩn hóa cùng lượng partial update khổng lồ khiến cluster quá tải, sửa dữ liệu sai có khi mất nhiều ngày. Họ chuyển sang các instance Postgres được shard, dữ liệu chuẩn hóa cao, dùng GIN index và phiên bản tùy biến của &lt;code&gt;ts_rank&lt;/code&gt;. Kết quả là khối lượng ghi giảm 10 lần, có thể index hàng trăm GB feature ML trong các bảng riêng rồi join bằng SQL, và search nhanh gấp đôi nhờ đẩy tính toán xuống gần dữ liệu trên NVMe thay vì kéo dữ liệu lên tầng ứng dụng.&lt;/p&gt;
&lt;p&gt;Năm 2021, họ thêm semantic search bằng một service ANN riêng dùng FAISS với embedding từ mô hình MiniLM, kết quả hai nguồn được trộn ở tầng ứng dụng. Kiến trúc này buộc phải overfetch rồi lọc sau, khó tối ưu cách kết hợp hai cơ chế retrieval, và tốn công giữ hai hệ thống đồng bộ. Giải pháp cuối cùng là hybrid search ngay trong Postgres với pgvector, dùng thông tin tồn kho thời gian thực làm pre-filter. Họ thay hàng trăm index HNSW theo từng retailer bằng các index gộp theo đặc điểm retailer, tăng số parallel worker và lưu embedding inline thay vì TOAST. pgvector chậm hơn FAISS chút ít với retailer lớn nhưng recall tốt hơn. A/B test cho thấy số lượt tìm kiếm không có kết quả giảm 6%, kéo theo doanh thu tăng đáng kể.&lt;/p&gt;
&lt;h2 id="curl-performance"&gt;&lt;a href="https://daniel.haxx.se/blog/2026/08/14/curl-performance-2/" rel="noopener noreferrer"&gt;curl performance&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Daniel Stenberg kể chuyện curl cuối cùng cũng có bộ test hiệu năng công khai tại curl.se/perf, sau nhiều năm ý tưởng này bị gác lại vì quá khó và không ai nhận làm. Thấy Grafana quá phức tạp, ông quyết định không để sự hoàn hảo cản trở điều tốt và bắt đầu bằng những gì mình đã biết. Hạ tầng cloud dùng chung không đủ ổn định cho test hiệu năng, nên ông chạy trên chính máy phát triển nhiều nhân của mình. Một shell script cập nhật source từ git, build và chạy test, chẳng hạn tốc độ tải file 100 GB từ localhost hay số lần và kích thước cấp phát bộ nhớ cho một HTTP download. Script thứ hai phân tích log và vẽ biểu đồ, script thứ ba chạy qua crontab mỗi hai mươi phút, rồi kết quả tự động được đẩy lên website. Chỉ vài giờ sau khi dữ liệu công khai, các developer đã gửi thay đổi để cải thiện số liệu, minh bạch thật sự giúp ích.&lt;/p&gt;
&lt;p&gt;Mỗi commit được build nhiều lần, biểu đồ lưu giá trị lớn nhất, trung vị và nhỏ nhất. Vì điều kiện test như phần cứng hay phiên bản thư viện sẽ thay đổi, số liệu chủ yếu hữu ích trong ngắn hạn để phát hiện regression vô tình. Để theo dõi thay đổi chậm trong dài hạn, ông đặt các &amp;ldquo;stake&amp;rdquo;, ngưỡng đặt tay cho từng test, kết hợp thuật toán LTTB để downsample biểu đồ dài hạn và Mann-Kendall cùng Sen&amp;rsquo;s Slope để nhận diện xu hướng. Một bài học sớm là sự đánh đổi: giảm số lần cấp phát bộ nhớ lại khiến một struct chính phình to hơn mong muốn. Hiện có 24 test, mỗi vòng khoảng sáu phút.&lt;/p&gt;
&lt;h2 id="compression-is-prediction"&gt;&lt;a href="https://ngrok.com/blog/compression-is-prediction" rel="noopener noreferrer"&gt;Compression is prediction&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Annie Sexton của ngrok dẫn dắt người đọc qua các khái niệm nén dữ liệu bằng một loạt demo tương tác, để đi tới kết luận: nén chính là dự đoán, và LLM là những bộ nén. Nén thật sự khai thác sự dư thừa, chẳng hạn run-length encoding thay chuỗi lặp bằng cặp ký tự và số lần. Một bộ nén gồm ba phần: transform chuẩn bị dữ liệu, model gán xác suất cho từng ký hiệu, và entropy coder biến xác suất thành dòng bit. Arithmetic coding thu hẹp dần khoảng từ 0 tới 1 theo xác suất của từng ký hiệu, nên cả thông điệp chỉ còn là một con số. Entropy, trung bình của −log₂ xác suất, là giới hạn lý thuyết cho nén lossless. Huffman coding, dùng trong gzip và Brotli, gán mã ngắn cho ký hiệu phổ biến nhưng phải làm tròn tới số bit nguyên.&lt;/p&gt;
&lt;p&gt;Xác suất trở nên chính xác hơn khi xét ngữ cảnh: chữ &amp;ldquo;U&amp;rdquo; hiếm trong tiếng Anh, nhưng gần như chắc chắn xuất hiện sau &amp;ldquo;Q&amp;rdquo;. LLM cũng dự đoán phân phối xác suất cho token kế tiếp, nên mã hóa token thật tốn −log₂ xác suất bit: đoán đúng và tự tin thì rẻ, tự tin mà sai thì đắt. Với một đoạn văn của Dickens, model bậc một cần 24% kích thước gốc, còn GPT-2 kết hợp arithmetic coding chỉ cần 10%. LLM không được dùng để nén hằng ngày vì cả hai đầu cần hàng gigabyte trọng số. Nén lossless gần như đã được giải quyết, bài toán còn lại là xây dựng bộ dự đoán tốt hơn, và huấn luyện LLM để giảm cross-entropy cũng chính là xây dựng một bộ nén.&lt;/p&gt;
&lt;h2 id="how-anthropic-builds-and-how-engineering-will-change-soon"&gt;&lt;a href="https://www.developing.dev/p/how-anthropic-builds-and-how-engineering" rel="noopener noreferrer"&gt;How Anthropic Builds And How Engineering Will Change Soon&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Ryan Peterman trò chuyện với Thariq Shihipar, kỹ sư trong team Claude Code, về cách Anthropic khai thác model cho công việc kỹ thuật. Lời khuyên đầu tiên khi onboard là coi Claude như một người cùng suy nghĩ, cung cấp đủ ngữ cảnh, và tìm cách lên một tầng trừu tượng cao hơn: xây hệ thống tạo ra hệ thống thay vì chỉ xây sản phẩm. Thariq cho rằng harness ngày càng quan trọng chứ không bớt đi: model càng giỏi, chạy càng lâu thì càng cần những phần mềm phức tạp như Auto mode, sandbox hay Artifacts để làm việc an toàn và trình bày kết quả. Ông dùng Fable để lập kế hoạch, brainstorm và viết spec chi tiết, rồi dùng Claude Opus 5 để hiện thực. Loop engineering, tức dựng hệ thống tự prompt Claude, giúp team theo dõi những issue trước đây không xuể, nếu skill và khâu kiểm chứng tốt.&lt;/p&gt;
&lt;p&gt;Về viết lách, quy tắc của ông là chỉ gửi output nếu sẵn sàng cho người khác xem prompt; báo cáo dữ liệu thì để Claude viết, còn các bài essay nội bộ đề xuất hướng đi gần như luôn do người viết. Về bảo trì code, naming và style bớt quan trọng, thay vào đó cần skill đơn giản hóa và lượng code kiểm thử nhiều hơn khoảng 100 lần trước đây. Để giảm sự cố, Anthropic hướng tới môi trường test lý tưởng có thể replay request và chaos test mọi thứ. Học code vẫn rất đáng: chỉ người hiểu kỹ thuật mới đánh giá được phần mềm tốt, và &amp;ldquo;coding đã được giải quyết&amp;rdquo; chỉ có nghĩa là lập trình không còn khó đoán như trước.&lt;/p&gt;
&lt;h2 id="a-faster-way-to-convert-a-timestamp--hour-min-sec"&gt;&lt;a href="https://www.benjoffe.com/fast-time-of-day" rel="noopener noreferrer"&gt;A faster way to convert a timestamp ➜ Hour, Min, Sec&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Tiếp nối bài viết về cách tính thứ trong tuần, Ben Joffe giải bài toán tách số giây trong ngày (0 tới 86399) thành giờ, phút, giây. Theo ông, đa số thư viện date làm việc này mất khoảng 16 chu kỳ CPU, trong khi có thể chỉ tốn độ trễ cỡ hai phép nhân. Cách truyền thống chia lấy giờ, lấy phần dư rồi chia tiếp, tạo thành một chuỗi phụ thuộc dài. Cách thứ hai, được V8, Boost và musl dùng, tính từng trường độc lập. Cassio Neri (2020) dùng phép nhân 64-bit và tái sử dụng nửa cao, nửa thấp của tích, được chọn làm mốc so sánh. Biến thể V1 của ông phá chuỗi phụ thuộc bằng cách tính &lt;code&gt;time / 60&lt;/code&gt; và &lt;code&gt;time / 3600&lt;/code&gt; song song, rồi suy ra giây và phút bằng phép trừ.&lt;/p&gt;
&lt;p&gt;V2 dùng số học fixed-point: nhân với 2³²/3600 + 1 để lấy giờ từ 32 bit cao, rồi nhân phần thấp với 60 để lấy phút; tương tự cho giây. V3 áp dụng mẹo modulo từ bài viết trước, tính giây bằng &lt;code&gt;(time + 4*tmin) % 64&lt;/code&gt;, vì 60 = 64 − 4. Ngoài ra còn có phiên bản cho mili giây, leap second, ARM NEON SIMD và biến thể SWAR của Consroe. Trên Apple M4 Pro, V1, V2, V3 lần lượt tốn 0,65, 0,64 và 0,60 lần thời gian của Neri; trên Ryzen 9 9950X3D với Clang, V2 và V3 chỉ còn 0,49 lần. Kỹ thuật đã được thư viện Java ITU và Elixir áp dụng. Lưu ý là các biến thể nhanh chỉ đúng trong một miền giá trị giới hạn và kết quả phụ thuộc nền tảng. Ông khuyên V2 là lựa chọn toàn diện nhất cho đa số thư viện.&lt;/p&gt;
&lt;h3 id="bonus"&gt;Bonus&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;Videos:&lt;/strong&gt;
&lt;a href="https://www.youtube.com/watch?v=WvSVSbGo0wI" rel="noopener noreferrer"&gt;Giải thích HTTP và HTTPS&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
 &lt;p&gt;Video của ByteByteGo so sánh HTTP và HTTPS, giải thích cách HTTPS dùng TLS để mã hóa dữ liệu và xác thực server, giúp kết nối giữa trình duyệt và website an toàn hơn.&lt;/p&gt;

&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Images:&lt;/strong&gt;
&lt;img src="https://substackcdn.com/image/fetch/$s_!6j7M!,w_1100,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2d0d1c6c-a864-4b20-b1fc-2d86c8ad2fab_2484x3002.png" alt="Top 12 Agent Skills You Should Know"&gt;
&lt;img src="https://substackcdn.com/image/fetch/$s_!m1c9!,w_1100,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8b053abe-404e-4bca-8e49-52a47392ab83_2484x3002.png" alt="9 Types of API Testing"&gt;
&lt;img src="https://substackcdn.com/image/fetch/$s_!4xag!,w_1100,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac1ed471-18f6-45dd-ad5f-02bc6d96d375_2484x3002.png" alt="Forward Proxy, Reverse Proxy, and API Gateway Explained"&gt;&lt;/p&gt;</description></item><item><title>Newsletter #135</title><link>https://miti99.com/post/2026/10/03/</link><pubDate>Sat, 03 Oct 2026 00:00:00 +0700</pubDate><guid>https://miti99.com/post/2026/10/03/</guid><description>&lt;p&gt;&lt;em&gt;Mời bạn thưởng thức Newsletter #135.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="git-at-any-scale"&gt;&lt;a href="https://cursor.com/blog/git-at-any-scale" rel="noopener noreferrer"&gt;Git at any scale&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Vicent Martí giải thích vì sao host Git tập trung ở quy mô lớn rất khó: packfile là file nhị phân lớn, phải nằm trên filesystem và bị đọc theo kiểu truy cập rải rác. Thời kỳ đầu, việc bỏ packfile để lưu object trong key-value store phân tán thì &lt;code&gt;git clone&lt;/code&gt; quá chậm, vì mỗi bước duyệt đồ thị commit/tree tốn một round trip. Distributed filesystem như NFS, GFS hay DRBD thì chậm, nhiều lỗi và khó vận hành. Spokes của GitHub nhân bản ở tầng ứng dụng, chạy three-phase commit cho reference transaction, nhưng tốc độ push bị giới hạn bởi replica chậm nhất. Mỗi repo cố định ba bản sao là thừa với repo nhỏ, thiếu với monorepo nặng CI, lại cần routing database lớn.&lt;/p&gt;
&lt;p&gt;Continuity của Cursor lấy write-ahead log (WAL) trên object storage tương thích S3 làm nguồn sự thật. Mỗi push được ghi vào WAL trước khi xác nhận, và chỉ hiển thị sau khi reference transaction thành công cùng với việc cập nhật WAL index, nhờ đó mọi push đều linearizable. Repo Git thường trên NVMe chỉ đóng vai trò cache, được dựng lại từ WAL khi thiếu, nên hệ thống stateless, không cần consensus. Node được chọn bằng rendezvous hashing, còn an toàn ghi dựa vào compare-and-swap của S3. Replica nhận thông báo qua UDP gossip rồi kiểm tra độ mới bằng conditional GET với ETag, thường dưới 10 ms. Chỉ primary chạy compaction, replica tải pack đã nén từ S3. Thử nghiệm trên monorepo của Cursor cho thấy read scale tuyến tính tới 100 replica, S3 Standard đạt khoảng 120 push/s, S3 Express One Zone vượt 300 push/s. Đây là nền móng cho Origin, nền tảng Git hosting cho workload của agent.&lt;/p&gt;
&lt;h2 id="regressive-jpegs"&gt;&lt;a href="https://maurycyz.com/projects/bad_jpeg/" rel="noopener noreferrer"&gt;Regressive JPEGs&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Maurycy khám phá một mẹo vui với progressive JPEG, định dạng cho phép lưu các thành phần tần số thấp trước để ảnh tải dở vẫn hiện ở độ phân giải thấp thay vì bị cắt ngang. Trong file, dữ liệu nén được chia thành nhiều &amp;ldquo;scan&amp;rdquo;, mỗi scan có header riêng cho biết kênh màu nào (Y, Cb, Cr), khoảng DCT bin nào và độ chính xác bao nhiêu. Scan đầu thường chứa bin DC của cả ba kênh, các scan sau bổ sung dần chi tiết. Vì mỗi scan tự khai báo phạm vi phổ của nó, tác giả nhận ra có thể tạo file JPEG mà scan sau ghi đè lên dữ liệu đã render. Chỉ cần nối nhiều ảnh cùng độ phân giải và bỏ các marker start-of-image, start-of-frame, end-of-image. Khi tải qua mạng chậm, file này sẽ chuyển qua lại giữa nhiều ảnh.&lt;/p&gt;
&lt;p&gt;Vấn đề là đa số decoder bỏ cuộc sau một số scan nhất định (có lẽ để tránh kiểu zip bomb), nên chỉ được khoảng chín frame. Để làm animation thật, cần giảm số scan mỗi frame. Progressive mode không cho phép một scan chứa cả dữ liệu AC lẫn DC, nên frame nhỏ nhất là một scan chỉ có DC, cho ảnh độ phân giải thấp hơn nhiều so với gốc. Với cách này, Chrome render được khoảng 90 frame trước khi dừng, Firefox còn kiên nhẫn hơn, và hiện tượng bóng mờ cũng biến mất vì không còn scan AC tinh chỉnh dữ liệu cũ. Kết quả là cả một video nằm trong một file ảnh. Ngoài việc rickroll hay troll, mẹo này không có ứng dụng thực tế, vì không thể thêm thông tin thời gian và tốc độ phát phụ thuộc hoàn toàn vào độ trễ mạng.&lt;/p&gt;
&lt;h2 id="a-quick-look-at-zero-knowledge-proofs"&gt;&lt;a href="https://bernsteinbear.com/blog/zkp/" rel="noopener noreferrer"&gt;A quick look at zero-knowledge proofs&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Max Bernstein và Chris Gregory tìm hiểu zero-knowledge proof (ZKP) qua một ví dụ không dính dáng gì tới tiền mã hóa: bài toán tô ba màu cho đồ thị theo paper của Goldreich, Micali, Wigderson. Trong ZKP có hai bên, prover tuyên bố biết lời giải của một bài toán khó, còn verifier cần bị thuyết phục mà không được biết lời giải đó. Tô ba màu là gán màu cho mỗi node sao cho hai node kề nhau không trùng màu, tìm thì chậm nhưng kiểm tra rất nhanh. Mỗi vòng của giao thức gồm bốn bước: prover hoán vị ngẫu nhiên ba màu rồi &amp;ldquo;khóa&amp;rdquo; màu từng node vào hộp và gửi các hộp đi; verifier chọn ngẫu nhiên một cạnh; prover gửi chìa khóa của hai hộp ở hai đầu cạnh; verifier mở hộp, kiểm tra màu hợp lệ và khác nhau.&lt;/p&gt;
&lt;p&gt;Trong code, &amp;ldquo;hộp khóa&amp;rdquo; là hash của màu kèm một nonce ngẫu nhiên, vì nếu chỉ hash màu thì các node cùng màu sẽ có hash giống nhau và lộ cấu trúc lời giải. Thực tế nên dùng &lt;code&gt;hashlib.sha256&lt;/code&gt; và &lt;code&gt;secrets&lt;/code&gt; thay cho &lt;code&gt;hash&lt;/code&gt; và &lt;code&gt;random&lt;/code&gt;. Một vòng chỉ bắt được prover gian lận với xác suất khoảng 1/m, với m là số cạnh, nên giao thức lặp m² vòng. Với đồ thị 1.000 cạnh, khoảng 4.600 vòng đưa xác suất gian lận lọt lưới xuống chừng 1%, còn 10.000 vòng chỉ còn 0,0045%. Nhóm tác giả còn dựng demo client-server. Cách làm tương tự áp dụng được cho Sudoku, và về lý thuyết cho mọi bài toán NP-complete nhờ phép quy dẫn, dù đồ thị có thể phình to rất nhanh, chẳng hạn bài toán phân tích thừa số một số có hai chữ số đã cần hàng nghìn node.&lt;/p&gt;
&lt;h2 id="the-pulse-we-need-to-talk-about-migrations-with-ai"&gt;&lt;a href="https://blog.pragmaticengineer.com/the-pulse-we-need-to-talk-about-migrations-with-ai/" rel="noopener noreferrer"&gt;The Pulse: We need to talk about migrations with AI&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Số The Pulse miễn phí này của The Pragmatic Engineer cho rằng công cụ AI coding đã biến những cuộc migration lớn, từng bị gác lại vì quá tốn kém, thành việc khả thi. Ví dụ nổi bật là Asana: theo case study của OpenAI, Codex giúp gỡ bỏ Enzyme, chuyển sang React Testing Library trong khoảng 1,5 tuần công kỹ sư, chi phí model và hạ tầng chừng 12 nghìn USD, trong khi kế hoạch trước đó ước tính 6 triệu USD và năm năm. Airbnb chuyển 3.500 file test Enzyme trong sáu tuần, so với ước tính thủ công 1,5 năm công. Uber chuyển 600 nghìn unit test từ JUnit 4 sang JUnit 5 trong bốn tháng với hai kỹ sư. Bun chuyển 530 nghìn dòng từ Zig sang Rust trong hai tuần, tốn 165 nghìn USD tiền API. Để so sánh, năm 2021 Sentry mất 1,5 năm với khoảng mười kỹ sư để chuyển 95 nghìn dòng JavaScript sang TypeScript.&lt;/p&gt;
&lt;p&gt;Các kỹ thuật lặp lại ở nhiều nơi gồm vòng retry tự động cho file thất bại, pipeline nhiều phase cho từng file, vòng kiểm chứng do kỹ sư thiết kế, ưu tiên file dễ hoặc thay đổi thường xuyên trước, và chuẩn bị codebase trước khi chạy LLM. Tuy vậy, con số 6 triệu USD có lẽ bị thổi phồng, vì ước tính thủ công bỏ qua việc tốc độ làm từng file tăng dần theo kinh nghiệm. Mọi ví dụ đều cần kỹ sư lập kế hoạch, thiết kế kiểm chứng và theo sát. Kết luận là bài toán chi phí và lợi ích của migration đã thay đổi, các team nên xem lại những dự án từng né tránh.&lt;/p&gt;
&lt;h2 id="the-accidental-application-runtime"&gt;&lt;a href="https://adventures.michaelfbryan.com/posts/accidental-application-runtime" rel="noopener noreferrer"&gt;The Accidental Application Runtime&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Michael F. Bryan kể về một service Go quản lý trang trại máy in 3D nhỏ. Ban đầu nó chỉ là dashboard: HTTP server khởi động một goroutine để poll máy in. Dần dần, việc nhận job từ backend, lưu job vào SQLite, lập lịch, chạy G-code và báo trạng thái ngược lên đều được thêm vào cùng đoạn setup đó, cho tới khi &lt;code&gt;NewServer&lt;/code&gt; khởi chạy năm goroutine chạy dài hạn. Khi cần graceful shutdown lúc nhận &lt;code&gt;SIGTERM&lt;/code&gt;, muốn biết goroutine nào đang chạy, dừng theo thứ tự nào, ai đóng channel thì phải đọc hết đoạn setup. Tác giả gọi đây là &amp;ldquo;accidental application runtime&amp;rdquo;: ứng dụng có kiến trúc thật gồm các thành phần đồng thời và luồng dữ liệu rõ ràng, nhưng không thứ gì trong code thể hiện nó.&lt;/p&gt;
&lt;p&gt;Lấy cảm hứng từ autopilot drone PX4, tác giả viết thư viện &lt;code&gt;backplane&lt;/code&gt;. Mỗi module là một hàm Go thường &lt;code&gt;func(ctx, deps...) error&lt;/code&gt;, trong đó tham số &lt;code&gt;chan&amp;lt;- T&lt;/code&gt; nghĩa là publish topic kiểu &lt;code&gt;T&lt;/code&gt;, &lt;code&gt;&amp;lt;-chan T&lt;/code&gt; là subscribe, &lt;code&gt;*backplane.Latest[T]&lt;/code&gt; là theo dõi giá trị mới nhất. Topic định danh bằng kiểu Go, không dùng chuỗi. &lt;code&gt;New&lt;/code&gt; kiểm tra wiring trước khi chạy, &lt;code&gt;Run&lt;/code&gt; giám sát các module theo kiểu &lt;code&gt;errgroup&lt;/code&gt;. Delivery diễn ra trong process, có backpressure, không durable. Service được dựng lại thành sáu module, với quy tắc then chốt commit-then-notify: scheduler ghi nhận job vào SQLite trước khi publish, để message chỉ đánh thức module chứ không phải nguồn sự thật. Sơ đồ Mermaid được sinh từ chính các khai báo này. Bài học rút ra là tách stream sự kiện khỏi lời gọi request/response, cẩn thận deadlock khi đồ thị có chu trình, và thư viện như vậy chỉ đáng dùng khi cùng một cơ chế lặp lại khắp ứng dụng.&lt;/p&gt;
&lt;h2 id="generic-methods-in-go-127"&gt;&lt;a href="https://dominik.info/blog/go-generic-methods" rel="noopener noreferrer"&gt;Generic Methods in Go 1.27&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Khi Go 1.18 ra mắt generics năm 2022, type parameter chỉ áp dụng cho function và struct, còn method thì không. Go 1.27 gỡ bỏ giới hạn này: method giờ có thể khai báo type parameter của riêng nó mà không cần thêm vào struct nhận. Bài viết minh họa bằng một node của đồ thị chứa giá trị generic &lt;code&gt;Node[T]&lt;/code&gt;. Muốn thêm method &lt;code&gt;Map&lt;/code&gt; biến node kiểu &lt;code&gt;T&lt;/code&gt; thành node kiểu &lt;code&gt;U&lt;/code&gt;, trước đây phải nhét &lt;code&gt;U&lt;/code&gt; vào chính struct thành &lt;code&gt;Node[T, U]&lt;/code&gt;. Đây là thiết kế tệ, vì &lt;code&gt;U&lt;/code&gt; chỉ liên quan tới &lt;code&gt;Map&lt;/code&gt;, vậy mà mọi method khác vẫn phải mang nó trong khai báo receiver. Cách né duy nhất là viết &lt;code&gt;Map&lt;/code&gt; thành function cấp package, khá rườm rà. Từ Go 1.27, có thể viết &lt;code&gt;func (n *Node[T]) Map[U any]() Node[U]&lt;/code&gt;, để &lt;code&gt;U&lt;/code&gt; chỉ thuộc về &lt;code&gt;Map&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Lý do tính năng này mất nhiều năm nằm ở cách Go hiện thực generics. Compiler chủ yếu dùng monomorphization, tức tạo một bản sao của struct hay function generic cho mỗi kiểu cụ thể được dùng. Trong khi đó, hệ thống interface của Go hoạt động ở runtime qua dynamic dispatch. Hai mô hình này xung đột: nếu interface &lt;code&gt;Mapper&lt;/code&gt; khai báo &lt;code&gt;Map[U any]() Node[U]&lt;/code&gt;, compiler phải xác định được một kiểu có implement interface hay không, nhưng với mỗi &lt;code&gt;T&lt;/code&gt; có thể có vô số cách khởi tạo &lt;code&gt;U&lt;/code&gt;. Reflection còn có thể gọi method ở runtime mà compiler không nhìn thấy khi quyết định cần sinh những bản khởi tạo nào. Cuối cùng, Go team chọn không cho phép generic method trong interface: kiểu cụ thể có thể có generic method, interface thì không, và generic method cũng không thỏa mãn một method interface tương ứng.&lt;/p&gt;
&lt;h2 id="your-executable-is-a-sqlite-database"&gt;&lt;a href="https://fzakaria.com/2026/08/23/your-executable-is-a-sqlite-database" rel="noopener noreferrer"&gt;Your executable is a SQLite database&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Farid Zakaria, tác giả sqlelf, đi xa hơn một bước: thay hẳn ELF bằng SQLite làm định dạng file thực thi. Prototype tên SELF (Structured Executable &amp;amp; Linkable Format) là một file SQLite thật sự, có thể &lt;code&gt;chmod +x&lt;/code&gt; rồi chạy. Lập luận cốt lõi là ELF vốn đã là một database &amp;ldquo;không chịu thừa nhận&amp;rdquo;: string table, hash section, section header, symbol versioning đều tự hiện thực lại string interning, index, foreign key và record. Schema của SELF gồm bảng &lt;code&gt;self_meta&lt;/code&gt; chứa ELF header, bảng &lt;code&gt;segments&lt;/code&gt; lưu mỗi program header một dòng với dữ liệu dạng BLOB, và bảng &lt;code&gt;symbols&lt;/code&gt; có index B-tree thay cho &lt;code&gt;.dynstr&lt;/code&gt;, &lt;code&gt;.gnu.hash&lt;/code&gt;. Một đăng ký binfmt_misc giúp kernel chuyển file cho &lt;code&gt;self-exec&lt;/code&gt;, một trình nạp C nhỏ map segment, relocate rồi nhảy tới entry point.&lt;/p&gt;
&lt;p&gt;Dynamic linking có hai cách: giữ &lt;code&gt;ld.so&lt;/code&gt; của glibc và dùng rtld-audit để một câu SQL quyết định thư viện nào được nạp, hoặc dùng &lt;code&gt;self-ld&lt;/code&gt;, linker thử nghiệm làm mọi việc qua SQL. Lệnh &lt;code&gt;self closure&lt;/code&gt; đóng gói binary cùng toàn bộ dependency vào một database, khiến &lt;code&gt;ldd&lt;/code&gt; chỉ còn là một phép JOIN. Áp dụng cho 723 chương trình trên một máy cho ra một database 611,9 MiB, nhỏ hơn 644,4 MiB của ELF nhờ khử trùng lặp thư viện và symbol, dù riêng từng file SELF lớn hơn 2,2 tới 3,6 lần. Lợi ích gồm query binary bằng SQL, strip hay patch như một transaction, và &lt;code&gt;LD_PRELOAD&lt;/code&gt; nguyên tử trên toàn hệ thống. Hạn chế là segment phải copy ra thay vì mmap nên các process không chia sẻ trang text, khởi động chậm hơn với chương trình có nhiều object, và chưa có compiler hay linker sinh SELF trực tiếp.&lt;/p&gt;
&lt;h2 id="what-happens-when-a-gpu-reads-memory"&gt;&lt;a href="https://blog.doubleword.ai/what-happens-when-a-gpu-reads-memory" rel="noopener noreferrer"&gt;What happens when a GPU reads memory&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Fergus Finn, founder của Doubleword, lần theo một lệnh load toàn cục duy nhất (&lt;code&gt;LDG.E&lt;/code&gt;) xuyên qua phần cứng RTX 4090. Vì NVIDIA công bố rất ít về con đường này, phần lớn chi tiết đến từ thí nghiệm đo thời gian và hardware counter. Bộ coalescer gộp 32 request bốn byte của một warp thành bốn sector 32 byte, đúng một cache line 128 byte. Request trượt L1, rồi qua TLB 16 entry mỗi SM để dịch địa chỉ trên trang 2 MiB, vì L2 được index theo địa chỉ vật lý. Crossbar đưa request tới một trong 36 slice L2, được chọn bằng một hàm phức tạp mà tác giả phải dò ra. Tiếp đó là 12 memory controller, mỗi cái điều khiển một chip GDDR6X: controller mở một row 1 KiB bằng lệnh activate tốn kém, rồi đọc bốn sector với chi phí rẻ.&lt;/p&gt;
&lt;p&gt;Các con số đáng chú ý: L1 hit khoảng 15,4 ns, L2 hit khoảng 127 ns, còn một lần trượt mọi tầng xuống DRAM mất khoảng 255 ns, tương đương 660 chu kỳ mà warp phải đứng chờ. Activate một row tốn gấp khoảng 15 lần một lần đọc trong row đang mở. Load dồn vào một slice L2 chỉ đạt 1.957 triệu load/s, còn trải đều 36 slice đạt khoảng 68 nghìn triệu, gấp 34,8 lần. GPU che giấu độ trễ bằng song song khổng lồ: trong khi một warp chờ, phần còn lại của chip vẫn tiếp tục phát lệnh load. Vì thế hiệu năng phụ thuộc vào mẫu truy cập, cần coalesced và trải đều địa chỉ qua slice và row. Tác giả lưu ý các hàm ánh xạ L1, L2 tìm được chỉ là mô hình hợp lý, chưa phải đặc tả chính thức.&lt;/p&gt;
&lt;h2 id="a-faster-way-to-calculate-the-day-of-the-week"&gt;&lt;a href="https://www.benjoffe.com/fast-day-of-week" rel="noopener noreferrer"&gt;A faster way to calculate the day-of-the-week&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Ben Joffe giải bài toán nghe có vẻ tầm thường: đổi số ngày tính từ Unix epoch (một số nguyên 32-bit có dấu, 1970-01-01 là thứ Năm) thành thứ trong tuần, sao cho vừa nhanh vừa đúng trên toàn bộ miền giá trị. Thuật toán của Neri (2024) được chọn làm mốc so sánh, nhưng cần thêm bước hiệu chỉnh vì 7 là ước số &amp;ldquo;khó chịu&amp;rdquo;. Ý tưởng chính của tác giả là tận dụng việc 7 = 2³ − 1, một số Mersenne, nên N mod 7 bằng floor(N·8/7) mod 8. Phép nhân 8/7 được xấp xỉ bằng một phép nhân với hằng số magic rồi dịch phải, còn mod 8 có sẵn nhờ chỉ giữ ba bit cao. Phiên bản giới hạn chỉ cần &lt;code&gt;(u32(rd) * M + Z) &amp;gt;&amp;gt; 29&lt;/code&gt;, đúng trong khoảng ±242 nghìn năm, giúp thư viện Rust &lt;code&gt;jiff&lt;/code&gt; nhanh hơn khoảng 40% ở một số hàm.&lt;/p&gt;
&lt;p&gt;Để phủ toàn bộ miền 32-bit, tác giả đưa ra nhiều biến thể: nhân 64-bit (được WebKit dùng), thêm số hạng hiệu chỉnh bằng phép dịch có thể tính song song với phép nhân, dùng hai phép nhân để tăng throughput, hoặc chỉ ba lệnh x86 nhờ &lt;code&gt;LEA&lt;/code&gt;. Định dạng ISO chỉ cần đổi hằng số. Kỹ thuật còn tổng quát hóa cho các ước số dạng 2^N − 2^K, như &lt;code&gt;x % 24&lt;/code&gt; hay &lt;code&gt;x % 60&lt;/code&gt; khi tính giờ, phút. Benchmark trên Apple M4 Pro cho thấy các biến thể mới chỉ tốn khoảng 0,17 tới 0,39 lần thời gian của Neri, trên Ryzen 9 9950X3D là 0,27 tới 0,59 lần. Kỹ thuật đã có mặt trong WebKit, Elixir, Jiff và thư viện Zig Zeit.&lt;/p&gt;
&lt;h2 id="dark-mode-toggles-two-states-are-enough"&gt;&lt;a href="https://lea.verou.me/blog/2026/dark-mode-toggles/" rel="noopener noreferrer"&gt;Dark mode toggles: two states are enough&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Lea Verou lập luận rằng nút chuyển dark mode trên header website chỉ nên có hai trạng thái hiển thị, dù mô hình dữ liệu bên dưới có ba: System, Light và Dark. Theo bà, trạng thái thứ ba giải quyết một mục tiêu &amp;ldquo;tưởng tượng&amp;rdquo;. Người dùng tìm tới nút chuyển theme khi trang khó đọc, chứ không phải để ghim trước một theme vốn đã ổn. Đây là UI thiết kế theo data model thay vì mục tiêu người dùng. Toggle ba trạng thái rất phổ biến, từ Tailwind tới Docusaurus, nhưng chúng thêm rối mắt, thêm thao tác và tăng tải nhận thức. Ngược lại, một toggle hai trạng thái làm kém còn tệ hơn, vì có thể khóa người dùng vào lựa chọn ghi đè mà không có đường quay về System. Tác giả cũng thừa nhận chỉ dùng system preference mà không có toggle là lựa chọn hoàn toàn hợp lệ.&lt;/p&gt;
&lt;p&gt;Thiết kế được đề xuất là một toggle duy nhất nhưng biểu diễn đủ ba trạng thái. Ở chế độ mặc định, nút hiển thị theme đang áp dụng và không lưu gì. Lần bấm đầu chuyển sang theme ngược lại và lưu giá trị đó vào &lt;code&gt;localStorage&lt;/code&gt;. Lần bấm thứ hai quay về System và xóa giá trị đã lưu, tránh biến thay đổi tạm thời thành ghim vĩnh viễn. Override chỉ được xét lại khi người dùng tương tác, nên người dùng có OS tự đổi theme theo giờ vẫn ghim được theme mong muốn. Control ba trạng thái vẫn hợp lý trong trang settings hoặc với site có bảng màu khác nhau tùy theme OS. Bài học chung vượt ra ngoài dark mode: không phải trạng thái nào của state machine cũng cần UI hiển thị.&lt;/p&gt;</description></item><item><title>Newsletter #134</title><link>https://miti99.com/post/2026/10/02/</link><pubDate>Fri, 02 Oct 2026 00:00:00 +0700</pubDate><guid>https://miti99.com/post/2026/10/02/</guid><description>&lt;p&gt;&lt;em&gt;Mời bạn thưởng thức Newsletter #134.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="the-startup"&gt;&lt;a href="https://hatchet.run/blog/postgres-survival-guide" rel="noopener noreferrer"&gt;The startup&amp;rsquo;s Postgres survival guide&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Alexander Belanger, CEO của Hatchet, đúc kết khoảng hai năm vận hành Postgres trên production thành một cẩm nang thực dụng, dễ tra cứu hơn manual chính thức khi đang xử lý sự cố. Về schema, nên dùng identity column hoặc UUID làm primary key, luôn dùng &lt;code&gt;timestamptz&lt;/code&gt;, cân nhắc &lt;code&gt;jsonb&lt;/code&gt; khi normal form làm chậm query. Với truy vấn đọc, join nên đi qua primary key, cột trong &lt;code&gt;ON&lt;/code&gt; cũng cần index như &lt;code&gt;WHERE&lt;/code&gt;, còn compound index nên đặt cột &lt;code&gt;ORDER BY&lt;/code&gt; ở cuối và đúng chiều sắp xếp. Với truy vấn ghi, giữ transaction ngắn, không gọi service ngoài bên trong transaction, luôn dùng &lt;code&gt;CREATE INDEX CONCURRENTLY&lt;/code&gt; cho bảng lớn, migration nên additive và dùng &lt;code&gt;NOT VALID&lt;/code&gt; khi thêm check constraint. Connection rất đắt nên cần pool, ưu tiên PgBouncer hoặc &lt;code&gt;pgxpool&lt;/code&gt;. Query planner là một abstraction &amp;ldquo;rò rỉ&amp;rdquo;, phụ thuộc thống kê trong &lt;code&gt;pg_stats&lt;/code&gt;, nên debug bằng &lt;code&gt;EXPLAIN (ANALYZE, BUFFERS)&lt;/code&gt; rồi trực quan hóa trên explain.dalibo.com, và tránh micro-optimize quá đà.&lt;/p&gt;
&lt;p&gt;Phần nâng cao nói về batching ghi dữ liệu (ví dụ &lt;code&gt;SendBatch&lt;/code&gt; của &lt;code&gt;pgx&lt;/code&gt;) giúp tăng throughput khoảng 10 lần, cùng việc theo dõi autovacuum qua &lt;code&gt;pg_stat_activity&lt;/code&gt; và cảnh giác với transaction ID wraparound. Table bloat nên xử lý bằng &lt;code&gt;pg_repack&lt;/code&gt; thay vì &lt;code&gt;VACUUM FULL&lt;/code&gt;, còn index bloat dùng &lt;code&gt;REINDEX INDEX CONCURRENTLY&lt;/code&gt;. Hatchet dùng &lt;code&gt;FOR UPDATE SKIP LOCKED&lt;/code&gt; để làm job queue và quản lý lease giữa các instance. Partitioning giúp autovacuum từng phần độc lập và xóa dữ liệu cũ gần như tức thì, rất hợp với dữ liệu time-series. Khi migrate bảng khổng lồ, nên dùng trigger để chép write mới, backfill theo batch bên ngoài transaction và dựa vào unique constraint để tránh trùng lặp.&lt;/p&gt;
&lt;h2 id="what-is-orasort-understanding-oracles-sorting-aglorithm-in-simple-terms"&gt;&lt;a href="https://deepsystemstuff.com/how-oracles-secret-column-sorting-technique-became-public-after-its-patent-expired-making-sorting-5x-faster/" rel="noopener noreferrer"&gt;What is Orasort? understanding Oracle’s Sorting Aglorithm In Simple Terms&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Nadeem Shaikh giới thiệu Orasort, kỹ thuật sắp xếp do Oracle đăng ký bằng sáng chế (đứng tên Mark Callaghan). Theo bài viết, bằng sáng chế này đã hết hạn 20 năm vào 2024 nên kỹ thuật giờ thuộc public domain, và có thể giúp database sort nhanh hơn tới 5 lần. Cách so sánh chuỗi truyền thống đi từng ký tự một, nên các giá trị dài có chung tiền tố, như email cùng domain, tốn rất nhiều phép so sánh. Orasort thì lấy 8 byte của mỗi giá trị, chuyển thành một số nguyên 64-bit vừa khít thanh ghi CPU rồi so sánh một lần. Nếu 8 byte đó đã phân định được thứ tự thì dừng, còn nếu bằng nhau thì mới xét tiếp 8 byte kế.&lt;/p&gt;
&lt;p&gt;Quy trình sort còn kèm vài kỹ thuật khác. Database chỉ nạp key và record ID vào buffer trên RAM thay vì cả dòng dữ liệu. Khi dữ liệu quá lớn so với RAM, ví dụ hàng triệu dòng, nó được chia thành nhiều phần lưu trên đĩa và lần lượt nạp vào để xử lý. Kết quả đã sắp xếp được ghi xuống đĩa bất đồng bộ trong lúc việc sort vẫn tiếp tục, rồi cuối cùng mới merge các phần trên đĩa. Tác giả cho rằng các database mã nguồn mở như MySQL, PostgreSQL đã tích hợp hoặc đang thử nghiệm ý tưởng này, và cloud provider có thể giảm chi phí vận hành nhờ tiết kiệm CPU cycle. Tuy vậy, bài viết không dẫn nguồn cho con số 5 lần hay các thông tin về bằng sáng chế và mức độ áp dụng, nên bạn nên xem đây là phần giải thích trực quan về ý tưởng hơn là tài liệu tham khảo chính xác.&lt;/p&gt;
&lt;h2 id="fornax"&gt;&lt;a href="https://docs.pixeldrain.com/posts/2024-03-07_network_optimizations/" rel="noopener noreferrer"&gt;Fornax&amp;rsquo;s Guide To Ridiculously Fast Ethernet&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Fornax, người vận hành dịch vụ chia sẻ file pixeldrain, thuê một server 100 GbE với kỳ vọng mọi thứ chạy ngay, nhưng máy crash khi mới phục vụ khoảng 20 Gbit/s. Sau hơn một năm đọc manual NIC, đọc mã nguồn kernel, profiling và cả patch kernel, tác giả đã đạt được tốc độ 100 Gbit/s và tổng hợp lại toàn bộ cấu hình. Về sysctl, dùng BBR kết hợp qdisc &lt;code&gt;fq&lt;/code&gt; vì BBR không giảm throughput đột ngột khi mất gói. Bật &lt;code&gt;tcp_shrink_window&lt;/code&gt; (patch của Cloudflare, có từ Linux 6.5) giúp giảm khoảng 80% bộ nhớ buffer TCP, vốn từng làm server 1 TB RAM bị hết bộ nhớ lúc cao điểm. Buffer mỗi kết nối được nâng tối đa lên 1 GiB theo bandwidth-delay product, còn &lt;code&gt;tcp_mem&lt;/code&gt; được đặt ở mức 40–60% RAM thay vì khoảng 5% mặc định.&lt;/p&gt;
&lt;p&gt;Ở tầng phần cứng, card ConnectX-5/6 chạy tốt nhất, driver có sẵn trong kernel là đủ dùng. Với ethtool, nên khớp số channel với số core, đặt ring buffer ở mức tối đa và tắt adaptive interrupt coalescing để dùng giá trị cố định 100 µs. Trong BIOS, nên đặt NUMA NPS1 và tắt SMT trên máy từ 128 core trở lên. Thay đổi có tác động lớn nhất là tắt IOMMU, giúp NIC ổn định dưới tải. Tác giả cũng bỏ reverse proxy như nginx hay Caddy vì mỗi request phải đi qua ranh giới kernel nhiều lần, thay vào đó dùng thẳng HTTP server của Go kèm &lt;code&gt;SO_REUSEPORT&lt;/code&gt; để restart không downtime. HTTP/2 được ưu tiên, còn HTTP/3 bị loại vì throughput chưa bằng một nửa. Cuối cùng là chuyển sang Debian tối giản và dùng kernel mới nhất, vì các bản 6.8 và 6.19 cải thiện đáng kể hiệu năng TCP.&lt;/p&gt;
&lt;h2 id="parity-before-cutover"&gt;&lt;a href="https://alexocallaghan.com/parity-before-cutover" rel="noopener noreferrer"&gt;Parity before cutover&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Alex O&amp;rsquo;Callaghan kể về một service mà nhiều team khác phụ thuộc vào, tồn tại cùng lúc hai phiên bản: bản production chạy Python 2 trên server on-prem và bản Python 3 hiện đại hóa nằm trên một branch riêng, chờ migrate lên cloud để thay thế. Cách nâng cấp kiểu big-bang này, tức viết lại hoặc giữ branch dài hạn rồi cutover một lần ở cuối, gom hết rủi ro và đẩy phản hồi về muộn. Công việc bị nhân đôi vì mọi bản sửa gấp phải làm trên bản cũ rồi port sang bản mới, nếu không sẽ bị quên. Những khác biệt về hành vi giữa hai bản chỉ lộ ra sau cutover, khi rất khó truy nguyên.&lt;/p&gt;
&lt;p&gt;Thay vì nhắm thẳng tới đích là bản mới trên cloud, tác giả đề xuất &amp;ldquo;lùi một bước để tiến&amp;rdquo;: chạy bản hiện đại trên hạ tầng cũ trước. Vì OS trên server cũ chỉ hỗ trợ tối đa Python 3.6, team hạ phiên bản service từ 3.7 xuống 3.6 thay vì dùng workaround, rồi chọn cấu hình theo môi trường lúc runtime để một codebase deploy được cả lên server cũ lẫn Kubernetes. Từ đó, mỗi lần release đều đi vào cả hai môi trường, khác biệt lộ ra ngay theo từng bước nhỏ trong khi hạ tầng cũ vẫn là phương án dự phòng. Khi code đã giống hệt nhau, cutover lên cloud chỉ còn là thay đổi hạ tầng, mỗi lần thay đổi một biến. Bài học rút ra: nếu một thay đổi đã mở nhiều tháng hoặc đang chờ team khác, có lẽ nó đang ôm quá nhiều việc cùng lúc, nên hãy tìm một trạng thái trung gian release được ngay, dù trông giống như đi lùi.&lt;/p&gt;
&lt;h2 id="the-new-go-json-api-twice-as-fast-or-15x-slower"&gt;&lt;a href="https://lemire.me/blog/2026/08/29/the-new-go-json-api-twice-as-fast-or-1-5x-slower/" rel="noopener noreferrer"&gt;The new Go JSON API: twice as fast, or 1.5x slower?&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Từ Go 1.27, package &lt;code&gt;encoding/json/v2&lt;/code&gt; có sẵn mặc định trong thư viện chuẩn. Hàm &lt;code&gt;Marshal&lt;/code&gt; và &lt;code&gt;Unmarshal&lt;/code&gt; của nó trông gần như giống hệt API cũ, nhưng không thể thay thế trực tiếp vì khác nhau ở cách kiểm tra Unicode, phân biệt hoa thường và một số hành vi khác. API cũ cũng đã được viết lại trên engine mới, nên Daniel Lemire so sánh ba cấu hình: bản &lt;code&gt;encoding/json&lt;/code&gt; gốc (build với &lt;code&gt;GOEXPERIMENT=nojsonv2&lt;/code&gt;), API cũ chạy trên backend mới của Go 1.27 và &lt;code&gt;json/v2&lt;/code&gt; dùng trực tiếp. Dữ liệu benchmark gồm ba file của simdjson là &lt;code&gt;twitter.json&lt;/code&gt;, &lt;code&gt;canada.json&lt;/code&gt; và &lt;code&gt;citm_catalog.json&lt;/code&gt;, parse vào kiểu &lt;code&gt;any&lt;/code&gt;, chạy đơn nhân trên Apple M4 Max và Intel Xeon Emerald Rapids.&lt;/p&gt;
&lt;p&gt;Chỉ cần nâng lên Go 1.27 mà không sửa code, API cũ đã marshal nhanh hơn tới khoảng 2 lần, ví dụ &lt;code&gt;twitter.json&lt;/code&gt; tăng từ 198 lên 374 MB/s. Unmarshal cũng nhanh hơn trên hai file, nhưng lại chậm hơn trên &lt;code&gt;canada.json&lt;/code&gt; (từ 128 xuống 106 MB/s). So với bản gốc, &lt;code&gt;json/v2&lt;/code&gt; unmarshal nhanh hơn 1,5 đến 2,3 lần và marshal nhanh hơn 1,2 đến 3 lần. Tuy nhiên, với bài test round trip 10.000 struct nhỏ có schema cố định, &lt;code&gt;json/v2&lt;/code&gt; vẫn unmarshal nhanh hơn nhưng marshal lại chậm hơn khoảng 1,5 lần so với bản gốc, trái với release note của Go 1.27 vốn nói hiệu năng marshal tương đương. Kết luận: unmarshal nhanh hơn ở mọi trường hợp, còn marshal thì phụ thuộc kiểu dữ liệu, nên hãy benchmark đúng workload của bạn trước khi chuyển sang &lt;code&gt;json/v2&lt;/code&gt;, nhất là khi marshal struct.&lt;/p&gt;
&lt;h2 id="a-deep-dive-into-deepsec"&gt;&lt;a href="https://flaviocopes.com/deepsec/" rel="noopener noreferrer"&gt;A deep dive into DeepSec&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Flavio Copes tìm hiểu DeepSec, công cụ quét bảo mật bằng AI mã nguồn mở (Apache 2.0) của Vercel. Khác với các công cụ chỉ review thay đổi trong pull request, DeepSec rà soát toàn bộ repository, kể cả code cũ chưa từng được review. Quy trình gồm bước quét pattern cục bộ, nhanh và miễn phí, bằng các matcher kết hợp đường dẫn file với regex. Sau đó Codex hoặc Claude Code đọc các file bị đánh dấu, lần theo luồng dữ liệu, tìm cơ chế phòng thủ sẵn có và ghi lại finding kèm mức độ, độ tin cậy và cách sửa. Một lượt agent thứ hai xác minh lại từng finding, bước triage rẻ hơn gán mức ưu tiên P0 đến P2, còn mọi trạng thái nằm trong thư mục &lt;code&gt;.deepsec/&lt;/code&gt; để chạy tiếp được.&lt;/p&gt;
&lt;p&gt;Khi chạy trên flaviocopes.com bằng Codex qua gói ChatGPT, DeepSec kiểm tra 729 file và trả về 504 finding, trong đó có 1 critical và 16 high. Tuy vậy, 462 finding nằm trong các công cụ chạy trên trình duyệt của site. Tác giả nhờ Codex gom finding theo nguyên nhân gốc và tách rủi ro production thực sự khỏi script hay ví dụ, nhờ đó thu hẹp về luồng thanh toán, khôi phục khóa học và form công khai. DeepSec không chứng minh được repository an toàn, vẫn có false positive và gửi code cho nhà cung cấp model. Bài học rút ra: con số finding thô dễ gây hiểu nhầm, nên hãy bắt đầu từ nơi input không tin cậy chạm tới xác thực, dữ liệu riêng tư, tiền bạc hay hạ tầng.&lt;/p&gt;
&lt;h2 id="imthenachomanhow-to-secure-a-linux-server-an-evolving-how-to-guide-for-securing-a-linux-server"&gt;&lt;a href="https://github.com/imthenachoman/How-To-Secure-A-Linux-Server" rel="noopener noreferrer"&gt;imthenachoman/How-To-Secure-A-Linux-Server: An evolving how-to guide for securing a Linux server.&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Đây là một hướng dẫn mã nguồn mở (giấy phép CC BY-SA 4.0, hơn 31 nghìn star) tổng hợp các bước bảo mật một Linux server, ra đời từ những ghi chép của tác giả khi tự dựng một máy Debian. Theo tác giả, kiến thức về chủ đề này nằm rải rác qua vô số bài viết khác nhau, còn phần lớn chúng ta không cài Linux mỗi ngày nên rất dễ quên những bước cơ bản. Ngay khi một server xuất hiện trên internet, nó đã trở thành mục tiêu, và kẻ xấu có thể lặng lẽ sao chép dữ liệu hoặc biến máy thành một node trong các cuộc tấn công DDoS mà bạn không hay biết. Tác giả khuyên đọc hướng dẫn này trước rồi mới đến CIS Benchmarks để các khuyến nghị của CIS được ưu tiên.&lt;/p&gt;
&lt;p&gt;Phần SSH hướng dẫn dùng public/private key, giới hạn người đăng nhập qua &lt;code&gt;AllowGroups&lt;/code&gt;, siết cấu hình &lt;code&gt;sshd_config&lt;/code&gt;, loại bỏ khóa Diffie-Hellman ngắn và bật 2FA. Phần cơ bản gồm giới hạn người được dùng &lt;code&gt;sudo&lt;/code&gt; và &lt;code&gt;su&lt;/code&gt;, chạy ứng dụng trong sandbox với FireJail, bảo vệ &lt;code&gt;/proc&lt;/code&gt;, bắt buộc mật khẩu mạnh và tự động cập nhật bản vá bảo mật. Về mạng, hướng dẫn dùng UFW làm firewall (kèm cách xử lý việc Docker bỏ qua UFW), PSAD để phát hiện tấn công từ log iptables, Fail2Ban và CrowdSec để chặn truy cập bất thường vào ứng dụng. Phần kiểm tra giới thiệu AIDE, ClamAV, Rkhunter, logwatch, Lynis và OSSEC. Cuối cùng là &amp;ldquo;vùng nguy hiểm&amp;rdquo; với những thay đổi cần cân nhắc kỹ như hardening sysctl của kernel, đặt mật khẩu cho GRUB hay tắt đăng nhập root. Có cả Ansible playbook do cộng đồng viết để tự động hóa các bước trên.&lt;/p&gt;
&lt;h2 id="postgres-19-how-our-advice-has-changed-since-we-wrote-it"&gt;&lt;a href="https://www.crunchydata.com/blog/postgres-19-how-our-advice-has-changed-since-we-wrote-it" rel="noopener noreferrer"&gt;Postgres 19: How Our Advice Has Changed Since We Wrote It&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Christopher Winslett của Crunchy Data xem lại loạt bài cũ về nạp dữ liệu, lưu trữ, index và partitioning, phần lớn viết cho Postgres 10 hay 11, để xem lời khuyên nào còn đúng với Postgres 19 (dựa trên bản beta). Async I/O từ bản 18 giúp tăng tốc sequential scan, bitmap heap scan và vacuum; bản 19 thêm tự động co giãn I/O worker, &lt;code&gt;EXPLAIN (ANALYZE, IO)&lt;/code&gt; và autovacuum song song, đồng thời tắt JIT theo mặc định. Lệnh &lt;code&gt;COPY&lt;/code&gt; ngày càng mạnh: parse CSV bằng SIMD, &lt;code&gt;ON_ERROR set_null&lt;/code&gt;, bỏ qua nhiều dòng header, xuất JSON trực tiếp và chạy &lt;code&gt;COPY TO&lt;/code&gt; trên bảng partitioned. LZ4 trở thành thuật toán nén TOAST mặc định, dù dữ liệu cũ vẫn giữ kiểu nén ban đầu, còn lệnh &lt;code&gt;REPACK&lt;/code&gt; có sẵn (kèm &lt;code&gt;CONCURRENTLY&lt;/code&gt;) thay cho &lt;code&gt;VACUUM FULL&lt;/code&gt; và &lt;code&gt;CLUSTER&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Với index, BRIN có thêm opclass &lt;code&gt;minmax_multi&lt;/code&gt; cho dữ liệu không được sắp xếp hoàn hảo và bloom cho truy vấn so sánh bằng trên UUID. Skip scan của B-tree từ bản 18 cho phép index nhiều cột phục vụ cả truy vấn lọc theo cột sau khi cột đầu có ít giá trị khác nhau, nên có thể bỏ bớt index đơn cột dư thừa. Partitioning có thêm &lt;code&gt;MERGE PARTITIONS&lt;/code&gt; và &lt;code&gt;SPLIT PARTITION&lt;/code&gt;, nhưng cả hai giữ lock &lt;code&gt;ACCESS EXCLUSIVE&lt;/code&gt; nên chỉ nên chạy trong cửa sổ bảo trì. Kết luận: các lời khuyên cốt lõi không đảo ngược, như dùng &lt;code&gt;COPY&lt;/code&gt; để nạp hàng loạt, lưu JSON bằng &lt;code&gt;jsonb&lt;/code&gt;, partition trước hết để quản lý vòng đời dữ liệu. Postgres 19 chủ yếu giúp làm theo chúng dễ hơn, nên hãy giữ nguyên tư duy, cập nhật lệnh và cấu hình, rồi kiểm tra lại bằng &lt;code&gt;EXPLAIN (ANALYZE, BUFFERS, IO)&lt;/code&gt; sau khi nâng cấp.&lt;/p&gt;
&lt;h2 id="how-we-saved-100-terabytes-of-memory-by-optimizing-1111s-dns-cache"&gt;&lt;a href="https://blog.cloudflare.com/dns-cache-memory-optimization-1111/" rel="noopener noreferrer"&gt;How we saved 100 terabytes of memory by optimizing 1.1.1.1’s DNS cache&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Big Pineapple, nền tảng viết bằng Rust đứng sau 1.1.1.1 và nhiều dịch vụ DNS khác của Cloudflare, lưu hơn 250 tỷ entry trong cache. Ở quy mô này, chỉ một byte lãng phí mỗi entry đã tốn hơn 250 GB trên toàn hệ thống. Entry một khi đã lưu thì không bao giờ bị sửa, nhưng các field vẫn dùng &lt;code&gt;Vec&lt;/code&gt; và &lt;code&gt;String&lt;/code&gt;, vốn mang theo field capacity và cấp phát dư. Thêm vào đó, mỗi record lại có kích thước bằng loại record DNS lớn nhất, nên A và AAAA bị độn rất nhiều. Đội ngũ đo đạc bằng benchmark mô phỏng traffic thật, kèm một allocator tùy biến theo dõi bộ nhớ từng entry, đồng thời đo throughput và latency để tránh làm chậm hệ thống.&lt;/p&gt;
&lt;p&gt;Họ thực hiện năm thay đổi: thay &lt;code&gt;Vec&lt;/code&gt;/&lt;code&gt;String&lt;/code&gt; bằng &lt;code&gt;Box&amp;lt;[T]&amp;gt;&lt;/code&gt;/&lt;code&gt;Box&amp;lt;str&amp;gt;&lt;/code&gt;, gộp ba danh sách record thành một và đánh dấu các phần bằng offset &lt;code&gt;u16&lt;/code&gt;, bỏ owner name khi nó trùng với tên truy vấn, đưa các biến thể enum lớn như TXT hay NAPTR ra sau &lt;code&gt;Box&lt;/code&gt;, và cuối cùng lưu toàn bộ record dưới dạng byte wire format đóng gói trong một allocation duy nhất. Với cách này, các record như A, AAAA hay TXT được chép thẳng vào response mà không cần parse. Kết quả benchmark: bộ nhớ mỗi entry giảm 56% (từ 953 xuống 420 byte), throughput insert tăng 43% và latency lookup giảm 19%. Trên production, bộ nhớ resident ở p99 giảm từ 9,3 GB xuống 5,3 GB, tổng cộng tiết kiệm khoảng 100 TB, tương đương RAM của khoảng 130 server. Cloudflare dự định dùng phần bộ nhớ này để mở rộng cache, qua đó tăng hit rate và giảm truy vấn lên upstream.&lt;/p&gt;
&lt;h2 id="agentic-code-quality"&gt;&lt;a href="https://addyo.substack.com/p/agentic-code-quality" rel="noopener noreferrer"&gt;Agentic Code Quality&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Addy Osmani cho rằng code review truyền thống không theo kịp khối lượng code do agent tạo ra, nên việc kiểm soát chất lượng phải chuyển vào harness, môi trường và hệ thống xung quanh agent. Luận điểm trung tâm: chất lượng phần mềm giờ phụ thuộc vào các ràng buộc bạn đặt quanh agent. Các ràng buộc này gồm unit test, property test, acceptance test, mutation testing để kiểm tra test có bắt được lỗi cố ý cài vào hay không, cùng các chỉ số như độ phức tạp cyclomatic. Mức độ rủi ro quyết định bạn có thể bỏ qua việc đọc code đến đâu: prototype hay code dùng một lần thì có thể, còn code quan trọng cần người đọc hoặc ràng buộc thật chặt. Mục tiêu là một môi trường cho agent phản hồi đáng tin cậy và để nó thất bại mà ít gây thiệt hại.&lt;/p&gt;
&lt;p&gt;Ràng buộc nên áp dụng ở ba thời điểm: trước khi bắt đầu, trong lúc làm việc dưới dạng phản hồi, và tại ranh giới production. Mỗi lớp kiểm tra nên có trách nhiệm riêng, từ type safety, hiệu năng đến quét bảo mật. Compiler, test thất bại, chính sách bảo mật và CI đều tạo &amp;ldquo;back-pressure&amp;rdquo; sớm thay vì dồn vào một lần review cuối. Khi lượng thay đổi vượt quá khả năng kiểm chứng, team có thể mở rộng hệ thống kiểm chứng, giảm tốc độ sinh code hoặc hạ chuẩn chất lượng, lựa chọn mà tác giả ít mong muốn nhất. Sự chú ý của con người là tài nguyên khan hiếm, nên hãy dành nó cho những quyết định tinh tế về ý đồ, gu và kiến trúc, và chỉ kéo người vào khi các hàng rào tự động thất bại.&lt;/p&gt;
&lt;h3 id="bonus"&gt;Bonus&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;Videos:&lt;/strong&gt;
&lt;a href="https://www.youtube.com/watch?v=bF28LFPjFsI" rel="noopener noreferrer"&gt;JVM thực sự hoạt động như thế nào&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
 &lt;p&gt;Video của ByteByteGo giải thích cách Java Virtual Machine vận hành bên trong, từ lúc mã Java được biên dịch thành bytecode cho tới khi được thực thi trên máy.&lt;/p&gt;

&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Images:&lt;/strong&gt;
&lt;img src="https://substackcdn.com/image/fetch/$s_!tsAw!,w_1100,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0fa724e-feb3-4ec6-9c53-50437d9cf884_2484x3002.png" alt="MCP vs RAG vs AI Agents"&gt;
&lt;img src="https://substackcdn.com/image/fetch/$s_!aJjm!,w_1100,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F79e409f6-979a-4756-9366-de84824ab9d8_2484x3002.png" alt="9 Distributed Systems Patterns You Should Know"&gt;&lt;/p&gt;</description></item><item><title>Newsletter #133</title><link>https://miti99.com/post/2026/10/01/</link><pubDate>Thu, 01 Oct 2026 00:00:00 +0700</pubDate><guid>https://miti99.com/post/2026/10/01/</guid><description>&lt;p&gt;&lt;em&gt;Mời bạn thưởng thức Newsletter #133.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="why-go-is-an-ideal-language-for-ai-assisted-software-engineering"&gt;&lt;a href="https://developers.googleblog.com/why-go-is-an-ideal-language-for-ai-assisted-software-engineering/" rel="noopener noreferrer"&gt;Why Go is an Ideal Language for AI-Assisted Software Engineering&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Cameron Balahan và Richard Seroter từ Google cho rằng nút thắt của kỹ thuật phần mềm đã dịch chuyển: khi AI sinh ra hàng trăm dòng mã trong vài giây, phần khó không còn là viết mà là duyệt, kiểm chứng và bảo trì. Ngôn ngữ được thiết kế cho cộng tác nhóm lâu dài sẽ có lợi thế, và Go ra đời ở Google chính với mục tiêu ấy. Go là cả một nền tảng: bộ công cụ chuẩn có sẵn trình định dạng &lt;code&gt;gofmt&lt;/code&gt;, khung kiểm thử, quản lý module và công cụ bảo mật, cùng thư viện chuẩn đầy đủ giúp giảm phụ thuộc bên ngoài. Go ưu tiên sự tường minh hơn là ngắn gọn hay khéo léo, và khi mã của người lẫn mô hình ngôn ngữ đều chung một định dạng, việc phát hiện lời gọi API bịa đặt hay lỗi logic trở nên dễ hơn.&lt;/p&gt;
&lt;p&gt;Về độ tin cậy, kiểu tĩnh và tốc độ biên dịch rất nhanh tạo vòng lặp tự sửa lỗi ngắn cho agent. Cơ sở dữ liệu checksum chống việc phụ thuộc bị sửa đổi, còn &lt;code&gt;govulncheck&lt;/code&gt; chỉ cảnh báo những lỗ hổng mà mã thực sự gọi tới. Về bảo trì, cam kết tương thích ngược bảo đảm mã viết cho Go 1.0 vẫn chạy trên công cụ mới nhất, chương trình biên dịch thành một tệp nhị phân tĩnh, và &lt;code&gt;go fix&lt;/code&gt; giúp agent hiện đại hóa mã cũ một cách tất định. Bài viết thiên về lập luận, không có số liệu đo đạc, nhưng thông điệp rõ ràng: AI là cộng sự năng suất cao cần rào chắn chắc chắn, và Go cung cấp sẵn các rào chắn đó.&lt;/p&gt;
&lt;h2 id="how-does-programming-language-affect-token-efficiency-and-correctness"&gt;&lt;a href="https://danluu.com/pl-tokens/" rel="noopener noreferrer"&gt;How does programming language affect token efficiency and correctness?&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Có ý kiến cho rằng ngôn ngữ động tiết kiệm token hơn ngôn ngữ tĩnh khi làm việc với mô hình ngôn ngữ lớn. Dan Luu kiểm chứng điều này, bắt đầu bằng việc soi lại hai bộ đánh giá sẵn có. Bộ thứ nhất đo số token để giải các bài Rosetta Code, nhưng bài toán quá đơn giản nên khó khái quát. Bộ thứ hai có lỗi đường dẫn khiến Rust và Haskell trượt oan, cùng kiểm thử luôn đạt bất kể kết quả; chấm lại thì Rust đạt điểm tuyệt đối. Luu tự xây đánh giá riêng: agent viết trọn bộ giải mã zstd chỉ dựa trên đặc tả RFC rồi chấm bằng bộ kiểm thử ẩn, và một bài tái hiện Pandoc chấm trên tập kiểm thử giữ riêng.&lt;/p&gt;
&lt;p&gt;Kết quả: với zstd ở mức suy luận trung bình, ngôn ngữ động có vẻ nhỉnh hơn, nhưng ở mức cao nhất thì nhiều ngôn ngữ tĩnh dẫn đầu. Clojure thất bại 36 trên 40 lần ở mức trung bình, chủ yếu vì phép chuyển kiểu &lt;code&gt;byte&lt;/code&gt; ném lỗi với giá trị 128–255. Assembly và các ngôn ngữ ít phổ biến làm kém, còn độ phổ biến của ngôn ngữ tương quan nhẹ với độ chính xác và chi phí thấp. Gần như mọi chương trình Pandoc viết bằng C và C++ đều có lỗi an toàn bộ nhớ mà kiểm thử không phát hiện. Tác giả thừa nhận hai bài toán chưa đủ để kết luận chắc chắn, nhưng nhận định &amp;ldquo;ngôn ngữ động tốt hơn cho mô hình ngôn ngữ lớn&amp;rdquo; không đứng vững; chọn ngôn ngữ phổ biến là mặc định hợp lý, và đặc tả rõ ràng giúp ích rất nhiều.&lt;/p&gt;
&lt;h2 id="my-agentic-coding-setup-july-2026"&gt;&lt;a href="https://domenic.me/agentic-coding-setup/" rel="noopener noreferrer"&gt;My Agentic Coding Setup, July 2026&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Domenic Denicola chia sẻ môi trường lập trình cùng agent mà anh chốt lại sau nhiều tháng thử nghiệm, với kết quả là có thể nhờ các mô hình hàng đầu sửa lỗi trên hệ thống thật ngay từ điện thoại khi đang ngồi tàu. Yêu cầu đặt ra khá rõ: agent chạy trên Linux vì chúng thạo Bash hơn PowerShell, công việc chuyển qua lại giữa máy bàn và laptop mà không mất phiên làm việc, nhiều agent làm song song trên cùng dự án, và gần như không phải bấm duyệt quyền. Nền tảng là một máy ảo Ubuntu &amp;ldquo;dùng xong có thể bỏ&amp;rdquo; đặt trên máy bàn luôn bật, nối với các thiết bị khác qua mạng riêng Tailscale để SSH và truy cập HTTPS từ bất cứ đâu. Trên đó anh chạy Claude Code và Codex CLI ở chế độ toàn quyền, cài sẵn &lt;code&gt;gh&lt;/code&gt; để agent tự quản lý kho mã, PR và CI. Anh thừa nhận cách này không an toàn về nguyên tắc, và chốt chặn chính là đẩy mã lên GitHub sớm và thường xuyên.&lt;/p&gt;
&lt;p&gt;Các mảnh ghép còn lại giải quyết từng yêu cầu: git worktree cho agent làm song song (anh chuộng ChatGPT hơn vì Claude đặt worktree ngay trong thư mục dự án), VS Code Remote-SSH để xem xét thay đổi, công cụ Portless bọc qua Tailscale để mỗi máy chủ phát triển có địa chỉ HTTPS riêng chỉ mình anh truy cập được, và chezmoi để đồng bộ &lt;code&gt;AGENTS.md&lt;/code&gt;, skill cùng cấu hình giữa các máy. Những điểm còn thiếu gồm dev container để cách ly an toàn hơn, lịch sử phiên gắn chặt với đường dẫn thư mục, và chưa có cách sao lưu bền vững bản ghi phiên.&lt;/p&gt;
&lt;h2 id="the-bedrock-of-software-design"&gt;&lt;a href="https://alex.draftist.io/blog/the-bedrock-of-software-design-ycqvcedsj" rel="noopener noreferrer"&gt;The Bedrock of Software Design&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Alex Fedoseev cho rằng kiểu dữ liệu đại số (algebraic data type) là thứ ảnh hưởng tới cách anh thiết kế phần mềm nhiều nhất, và chúng đơn giản hơn cái tên rất nhiều. &lt;em&gt;Kiểu tích&lt;/em&gt; gộp nhiều trường thành một giá trị, như một struct có &lt;code&gt;id&lt;/code&gt; và &lt;code&gt;role&lt;/code&gt;; &lt;em&gt;kiểu tổng&lt;/em&gt; nói rằng giá trị là một trong vài biến thể, mỗi biến thể mang dữ liệu riêng, chẳng hạn phiên đăng nhập hoặc ẩn danh, hoặc đã xác thực và chỉ biến thể sau mới chứa thông tin người dùng. Quy tắc nghiệp vụ nằm rải rác trong chú thích hay hàm kiểm tra rất dễ bị bỏ qua. Thay vì mô hình giao diện trang web bằng một biến boolean cùng vài trường có thể null, ta dùng kiểu tổng với hai biến thể &lt;code&gt;Switchable&lt;/code&gt; và &lt;code&gt;Fixed&lt;/code&gt;, mỗi biến thể chỉ giữ đúng trường cần thiết, để trạng thái không hợp lệ không thể biểu diễn được.&lt;/p&gt;
&lt;p&gt;Tư duy này áp dụng cho cả lỗi và sự vắng mặt của giá trị. Với ngoại lệ, chữ ký hàm chỉ cho thấy trường hợp thành công; Rust thì trả lỗi dự kiến qua &lt;code&gt;Result&lt;/code&gt;, và kiểu lỗi cũng có thể là kiểu tổng kèm ngữ cảnh. Thay cho &lt;code&gt;null&lt;/code&gt; là &lt;code&gt;Option&amp;lt;T&amp;gt;&lt;/code&gt;, và &lt;code&gt;Result&amp;lt;Option&amp;lt;User&amp;gt;, FindUserError&amp;gt;&lt;/code&gt; phân biệt rõ ba kết cục: tìm thấy, không có, và tra cứu thất bại. Phép &lt;code&gt;match&lt;/code&gt; vét cạn buộc xử lý mọi trường hợp, nên khi thêm vai trò &lt;code&gt;Staff&lt;/code&gt;, trình biên dịch chỉ ra mọi chỗ cần quyết định. Kết luận: kiểu tổng và so khớp vét cạn nên là yêu cầu tối thiểu của một ngôn ngữ lập trình.&lt;/p&gt;
&lt;h2 id="90--of-the-t-distribution"&gt;&lt;a href="https://entropicthoughts.com/ninety-percent-of-the-t-distribution" rel="noopener noreferrer"&gt;90 % of the t distribution&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Khi ước lượng khoảng tin cậy 90% cho giá trị trung bình, cách làm tắt quen thuộc là lấy trung bình mẫu cộng trừ 1,645 lần độ lệch chuẩn mẫu. Cách này ngầm giả định độ lệch chuẩn mẫu bằng đúng độ lệch chuẩn thật, bỏ qua sai số của chính phép ước lượng ấy, nên khoảng thu được hẹp hơn thực tế, và càng ít mẫu thì sai lệch càng lớn. Bài viết đưa ra một bảng hiệu chỉnh rút ra từ phân phối t của William Sealy Gosset (&amp;ldquo;Student&amp;rdquo;): nhân độ lệch chuẩn với 4 khi có 2 mẫu, 2 khi có 3 mẫu, 1,5 khi có 4 mẫu, 1,3 khi có 5 mẫu, 1,2 với 6 đến 8 mẫu và 1,1 với 9 đến 20 mẫu; trên 20 mẫu thì công thức thông thường đã đủ tốt. Ví dụ, với 7 mẫu có trung bình 32 phút và độ lệch chuẩn 8 phút, khoảng đúng là 32 ± 8 × 1,2 × 1,645.&lt;/p&gt;
&lt;p&gt;Tác giả còn chỉ ra một mẹo hữu ích: chỉ với hai giá trị vẫn ước lượng được độ phân tán. Sau khi hiệu chỉnh theo phân phối t, độ lệch chuẩn ước lượng xấp xỉ 1,3 lần khoảng cách giữa hai giá trị. Chẳng hạn, ai đó khoe đạt kết quả 49 lít trong khi hai giá trị tham chiếu là 43 và 47 lít: độ lệch chuẩn khoảng 5 lít, điểm giữa là 45, nên 49 chỉ cách chưa tới một độ lệch chuẩn và là kết quả bình thường chứ không có gì nổi bật. Thông điệp chính là mẫu nhỏ cần khoảng tin cậy rộng hơn, và vài con số đã đủ cho những phán đoán thường ngày.&lt;/p&gt;
&lt;h2 id="your-agent-needs-a-computer-not-a-container--introducing-cloudflarecomputer"&gt;&lt;a href="https://blog.cloudflare.com/cloudflare-computer/" rel="noopener noreferrer"&gt;Your agent needs a computer, not a container — introducing @cloudflare/computer&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Cloudflare ra mắt bản xem trước của @cloudflare/computer, một môi trường chạy agent mã nguồn mở, cài qua npm, cấp cho mỗi agent một &amp;ldquo;máy tính&amp;rdquo; riêng gồm hệ thống tệp, shell, công cụ và khả năng thực thi mã. Luận điểm chính là cấp cho mỗi agent một container riêng sẽ không mở rộng nổi khi số agent chạy đồng thời lên tới hàng trăm triệu, thậm chí hàng tỷ. Isolate phù hợp hơn: khởi động và dừng rất nhanh, ngủ đông khi rảnh, tự lưu trạng thái và mở rộng theo chiều ngang; container chỉ nên dùng khi thực sự cần năng lực của một máy Linux đầy đủ. Mục tiêu đặt ra là container chỉ đảm nhận dưới 10% khối lượng việc của một agent.&lt;/p&gt;
&lt;p&gt;Kiến trúc tách &amp;ldquo;bộ não&amp;rdquo; khỏi &amp;ldquo;đôi tay&amp;rdquo;: vòng lặp agent chạy trong một Durable Object, còn container được gắn vào và gọi như một công cụ khi cần. Không gian làm việc là hệ thống tệp ảo dựa trên SQLite, nạp dữ liệu từ kho lưu trữ đám mây hay kho git, cung cấp các thao tác đọc, ghi, sửa, liệt kê, thực thi, và dùng được qua lớp bọc tương thích &lt;code&gt;node:fs&lt;/code&gt;. Có hai môi trường thực thi chung một giao diện &lt;code&gt;exec&lt;/code&gt;: isolate dùng just-bash để dịch lệnh shell sang JavaScript, còn container dùng FUSE để đồng bộ thay đổi về không gian làm việc. Mọi thao tác trên tệp đều được kiểm soát và ghi nhật ký, còn mô hình tự chọn môi trường phù hợp cho từng lệnh, việc mà theo tác giả các mô hình hàng đầu làm khá tốt. Cloudflare coi đây là thử nghiệm và mong nhận phản hồi từ những ai chạy agent ở quy mô lớn.&lt;/p&gt;
&lt;h2 id="notes-on-incidents"&gt;&lt;a href="https://www.seangoedecke.com/notes-on-incidents/" rel="noopener noreferrer"&gt;Notes on incidents&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Sean Goedecke chia sẻ những gì anh rút ra sau nhiều lần tham gia xử lý sự cố. Trước hết, sự cố phần lớn khá nhàm chán, chủ yếu là chờ đợi: chờ đội khác, chờ triển khai hay chờ đồng nghiệp được gọi dậy. Đa số sự cố tự khỏi, vì hệ thống thiết kế tốt có cơ chế tự phục hồi như Kubernetes khởi động lại pod bị sập hay hàng đợi hấp thụ đợt tăng tải. Anh ước tính hơn một nửa số cuộc gọi sự cố anh từng tham gia sẽ kết thúc trong khoảng thời gian tương tự dù không ai can thiệp. Ngược lại, phần lớn hành động &amp;ldquo;cứu hỏa&amp;rdquo; lại làm mọi chuyện tệ hơn: xóa một hàng đợi lớn trên môi trường thật có thể vô tình xóa luôn các tác vụ tính tiền, biến sự cố độ trễ thành sự cố thanh toán. Vì vậy việc đầu tiên nên làm là&amp;hellip; không làm gì cả: pha một tách trà hay đi dạo vài phút.&lt;/p&gt;
&lt;p&gt;Cách sửa hiệu quả thường rất đơn giản, điển hình là tạm tắt tính năng gây lỗi. Yếu tố quan trọng nhất là hiểu biết về hệ thống: một kỹ sư nắm rõ kho mã sẽ nhanh chóng tìm ra đúng feature flag hay commit cần hoàn tác. Người đó cũng cần dũng cảm và quyết đoán: nói rõ mình định làm gì, chờ một chút rồi làm. Xử lý sự cố giúp ghi điểm với cấp trên, nhưng tác giả nhắc rằng đó không phải nguồn uy tín bền vững, vì lãnh đạo khó phân biệt nỗ lực anh hùng với cách sửa hiển nhiên, và kết quả tốt nhất vẫn là không xảy ra sự cố.&lt;/p&gt;
&lt;h2 id="beyond-clean-code-why-your-comments-matter"&gt;&lt;a href="https://blog.moertel.com/posts/2026-07-27-beyond-clean-code-why-your-comments-matter.html" rel="noopener noreferrer"&gt;Beyond “Clean Code”: Why Your Comments Matter&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Tom Moertel cho rằng chú thích là thành phần cần thiết của mã nguồn tốt. Logic chỉ diễn đạt được điều máy tính được ra lệnh làm, trong khi người đọc còn cần biết tác giả &lt;em&gt;định&lt;/em&gt; làm gì và &lt;em&gt;tại sao&lt;/em&gt; mã được viết như vậy, những thông tin thường diễn đạt tốt nhất bằng ngôn ngữ tự nhiên. Nên thể hiện ý đồ ngay trong mã khi làm được một cách tự nhiên, nhưng đừng bẻ cong cấu trúc hay đặt tên thật dài chỉ để nhét lời giải thích vào. Anh đồng ý với Robert C. Martin trong &lt;em&gt;Clean Code&lt;/em&gt; rằng chú thích không nên dùng để chống đỡ cho logic tồi, nhưng cho rằng khẳng định &amp;ldquo;chú thích là thất bại&amp;rdquo; đã đi quá xa. Ví dụ về thông tin &amp;ldquo;tại sao&amp;rdquo; gồm thứ tự khóa toàn cục để tránh deadlock, một bất biến về độ dài mảng, lý do chọn cách vét cạn vì đầu vào được bảo đảm nhỏ.&lt;/p&gt;
&lt;p&gt;Anh gợi ý một phép thử: đọc mã mà bỏ qua tên gọi, nếu cấu trúc phức tạp hơn cần thiết thì có thể nó đang bị bẻ cong, khi đó hãy dùng chú thích. Anh cũng phản bác các lập luận quen thuộc. &amp;ldquo;Chú thích có thể nói dối&amp;rdquo;: không có chú thích thì logic sai chẳng để lại dấu hiệu gì, còn chú thích mâu thuẫn với mã chính là tín hiệu để điều tra. &amp;ldquo;Chú thích bị lỗi thời&amp;rdquo;: đó là vấn đề văn hóa kỹ thuật, cần sửa bằng duyệt mã chứ không phải xóa chú thích. Kết luận: mọi thứ trong mã nguồn nên truyền đạt ý đồ rõ ràng, và riêng logic thì không làm được điều đó với thông tin &amp;ldquo;tại sao&amp;rdquo;.&lt;/p&gt;
&lt;h2 id="relying-on-go"&gt;&lt;a href="https://antonz.org/relying-on-go/" rel="noopener noreferrer"&gt;Relying on Go&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Nhiều ngôn ngữ lập trình mới tự giới thiệu là &amp;ldquo;giống Go nhưng nhiều tính năng hơn&amp;rdquo; hoặc &amp;ldquo;giống Rust nhưng đơn giản hơn&amp;rdquo;. Anton Zhiyanov chọn hướng khác với Solod, &amp;ldquo;ngôn ngữ hệ thống dành cho lập trình viên C và Go&amp;rdquo;: thay vì sửa Go hay tạo ra một ngôn ngữ na ná Go, Solod chính là một tập con của Go. Nhờ vậy nó tận dụng nguyên bộ công cụ sẵn có: tô sáng cú pháp, LSP, linter và quản lý gói. Quy trình làm việc y hệt Go: cài công cụ dòng lệnh &lt;code&gt;so&lt;/code&gt;, tạo module, thêm thư viện chuẩn của Solod làm phụ thuộc rồi viết mã Go bình thường; lệnh &lt;code&gt;so run .&lt;/code&gt; chạy chương trình như &lt;code&gt;go run&lt;/code&gt;. Thư viện chuẩn của Solod tái sử dụng phần lớn mã và kiểm thử của Go, với một số hàm được sửa để hỗ trợ quản lý bộ nhớ thủ công.&lt;/p&gt;
&lt;p&gt;Tác giả cũng nói rõ giới hạn: công cụ Go chuẩn không biết về tập con này nên không cảnh báo khi dùng tính năng chưa được hỗ trợ như hàm ẩn danh hay iterator, việc đó do công cụ &lt;code&gt;so&lt;/code&gt; đảm nhận. Mã được chuyển từ Go sang cũng không mặc nhiên đúng, nên Solod vẫn cần kiểm thử riêng. Bên dưới, mã Solod được dịch sang C11 rồi biên dịch bằng GCC hoặc Clang, hưởng lợi từ hàng chục năm tối ưu của C; mã C sinh ra vẫn khá dễ đọc, và vì không có runtime nên gọi qua lại với C không tốn chi phí. Thông điệp chính: một ngôn ngữ mới không nhất thiết cần một hệ sinh thái mới, và dựa vào Go chính là điểm mạnh của Solod.&lt;/p&gt;</description></item><item><title>Newsletter #132</title><link>https://miti99.com/post/2026/09/10/</link><pubDate>Thu, 10 Sep 2026 00:00:00 +0700</pubDate><guid>https://miti99.com/post/2026/09/10/</guid><description>&lt;p&gt;&lt;em&gt;Mời bạn thưởng thức Newsletter #132.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="perfection-is-not-over-engineering"&gt;&lt;a href="https://var0.xyz/posts/perfection-is-not-over-engineering.html" rel="noopener noreferrer"&gt;Perfection is not over-engineering&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Câu &amp;ldquo;chúng ta không muốn làm cho hoàn hảo&amp;rdquo; xuất hiện trong các cuộc họp nhiều tới mức tác giả không đếm xuể. Anh hiểu sự dè chừng ấy vì thiết kế quá mức (over-engineering) từng làm nhiều đội kiệt sức, nhưng cho rằng cả ngành đang đánh đồng hai chuyện khác nhau. Thiết kế quá mức đơn giản là &lt;em&gt;giải sai bài toán&lt;/em&gt;, thường với thiện chí và kèm một đống phức tạp phát sinh. Còn giải pháp hoàn hảo thì có thật, miễn là yêu cầu rõ ràng và mọi ràng buộc được đặt lên bàn: siết ràng buộc đủ chặt thì chỉ còn một lựa chọn, và nó hoàn hảo chính vì là thứ duy nhất vừa khít. Chọn serverless với Python để khỏi bước biên dịch có thể đúng với người này nhưng sai với người không biết Python hoặc cần ưu tiên hiệu năng.&lt;/p&gt;
&lt;p&gt;Theo tác giả, mọi hệ thống đều là sản phẩm: thư viện, API hay công cụ nội bộ đều có người dùng với nhu cầu thật. Dấu hiệu rõ nhất của thiết kế quá mức là khi hỏi &amp;ldquo;tại sao lại xây như thế này?&amp;rdquo; mà câu trả lời không đứng vững. Ví dụ quen thuộc là đội ba người duy trì năm microservice dùng chung dữ liệu: khóa ngoại vốn do cơ sở dữ liệu bảo đảm thành chuỗi id rời rạc, dịch vụ này xóa bản ghi mà dịch vụ kia không hay biết. Đổi lại họ được triển khai độc lập, một vấn đề họ chưa từng gặp. Kết luận: đó là thất bại trong thu thập yêu cầu, và kẻ thù chưa bao giờ là sự hoàn hảo mà là yêu cầu mơ hồ.&lt;/p&gt;
&lt;h2 id="claude-is-not-a-compiler"&gt;&lt;a href="https://blog.exe.dev/claude-is-not-a-compiler" rel="noopener noreferrer"&gt;Claude Is Not a Compiler&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Josh Bleecher Snyder khẳng định Claude không phải trình biên dịch, vì nó còn &lt;em&gt;tốt hơn&lt;/em&gt; thế. Theo cách nhìn lý tưởng, phần mềm được xây theo từng lớp: tầm nhìn thành chiến lược, kế hoạch sản phẩm thành kế hoạch lập trình, mã nguồn thành tệp nhị phân, mỗi bước là vô số quyết định do một vai trò khác nhau đảm nhận. Nhưng thực tế các lớp luôn rò rỉ và cọ vào nhau, và làm việc xuyên lớp mới tạo ra giá trị lớn, như cách tòa Empire State Building được dựng xong trong chưa đầy một năm nhờ những cuộc họp gom đủ mọi bên liên quan. Claude làm được việc đó theo chiều dọc: bàn chiến lược, kiến trúc, mã nguồn, mã máy mà không cần xếp lịch họp.&lt;/p&gt;
&lt;p&gt;Ví dụ là máy chủ DNS phân tán của exe.dev. Các quyết định chiến lược và kiến trúc được chốt trực tiếp giữa người với người; phần còn lại anh giao cho nhiều vòng lặp agent chạy song song dựng trọn hệ thống kèm kiểm thử, rồi nhờ agent khác so sánh các bản cài đặt. Điều gây sốc là rất nhiều quyết định quan trọng được agent tự đưa ra mà không hỏi, và mỗi agent quyết một kiểu, chẳng hạn cách xử lý khi cơ sở dữ liệu bị lùi trạng thái. Lặp quy trình này ba lần, anh chắt lọc được một tài liệu ngắn đủ dẫn agent qua mọi quyết định quan trọng. Sau khoảng một tuần, gần như không đọc mã, anh trả lời được mọi câu hỏi của đồng nghiệp và một tháng sau số sự cố DNS là 0. Đó là vibe-engineering, và theo anh sắp tới nó chỉ đơn giản là kỹ thuật phần mềm.&lt;/p&gt;
&lt;h2 id="in-defense-of-not-understanding-your-codebase"&gt;&lt;a href="https://www.seangoedecke.com/in-defense-of-not-understanding-your-codebase/" rel="noopener noreferrer"&gt;In defense of not understanding your codebase&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Kỹ sư phải hiểu kho mã nguồn của mình tới đâu? Sean Goedecke cho rằng câu trả lời tùy vào môi trường. Người làm với kho mã nhỏ như Redis sẽ nói phải hiểu hết; người làm ở hệ thống khổng lồ như Google hay GitHub sẽ nói điều đó bất khả thi. Nhóm đầu áp đảo các cuộc thảo luận trên mạng, nên tác giả đứng ra bảo vệ nhóm sau: với hệ thống lớn, hiểu &lt;em&gt;một phần&lt;/em&gt; không có gì sai, thậm chí là mức tốt nhất có thể đạt được. Ở quy mô đủ lớn, ai cũng làm việc với một hình dung chưa đúng hẳn, nên kỹ sư giỏi phải dám phỏng đoán có cơ sở.&lt;/p&gt;
&lt;p&gt;Anh phản bác bài &amp;ldquo;Programming as Theory Building&amp;rdquo; của Peter Naur. Anh đồng ý rằng sản phẩm thật của lập trình viên là &amp;ldquo;lý thuyết về chương trình&amp;rdquo; trong đầu họ, nhưng bác bỏ kết luận rằng khi đội cũ ra đi thì nên bỏ mã cũ và làm lại từ đầu. Thứ nhất, hệ thống lớn chứa hàng nghìn tình huống kỳ quặc không thể tái hiện, và mọi lần viết lại thành công đều bắt đầu bằng việc chia nhỏ hệ thống cũ. Thứ hai, các kho mã bị bỏ hoang vẫn được hồi sinh thường xuyên: bắt đầu bằng việc hiểu trọn một luồng xử lý rồi mở rộng dần. Cuối cùng, giữ được lý thuyết chỉ là một giá trị trong nhiều giá trị, bị đánh đổi mỗi khi người khác cùng viết mã hay khi phải nâng phiên bản để vá lỗ hổng. Đi làm là nhận tiền để theo bộ giá trị của công ty, giống như đôi khi phải chấp nhận viết mã chậm cho kịp hạn.&lt;/p&gt;
&lt;h2 id="beyond-happy-path-engineering-the-network"&gt;&lt;a href="https://blog.gaborkoos.com/posts/2026-07-01-Beyond-Happy-Path-Engineering-the-Network/" rel="noopener noreferrer"&gt;Beyond Happy Path Engineering: the Network&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Bài mở đầu loạt &amp;ldquo;Beyond Happy Path Engineering&amp;rdquo; của Gabor Koos, xem xét những giả định mà môi trường phát triển luôn chiều chuộng nhưng môi trường thật thì không, bắt đầu với mạng. Một lời gọi mạng trông như yêu cầu và phản hồi đơn giản, nhưng thực chất băng qua ranh giới sang một hệ thống ta không kiểm soát. Với ví dụ bước xác thực thanh toán khi đặt hàng, lỗi &amp;ldquo;sạch&amp;rdquo; như không mở được kết nối dễ xử lý hơn nhiều so với timeout sau khi đã gửi yêu cầu: thao tác có thể đã xong, đã hỏng hoặc chưa hề bắt đầu. Nguyên tắc then chốt là &lt;em&gt;không nhận được phản hồi không có nghĩa là thao tác chưa xảy ra&lt;/em&gt;. Độ trễ cũng là một dạng lỗi: dịch vụ chậm vẫn giữ luồng và kết nối, giá trị trung bình che mất phần đuôi, và sự chậm chạp thường dẫn tới sập hệ thống.&lt;/p&gt;
&lt;p&gt;Các kỹ thuật được đề xuất: coi timeout là quyết định thiết kế dựa trên ngân sách thời gian của mỗi yêu cầu; chỉ thử lại khi an toàn, thường nhờ khóa idempotency gửi kèm mọi lần thử, cộng backoff và jitter để các client không đồng loạt thử lại; truyền deadline và tín hiệu hủy xuống tầng dưới. Circuit breaker và load shedding bảo vệ hệ thống khi quá tải, còn graceful degradation tách việc bắt buộc như thanh toán khỏi việc có thể hoãn như gửi email, kèm trạng thái đơn hàng rõ ràng như &amp;ldquo;đang chờ&amp;rdquo;. Sự cố GitHub năm 2018, khi gián đoạn mạng chưa tới một phút gây suy giảm dịch vụ hơn một ngày, minh họa rõ cái giá của những giả định này.&lt;/p&gt;
&lt;h2 id="beyond-happy-path-engineering-time"&gt;&lt;a href="https://blog.gaborkoos.com/posts/2026-07-19-Beyond-Happy-Path-Engineering-Time/" rel="noopener noreferrer"&gt;Beyond Happy Path Engineering: Time&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Phần thứ hai của loạt bài chuyển sang thời gian, thứ trông như phụ thuộc đơn giản nhất: lấy thời điểm hiện tại, so sánh, rồi quyết định. Ví dụ xuyên suốt là luồng thanh toán giữ hàng mười lăm phút với câu lệnh &lt;code&gt;if now() &amp;gt; reservation.expiresAt&lt;/code&gt;. Nếu đồng hồ của tiến trình dọn dẹp nhanh hơn hai phút, hàng sẽ bị nhả ra khi người dùng còn đang trả tiền. Đồng hồ có thể bị chỉnh tiến hoặc lùi giữa chừng khiến thời lượng đo được bị âm, còn dấu thời gian thì không chứng minh được thứ tự sự kiện. Đáng sợ nhất là lỗi thời gian lan âm thầm: dịch vụ vẫn chạy, bảng điều khiển vẫn xanh, nhưng hệ thống đang ra những quyết định hơi sai.&lt;/p&gt;
&lt;p&gt;Luận điểm chính là thời gian trả lời bốn loại câu hỏi khác nhau: &lt;em&gt;thời gian đồng hồ&lt;/em&gt; ghi nhận sự kiện xảy ra lúc nào, &lt;em&gt;thời gian đơn điệu&lt;/em&gt; để đo thời lượng và hạn chờ, &lt;em&gt;thời gian logic&lt;/em&gt; để xếp thứ tự các cập nhật cạnh tranh, và &lt;em&gt;thời gian nghiệp vụ&lt;/em&gt; gắn với kỳ thanh toán, giờ chốt đơn hay múi giờ người dùng. Hết hạn nên được coi là một &lt;em&gt;chuyển trạng thái&lt;/em&gt;: dấu thời gian chỉ để lọc ứng viên, còn lệnh ghi có điều kiện chỉ chuyển sang &lt;code&gt;expired&lt;/code&gt; khi bản ghi vẫn đang &lt;code&gt;reserved&lt;/code&gt;, nhờ vậy tiến trình chạy trễ hay chạy trùng cũng vô hại. Khi thời gian không đủ để phân xử thì đối soát với nguồn chính thống như nhà cung cấp thanh toán. Sự cố DNS của Cloudflare dịp giây nhuận đầu năm 2017, khi phép trừ hai mốc &lt;code&gt;time.Now()&lt;/code&gt; cho ra số âm và gây panic, là minh họa điển hình.&lt;/p&gt;
&lt;h2 id="beyond-happy-path-engineering-databases"&gt;&lt;a href="https://blog.gaborkoos.com/posts/2026-08-01-Beyond-Happy-Path-Engineering-Databases/" rel="noopener noreferrer"&gt;Beyond Happy Path Engineering: Databases&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Phần thứ ba của loạt bài bàn về cơ sở dữ liệu, tầng thường được coi là đáng tin nhất. Ví dụ xuyên suốt là luồng thanh toán của một cửa hàng trực tuyến, chạy trơn tru khi kiểm thử cục bộ vì không có lưu lượng cạnh tranh. Môi trường thật thì khác: thanh toán, trang quản trị, tác vụ nền, báo cáo và migration cùng tranh nhau kết nối và khóa. Một truy vấn chậm có thể làm cạn pool kết nối, yêu cầu xếp hàng, người dùng thử lại, và cuối cùng &amp;ldquo;thanh toán sập&amp;rdquo; dù chẳng có gì hỏng hẳn. Hai yêu cầu có thể cùng thấy còn một sản phẩm và cùng bán nó; transaction không tự giải quyết được, vì quy tắc nghiệp vụ phải được thực thi tường minh. Ngoài ra còn bản sao đọc bị trễ, timeout không cho biết lệnh ghi đã commit hay chưa, deadlock, và migration chạy khi nhiều phiên bản ứng dụng cùng hoạt động.&lt;/p&gt;
&lt;p&gt;Tác giả đề xuất bắt đầu từ bất biến cần giữ, như &amp;ldquo;không bán vượt tồn kho&amp;rdquo;, và thực thi nó ngay trong câu lệnh ghi, kèm ràng buộc trong cơ sở dữ liệu thay vì chỉ trong mã ứng dụng. Giữ transaction ngắn, không gọi mạng bên trong và chỉ thử lại lỗi tạm thời. Thao tác ghi cần idempotent, mỗi endpoint cần quy định mức độ &amp;ldquo;tươi&amp;rdquo; của dữ liệu đọc, migration nên chạy theo nhiều bước tương thích, và khôi phục phải được diễn tập trước. Sự cố của Atlassian năm 2022 và GitHub năm 2018 cho thấy khả năng khôi phục quan trọng không kém độ bền dữ liệu.&lt;/p&gt;
&lt;h2 id="beyond-happy-path-engineering-storage"&gt;&lt;a href="https://blog.gaborkoos.com/posts/2026-08-11-Beyond-Happy-Path-Engineering-Storage/" rel="noopener noreferrer"&gt;Beyond Happy Path Engineering: Storage&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Phần thứ tư của loạt bài bàn về object storage, thứ trông đơn giản hơn cơ sở dữ liệu nhưng ẩn chứa một cái bẫy: cơ sở dữ liệu và kho đối tượng là hai hệ thống riêng biệt, không có transaction chung. Ví dụ là tính năng tải tài liệu lên: ghi tệp vào kho, thêm bản ghi vào cơ sở dữ liệu, rồi trả về URL. Nếu tệp đã lưu mà bản ghi thất bại sẽ sinh ra đối tượng mồ côi; ngược lại, bản ghi tồn tại mà tệp không có thì mọi lượt tải đều trả về 404. Multipart upload bỏ dở vẫn âm thầm tốn tiền, presigned URL vẫn hợp lệ dù bản ghi đã bị xóa, hai lần ghi vào cùng một khóa thì lần sau đè lần trước, và CDN tiếp tục phục vụ tệp cũ. Độ bền dữ liệu cũng không đồng nghĩa với khả năng khôi phục, vì lệnh xóa nhầm cũng được lưu &amp;ldquo;bền vững&amp;rdquo; y như vậy.&lt;/p&gt;
&lt;p&gt;Tác giả đề xuất chọn thứ tự tải lên sao cho khi hỏng thì dễ sửa, theo dõi trạng thái tường minh (&lt;code&gt;pending&lt;/code&gt;, &lt;code&gt;confirmed&lt;/code&gt;, &lt;code&gt;failed&lt;/code&gt;…) và chỉ phục vụ nội dung đã xác nhận. Khóa đối tượng nên dùng UUID hoặc băm nội dung thay vì tên tệp; tệp lớn nên tải thẳng lên kho qua presigned URL ngắn hạn; việc xóa đi theo trình tự xóa mềm bản ghi rồi mới xóa đối tượng, kèm job đối soát định kỳ. Vụ Code Spaces năm 2014, công ty đóng cửa sau khi kẻ tấn công xóa sạch cả bản sao lưu, cho thấy bản sao lưu phải nằm ngoài phạm vi của cùng một thông tin xác thực.&lt;/p&gt;
&lt;h2 id="everyone-should-know-simd"&gt;&lt;a href="https://mitchellh.com/writing/everyone-should-know-simd" rel="noopener noreferrer"&gt;Everyone Should Know SIMD&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;SIMD mang tiếng phức tạp, và Mitchell Hashimoto gặp nhiều kỹ sư giỏi gạt nó đi vì nghĩ đó là thứ khó học hoặc chỉ dành cho phần mềm cần hiệu năng cực cao. Anh cho rằng nghĩ vậy là sai: phần lớn mã SIMD kiểu &amp;ldquo;xử lý N giá trị mỗi lần&amp;rdquo; đều theo cùng một khuôn, nắm được khuôn đó thì viết SIMD gần dễ như viết vòng lặp for. SIMD cho phép CPU xử lý nhiều giá trị bằng một chỉ thị, nên mỗi vòng lặp duyệt từng byte, từng ký tự hay từng phần tử đều là một cơ hội, miễn là dữ liệu đủ lớn, cỡ hàng nghìn tới hàng triệu byte.&lt;/p&gt;
&lt;p&gt;Khuôn chung gồm năm bước: phát tán (broadcast) hằng số và khởi tạo bộ tích lũy nếu cần; lặp qua đầu vào theo từng khối bằng độ rộng vector; so sánh hoặc tính toán song song trên mọi làn (lane); thu gọn hoặc lưu kết quả; và xử lý phần dư bằng chính vòng lặp vô hướng ban đầu. Ví dụ lấy từ Ghostty, dùng Zig: tìm điểm kết thúc của chuỗi ký tự in được bằng cách quét tới giá trị &lt;code&gt;0xF&lt;/code&gt; trở xuống. Bản vô hướng chỉ một dòng, bản vector thêm khoảng 12 dòng nhưng có thể nhanh gấp 4 lần với ARM NEON, 8 lần với AVX2 và 16 lần với AVX-512; đo thực tế trên máy Intel AVX2 được khoảng 5 lần. Trình biên dịch đôi khi tự vector hóa được, nên hãy kiểm tra trước. Nhưng khi vòng lặp đáng để quan tâm tới mức tăng tốc 5 lần, tác giả muốn việc vector hóa tường minh và dự đoán được chứ không lặng lẽ biến mất sau một lần nâng cấp trình biên dịch.&lt;/p&gt;
&lt;h2 id="htop-explained"&gt;&lt;a href="https://peteris.rocks/blog/htop/" rel="noopener noreferrer"&gt;htop explained&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Tác giả thừa nhận suốt một thời gian dài không hiểu hết các con số trên &lt;code&gt;htop&lt;/code&gt;, chẳng hạn từng nghĩ load average &lt;code&gt;1.0&lt;/code&gt; trên máy hai nhân nghĩa là CPU bận 50%. Vì vậy anh tra cứu và viết lại tất cả thành một bài rất dài, đi qua từng khu vực của màn hình. Điểm quý là anh luôn truy về nguồn dữ liệu thật: uptime đọc từ &lt;code&gt;/proc/uptime&lt;/code&gt;, load average đọc từ &lt;code&gt;/proc/loadavg&lt;/code&gt;, và cách tự tìm ra điều đó là dùng &lt;code&gt;strace&lt;/code&gt; để xem chương trình mở những tệp nào.&lt;/p&gt;
&lt;p&gt;Load average không phải phần trăm CPU mà là số tiến trình đang chạy hoặc chờ chạy, cộng cả tiến trình ngủ không thể ngắt đang chờ đĩa hay mạng. Nó cũng là trung bình suy giảm theo hàm mũ: giá trị một phút gồm khoảng 63% tải của phút vừa qua và 37% phần tích lũy từ lúc khởi động. Bài giải thích tiếp các trạng thái tiến trình (&lt;code&gt;R&lt;/code&gt; đang chạy, &lt;code&gt;S&lt;/code&gt; ngủ có thể ngắt, &lt;code&gt;D&lt;/code&gt; ngủ không thể ngắt, &lt;code&gt;Z&lt;/code&gt; zombie đã kết thúc nhưng tiến trình cha chưa thu hồi), độ &amp;ldquo;nice&amp;rdquo; từ -20 tới 19 với quan hệ &lt;code&gt;PR = 20 + NI&lt;/code&gt;, cùng các cột bộ nhớ: &lt;code&gt;VIRT&lt;/code&gt; gồm cả vùng đã xin mà chưa dùng nên ít hữu ích, &lt;code&gt;RES&lt;/code&gt; là phần nằm trong RAM thật nhưng có thể bị tính trùng do cơ chế sao chép khi ghi, còn &lt;code&gt;MEM%&lt;/code&gt; là &lt;code&gt;RES&lt;/code&gt; chia tổng RAM. Cuối bài, anh lần lượt xem từng tiến trình mặc định trên Ubuntu Server để quyết định cái nào có thể gỡ bỏ.&lt;/p&gt;
&lt;h2 id="the-productivity-experience-paradox"&gt;&lt;a href="https://annievella.com/posts/the-productivity-experience-paradox/" rel="noopener noreferrer"&gt;The Productivity-Experience Paradox&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Đây là bài thứ hai Annie Vella viết từ nghiên cứu thạc sĩ về tác động của AI lên kỹ sư. Nghiên cứu theo dõi cùng một nhóm kỹ sư ở hai thời điểm cách nhau sáu tháng (10/2024 tới 4/2025). Năng suất là tin vui ổn định: 84% nói AI giúp họ làm được nhiều hơn ở cả hai lần khảo sát. Nhưng trải nghiệm, đo bằng khung DevEx gồm vòng phản hồi, tải nhận thức và trạng thái dòng chảy (flow), lại đi xuống: nhóm có trải nghiệm tiêu cực tăng từ 14% lên 27%, và không ai rơi vào nhóm tiêu cực quay lại được mức hoàn toàn tích cực.&lt;/p&gt;
&lt;p&gt;Phát hiện trung tâm là hai thứ vốn được tin là đi cùng nhau đang tách rời: theo dõi cùng một người, trải nghiệm tụt mà năng suất không tụt, hệ số tương quan giữa thay đổi về flow và năng suất chỉ 0,02. Điều đó đáng lo vì lý do đầu tư vào trải nghiệm lập trình viên xưa nay là &amp;ldquo;trải nghiệm tốt thì năng suất sẽ theo sau&amp;rdquo;; khi sản lượng vẫn ổn, áp lực bị che giấu cho tới lúc thành kiệt sức. Flow giảm mạnh nhất (tỉ lệ đánh giá tệ hơn từ 7% lên 20%) trong khi vòng phản hồi lại cải thiện, và tác giả ngờ hai điều liên quan: mỗi câu trả lời nhanh của AI là một lần chuyển ngữ cảnh. Cô mượn khái niệm &amp;ldquo;junk flow&amp;rdquo; để mô tả cảm giác giống flow nhưng gây nghiện như máy đánh bạc, và kết luận rằng AI tuyệt vời cho lợi ích bên ngoài như sản lượng nhưng có thể bào mòn lợi ích nội tại là kỹ năng và niềm vui của nghề.&lt;/p&gt;
&lt;h2 id="prefactoring-clear-the-way-for-your-new-feature"&gt;&lt;a href="https://testing.googleblog.com/2026/07/prefactoring-clear-way-for-your-new.html" rel="noopener noreferrer"&gt;Prefactoring: Clear the Way for Your New Feature&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Bài viết ngắn từ loạt &amp;ldquo;Tech on the Toilet&amp;rdquo; của Google mở đầu bằng lời Kent Beck: &amp;ldquo;trước hết hãy làm cho thay đổi trở nên dễ, rồi mới thực hiện thay đổi dễ đó&amp;rdquo;. Khi làm tính năng mới trên mã nguồn vốn không được viết để đón thay đổi, cố nhồi thẳng vào sẽ rối rất nhanh: sửa chỗ này kéo theo chỗ kia, và bạn lún vào những tệp không hề định đụng tới. Prefactoring, viết tắt của &amp;ldquo;preparatory refactoring&amp;rdquo;, là thói quen sắp xếp lại mã hiện có cho hợp với thay đổi sắp tới &lt;em&gt;trước khi&lt;/em&gt; viết tính năng, thay vì dọn dẹp sau cùng.&lt;/p&gt;
&lt;p&gt;Lợi ích gồm: tính năng mới lắp vào tự nhiên hơn, rà soát nhanh hơn vì tái cấu trúc và tính năng nằm ở hai thay đổi riêng, ít lỗi hơn vì dọn dẹp được tách khỏi logic chức năng, và quay lui an toàn hơn vì thay đổi nhỏ dễ hoàn tác. Ví dụ minh họa: thay đổi đầu chỉ trích xuất hàm &lt;code&gt;get_display_name(user)&lt;/code&gt; để bỏ đoạn mã lặp ở trang hồ sơ và mẫu email; thay đổi thứ hai mới thêm hỗ trợ tên đệm bằng một dòng sửa trong hàm đó. Bạn cũng có thể tách phần dọn dẹp mà người rà soát gợi ý thành một thay đổi nền ngay khi thay đổi hiện tại đang được rà soát. Tuy vậy không phải việc dọn dẹp nào cũng cần làm trước: nếu nó không chặn tính năng thì để sau, hoặc gộp luôn nếu đủ nhỏ.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Bài viết đã được viết lại bởi Claude Code với Opus 5.5 vào ngày 27/09/2026.&lt;/em&gt;&lt;/p&gt;</description></item><item><title>Newsletter #131</title><link>https://miti99.com/post/2026/09/09/</link><pubDate>Wed, 09 Sep 2026 00:00:00 +0700</pubDate><guid>https://miti99.com/post/2026/09/09/</guid><description>&lt;p&gt;&lt;em&gt;Mời bạn thưởng thức Newsletter #131.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="the-codename-one-javascript-port-is-now-free-and-open-source"&gt;&lt;a href="https://dev.to/codenameone/the-codename-one-javascript-port-is-now-free-and-open-source-2f84" rel="noopener noreferrer"&gt;The Codename One JavaScript Port Is Now Free and Open Source&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Codename One vừa mở mã nguồn phần chuyển đổi sang JavaScript theo giấy phép GPLv2 kèm ngoại lệ Classpath. Trước đây đây là tính năng chỉ dành cho gói Enterprise, nay mọi gói dịch vụ đều dùng được, và bạn có thể dựng ứng dụng web ngay trên máy mình mà không cần tài khoản Codename One, chỉ bằng một lệnh Maven dùng bộ biên dịch ParparVM đi kèm. ParparVM cũng trở thành trình biên dịch mặc định thay cho TeaVM, kể cả với bản dựng trên đám mây; TeaVM vẫn có thể bật lại bằng tham số dựng để đối chiếu khi cần. Về kiến trúc, mã nguồn Java của ứng dụng chạy trong một Web Worker ở nền, còn phần giao tiếp với trình duyệt nằm trên luồng chính, nhờ vậy mã Java không thể vô tình phụ thuộc vào DOM hay các đối tượng toàn cục của trình duyệt.&lt;/p&gt;
&lt;p&gt;Bản phát hành còn có nhiều bổ sung cho framework. API lịch mới truy cập thống nhất lịch trên thiết bị, Google Calendar, Microsoft 365, CalDAV và tệp &lt;code&gt;.ics&lt;/code&gt;, hỗ trợ sự kiện lặp, đồng bộ tăng dần, hàng đợi thay đổi khi ngoại tuyến và xử lý xung đột. Bluetooth vào hẳn phần lõi với BLE, GATT, L2CAP, RFCOMM và Web Bluetooth. &lt;code&gt;EditField&lt;/code&gt;, &lt;code&gt;RichTextArea&lt;/code&gt; và &lt;code&gt;CodeEditor&lt;/code&gt; nay tự vẽ và soạn thảo văn bản thay vì đặt ô nhập liệu gốc đè lên, còn &lt;code&gt;RichTextComponent&lt;/code&gt; hiển thị được HTML, Markdown, AsciiDoc và RTF mà không cần web view. Lớp proxy hỗ trợ Jakarta Servlet, Node.js, PHP, AWS Lambda, Google Cloud Functions và Cloudflare Workers. ParparVM thêm chuỗi nén, giảm một nửa bộ nhớ cho văn bản ASCII, và Android API 36 sẽ thành mặc định từ tuần sau.&lt;/p&gt;
&lt;h2 id="hacking-in-the"&gt;&lt;a href="https://committing-crimes.com/articles/2026-08-04-java-nameof/" rel="noopener noreferrer"&gt;Hacking in the &lt;code&gt;nameOf&lt;/code&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Java không có biểu thức &lt;code&gt;nameof&lt;/code&gt; như C#, tức cách lấy tên của một định danh dưới dạng chuỗi. Thiếu sót này lộ rõ khi kiểm tra tham số: bạn phải viết cứng tên tham số vào thông điệp ngoại lệ kiểu &lt;code&gt;&amp;quot;name must not be blank&amp;quot;&lt;/code&gt;, và nếu đổi tên biến mà quên sửa chuỗi thì thông điệp sẽ sai mà trình biên dịch không hề báo. Bài viết thử tự dựng một hàm &lt;code&gt;nameOf&lt;/code&gt; cho Java, lấy được tên biến lúc chạy mà không cần công cụ chèn mã hay bộ xử lý annotation.&lt;/p&gt;
&lt;p&gt;Cách làm gồm ba bước. Đầu tiên, &lt;code&gt;StackWalker&lt;/code&gt; xác định khung gọi đã gọi &lt;code&gt;nameOf&lt;/code&gt;, cho biết lớp, tên phương thức, mô tả phương thức và vị trí bytecode. Tiếp theo, tệp &lt;code&gt;.class&lt;/code&gt; của lớp đó được đọc vào bộ nhớ và phân tích bằng ClassFile API thành một &lt;code&gt;ClassModel&lt;/code&gt;. Cuối cùng, tác giả mô phỏng ngăn xếp toán hạng của JVM: thay vì giá trị thật, mỗi ô chứa một giá trị tượng trưng như tên biến cục bộ hay tên trường, được lan truyền qua từng chỉ thị nạp, lưu, truy cập trường và gọi phương thức, cho tới khi gặp lời gọi &lt;code&gt;nameOf&lt;/code&gt; đúng vị trí thì lấy ra tên tượng trưng của đối số. Chính tác giả khuyên đừng dùng cách này: nó dễ vỡ khi xuất hiện opcode mới, phụ thuộc vào thông tin gỡ lỗi tùy chọn như cờ &lt;code&gt;-parameters&lt;/code&gt;, có thể hỏng với trình biên dịch khác và gần như chưa được kiểm thử. Đây là một lời giải phức tạp quá mức cho một vấn đề nhỏ, nhưng lại là bài học thú vị về bytecode và các API mới của Java.&lt;/p&gt;
&lt;h2 id="set-it-and-ship-it-how-i-let-ai-agents-build-my-java-services-while-i-sleep"&gt;&lt;a href="https://dev.to/sshenvi/set-it-and-ship-it-how-i-let-ai-agents-build-my-java-services-while-i-sleep-1jhj" rel="noopener noreferrer"&gt;Set It and Ship It: How I Let AI Agents Build My Java Services While I Sleep&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Sudarshan Shenvi cho rằng agent AI hoàn toàn có thể tự xây dựng dịch vụ Java đạt chất lượng sản phẩm trong những phiên chạy kéo dài nhiều giờ, với điều kiện công việc phải đi qua các cổng kiểm soát nghiêm ngặt; thiếu chúng, bạn chỉ nhận về những thứ nghe rất thuyết phục nhưng vô dụng. Có ba cổng: yêu cầu phải chấm được bằng máy, mỗi mục nêu một hành vi hay giới hạn hiệu năng cụ thể để đánh giá đạt hoặc trượt; một agent phản biện riêng kiểm tra toàn bộ kết quả theo từng mục, vì agent xây dựng không được tự duyệt bài của mình; và một hạ tầng luôn bật, tắt hộp thoại xin phép để phiên chạy năm tiếng không cần người trông, nhưng vẫn giữ quyền truy cập thu hẹp, môi trường cách ly và hạn mức chi tiêu.&lt;/p&gt;
&lt;p&gt;Ví dụ minh họa là một kỹ sư nhận phiếu công việc xây dịch vụ đối soát thanh toán. Thay vì viết mã nguồn, anh viết một bản hợp đồng yêu cầu dài mười lăm dòng, nêu rõ các điểm cuối REST, logic đối chiếu, thực thể JPA, độ trễ p95 dưới 500ms, độ phủ kiểm thử 100% và các ràng buộc bảo mật. Anh khởi chạy agent lúc 9 giờ 10 rồi rời đi; vòng phản biện phát hiện lỗi về bằng chứng độ trễ và cấu trúc gói, agent xây dựng tự sửa, và tới 11 giờ 35 anh nhận về bảng kiểm đạt đủ 15/15 mục với độ trễ đo được 410ms. Tác giả cảnh báo lượng token tăng khó lường nên cần giới hạn ngân sách, bắt đầu với nhánh riêng trong môi trường cách ly và luôn giữ bước duyệt của con người.&lt;/p&gt;
&lt;h2 id="software-abundance"&gt;&lt;a href="https://nader.substack.com/p/software-abundance" rel="noopener noreferrer"&gt;Software abundance&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Nader Dabit, kỹ sư với khoảng mười bốn năm kinh nghiệm và hiện làm việc tại Cognition, nói rằng anh chưa bao giờ hào hứng và lạc quan như lúc này, dù từng lo ngại về tác động của AI lên nghề lập trình. Anh nhận thấy thị trường việc làm đang phân hóa rõ rệt: những người chủ động áp dụng cách làm dựa trên AI và chuyển sang các mảng đang tăng trưởng nhờ AI thì có thu nhập và cơ hội tốt hơn; còn những người bám vào chuyên môn truyền thống như frontend hay backend, hoặc ở lại các công ty lớn chậm thay đổi, thì gặp khó khăn, thậm chí bị cắt giảm.&lt;/p&gt;
&lt;p&gt;Luận điểm trung tâm của bài là nghịch lý Jevons: khi năng lực làm ra phần mềm tăng, nhu cầu không giảm mà còn mở rộng. Các đội anh làm việc cùng hiện xuất xưởng số pull request gấp mười tới hai mươi lần trước, và agent chạy trên đám mây giúp cả những người không chuyên cũng triển khai được những việc trước đây cần kỹ sư giàu kinh nghiệm. Tốc độ tăng kéo theo lộ trình sản phẩm dài hơn và tiêu chuẩn chất lượng cao hơn. Theo tác giả, nghề xây dựng phần mềm không biến mất mà đang chuyển mình: AI tự động hóa phần lớn những việc lặp lại mà kỹ sư vốn không thích như sửa lỗi, nâng cấp phụ thuộc hay di trú hệ thống, để con người dồn sức cho óc phán đoán, sự tò mò, hiểu biết ngữ cảnh và gu thẩm mỹ, cũng như có thêm thời gian cho những thử nghiệm sáng tạo từng khiến họ đến với lập trình.&lt;/p&gt;
&lt;h2 id="the-development-pipeline-is-a-production-system"&gt;&lt;a href="https://sundry.jerryorr.com/2026/07/31/development-pipeline-is-a-production-system" rel="noopener noreferrer"&gt;The development pipeline is a production system&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Jerry Orr lập luận rằng quy trình phát triển phần mềm chính là một hệ thống sản xuất, nên mỗi khi nó hỏng, đội phát triển cần xử lý với mức khẩn cấp ngang một sự cố ảnh hưởng tới khách hàng. Khi mã nguồn không biên dịch được, lập trình viên không thể làm việc; khi máy chủ QA sập, người kiểm thử không thể xác nhận chất lượng sản phẩm. Với những người đó, đây đúng nghĩa là sự cố sản xuất, và bất kỳ thành phần nào cản trở chặng đường từ phát triển tới triển khai, từ hệ thống quản lý công việc, công cụ CI/CD đến bộ kiểm thử bị lỗi, đều là một sự cố nghiêm trọng.&lt;/p&gt;
&lt;p&gt;Tác giả so sánh với ngành chế tạo, nơi các nhà máy từ lâu đã có quy trình bài bản để giảm thời gian dây chuyền lắp ráp phải ngừng, trong khi các đội phần mềm lại thường xem nhẹ hạ tầng nội bộ của chính mình. Lời khuyên là xác định và ưu tiên mọi thành phần thiết yếu của quy trình phát triển: nền tảng quản lý công việc và thay đổi, công cụ cho lập trình viên như IDE và hệ thống dựng, kho gói và hạ tầng cục bộ, công cụ CI/CD, môi trường QA và bộ kiểm thử. Đội ngũ nên áp dụng cách quản lý sự cố có hệ thống vốn quen thuộc trong quản trị dịch vụ CNTT và sản xuất cho chính hạ tầng phát triển, vì một quy trình phát triển hỏng trực tiếp ngăn phần mềm tới tay khách hàng.&lt;/p&gt;
&lt;h2 id="how-llms-figure-out-what-you-mean---no-math-degree-required"&gt;&lt;a href="https://zarar.dev/how-ai-figures-out-what-you-mean-no-math-required/" rel="noopener noreferrer"&gt;How LLMs Figure Out What You Mean - No Math Degree Required&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Bài viết giải thích cách một mô hình ngôn ngữ lớn hiểu ý nghĩa của câu mà không cần kiến thức toán, dùng câu hỏi &amp;ldquo;Do lions roar?&amp;rdquo; làm ví dụ xuyên suốt. Bước đầu tiên là biến chữ thành số: văn bản được cắt thành các token, mỗi token có một mã số. Nhưng một con số đơn lẻ không mang ý nghĩa, vì số 2 chẳng có lý do gì để &amp;ldquo;giống&amp;rdquo; số 3, nên mỗi token được chuyển thành một vector, tức một dãy số mà các vị trí cùng nhau nắm bắt ý nghĩa của từ, và các từ gần nghĩa sẽ có vector gần giống nhau. Có thể hình dung vector như mũi tên: từ gần nghĩa chỉ cùng hướng, từ trái nghĩa chỉ ngược hướng, từ không liên quan tạo góc vuông.&lt;/p&gt;
&lt;p&gt;Để đo độ giống nhau, mô hình dùng tích vô hướng, được minh họa bằng hình ảnh hai người cùng đẩy một chiếc xe đẩy hàng: đẩy cùng hướng thì lực cộng dồn và điểm cao, đẩy chéo góc thì hiệu quả giảm, còn đẩy ngược chiều thì triệt tiêu và cho điểm âm. Điểm thô chỉ có nghĩa khi so với các điểm khác, nên mô hình dùng hàm softmax để đổi chúng thành tỉ lệ phần trăm; tác giả ví mỗi từ có đúng một đô la sự chú ý để chia thành các lát bánh pizza cho những từ khác tùy mức độ gần gũi. Nhờ vậy, trong câu ví dụ, &amp;ldquo;roar&amp;rdquo; dồn phần lớn sự chú ý cho &amp;ldquo;lions&amp;rdquo; chứ không phải &amp;ldquo;Do&amp;rdquo;. Các vector ban đầu hoàn toàn ngẫu nhiên và chỉ dần được chỉnh đúng qua quá trình huấn luyện bằng lan truyền ngược trên khối lượng văn bản khổng lồ.&lt;/p&gt;
&lt;h2 id="meet-alice-alice-is-impatient"&gt;&lt;a href="https://brooker.co.za/blog/2026/06/19/waiting.html" rel="noopener noreferrer"&gt;Meet Alice. Alice is impatient.&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Marc Brooker mở đầu bằng hai tình huống quen thuộc với người vận hành dịch vụ. Bạn đo được độ trễ trung bình 100ms, nhưng khách hàng Alice nói cô phải chờ trung bình một giây. Bạn báo cáo thời gian khôi phục trung bình dưới một phút, nhưng khách hàng Alex lại thấy mỗi lần sự cố kéo dài khoảng một tiếng. Theo tác giả, cả hai bên đều đúng: người vận hành đếm theo sự kiện, còn khách hàng trải nghiệm theo thời gian. Đây là nghịch lý kiểm tra: sự cố hay phản hồi chậm càng kéo dài thì càng bị nhiều khách hàng, hoặc nhiều lần thử lại, bắt gặp, nên thứ khách hàng cảm nhận là phiên bản phân phối đã được đánh trọng số theo độ dài.&lt;/p&gt;
&lt;p&gt;Về mặt toán học, nếu người vận hành đo được giá trị trung bình &lt;code&gt;E[X]&lt;/code&gt;, thì khách hàng trải nghiệm &lt;code&gt;E[X²] / (2E[X])&lt;/code&gt;, có thể viết lại thành một nửa của &lt;code&gt;E[X] + Var(X)/E[X]&lt;/code&gt;. Như vậy khoảng cách giữa hai góc nhìn tỉ lệ với phương sai chia cho trung bình, và phương sai lớn sẽ đẩy thời gian chờ cảm nhận lên rất cao. Tác giả kèm công cụ và đoạn mã Python mô phỏng: với trung vị 30 phút và p99 600 phút, khách hàng cảm nhận thời gian khôi phục khoảng 6 tiếng, trong khi MTTR đo được chỉ một tiếng. Hệ thống thực thường có phân phối đuôi nặng, nên phép đo cắt bớt ngoại lai sẽ che mất phần đuôi vốn quyết định trải nghiệm. Sự cố ngắn đã có cách xử lý tốt, còn sự cố kéo dài thì cả ngành chưa giải được, và đó mới là chỗ nên đầu tư.&lt;/p&gt;
&lt;h2 id="harness-engineering-deep-dive-where-the-term-came-from-and-how-to-actually-build-one"&gt;&lt;a href="https://lukasniessen.medium.com/harness-engineering-deep-dive-where-the-term-came-from-and-how-to-actually-build-one-a955bcdf5cce" rel="noopener noreferrer"&gt;Harness Engineering Deep Dive: Where The Term Came From, And How To Actually Build One&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Bài viết của Lukas Niessen bắt đầu từ công thức đã thành chuẩn: agent bằng mô hình cộng harness. Harness là mọi thứ còn lại: vòng lặp, công cụ, môi trường cách ly, lắp ghép ngữ cảnh, phân quyền, kiểm thử, trạng thái bền vững, vết chạy, hạn mức chi phí và cổng phê duyệt. Mô hình là thứ bạn đi thuê, còn harness là thứ bạn hoàn toàn sở hữu, nên khi agent thất bại, thay vì chờ mô hình tốt hơn, bạn có thể sửa môi trường để lỗi đó không thể xảy ra về mặt cấu trúc. Thuật ngữ được định hình đầu năm 2026 qua các bài của Mitchell Hashimoto, Birgitta Böckeler và một báo cáo của OpenAI, như bước tiếp theo sau prompt engineering và context engineering. Ranh giới giữa wrapper và harness nằm ở vòng lặp khép kín: khi đầu ra của mô hình trở thành quyết định được hệ thống thực thi, rồi mô hình quan sát kết quả và quyết định tiếp, mọi mối lo của harness bắt đầu có hiệu lực.&lt;/p&gt;
&lt;p&gt;Phần kỹ thuật đi qua bảy vấn đề của vòng lặp ngây thơ: cảm biến phải chắt lọc tín hiệu thay vì đổ nguyên đầu ra vào ngữ cảnh; lịch sử hội thoại chỉ là bộ nhớ đệm, còn sổ cái mới là sự thật; agent không được tự quyết định là đã xong; thu hẹp không gian hành động bằng lược đồ công cụ thay vì viết lời nhắc dài hơn; hạn mức kèm phát hiện đình trệ; cổng công cụ phân tầng theo rủi ro; và coi vết chạy là đơn vị gỡ lỗi. Theo tác giả, chất lượng harness có thể làm lượng token tiêu thụ chênh nhau 10–50 lần.&lt;/p&gt;
&lt;h2 id="how-big-is-a-git-commit"&gt;&lt;a href="https://ratfactor.com/cards/git-commit-size" rel="noopener noreferrer"&gt;How big is a Git commit?&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Một commit của Git thực sự chiếm bao nhiêu dung lượng? Để trả lời, Dave Gauer đo kích thước thư mục &lt;code&gt;.git&lt;/code&gt; bằng &lt;code&gt;du -sb&lt;/code&gt; qua nhiều kịch bản, chỉ dùng đối tượng rời và không dùng packfile để thấy rõ cách lưu trữ cốt lõi. Một kho mới khởi tạo tốn khoảng 64 KB; commit 250 tệp nhỏ trong 5 thư mục tốn thêm chừng 47 KB; một thay đổi nhỏ trong một tệp tốn 17 KB, trong khi ở kho chỉ có một tệp thì cùng loại thay đổi ba byte chỉ cần khoảng 8,7 KB.&lt;/p&gt;
&lt;p&gt;Nguyên nhân nằm ở cấu trúc đối tượng: khi commit thay đổi của một tệp, Git tạo bốn đối tượng gồm chính commit, một cây cho thư mục chứa tệp liệt kê mọi tệp trong đó, nội dung tệp và cây thư mục gốc, tất cả được nén bằng zlib. Vì vậy dù thay đổi rất nhỏ, phần phụ trội vẫn tính bằng kilobyte và tăng theo độ phức tạp của cây thư mục. Với tệp lớn, khả năng nén rất ấn tượng: tệp nhị phân khoảng 580 KB được lưu thành 298 KB, còn tệp mã nguồn 16,4 MB chỉ còn 1,6 MB, tức khoảng 10% kích thước gốc. Nhìn chung, với commit lớn gồm ít tệp, dữ liệu trong &lt;code&gt;.git&lt;/code&gt; chỉ bằng 50% tới 10% kích thước tệp chưa nén, tùy khả năng nén của dữ liệu. Tác giả kết luận Git lưu trữ rất hiệu quả, nhất là với văn bản, và packfile còn tiết kiệm hơn nữa nhờ loại bỏ phần trùng lặp giữa các đối tượng.&lt;/p&gt;
&lt;h2 id="how-i-find-problems-to-solve-as-a-staff-engineer"&gt;&lt;a href="https://lalitm.com/post/find-problems-staff-engineer/" rel="noopener noreferrer"&gt;How I Find Problems to Solve as a Staff Engineer&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Lalit Maganti chia sẻ cách anh tìm ra những bài toán đáng giải khi làm kỹ sư staff. Thay vì chặn lịch để &amp;ldquo;suy nghĩ chiến lược&amp;rdquo;, anh hành xử như một miếng bọt biển: lắng nghe dòng tiếng ồn hằng ngày trong các cuộc họp, kênh chat và email, thấm dần những vấn đề mọi người đang gặp. Khi điều gì đó chạm tới mảng của mình, anh đào sâu tìm vấn đề thực sự thay vì chấp nhận yêu cầu bề mặt, có khi ngồi cùng các đội xem họ làm việc hoặc tự tay thử gỡ lỗi để phân biệt nhu cầu thật với giải pháp người ta đề xuất. Anh cũng cố ý không lao vào xây dựng ngay sau yêu cầu đầu tiên, vì đội hăng hái nhất chưa chắc đại diện cho ưu tiên của tổ chức; khi cùng một vấn đề xuất hiện độc lập ở nhiều đội, đó mới là tín hiệu mạnh.&lt;/p&gt;
&lt;p&gt;Những buổi đi bộ giúp anh nhận ra mối liên hệ giữa các vấn đề tích lũy được. Với Perfetto, trong hai năm các đội xin nhiều tính năng giao diện khác nhau như ghim track, mức phóng tùy chỉnh hay tổng hợp theo ý riêng, và cuối cùng anh nhận ra nhu cầu thật là khả năng mở rộng giao diện. Trước khi cam kết nguồn lực, anh kiểm chứng ý tưởng bằng bản mẫu dùng xong bỏ và xin ý kiến quản lý cùng các đội liên quan. Mỗi lần giải đúng vấn đề, anh được mời vào thảo luận sớm hơn và có ảnh hưởng tới lộ trình mà không cần tự làm mọi thứ. Anh lưu ý cách này hợp nhất với môi trường tự chủ từ dưới lên.&lt;/p&gt;
&lt;h2 id="golang-maps-how-swiss-tables-replaced-the-old-bucket-design"&gt;&lt;a href="https://blog.gaborkoos.com/posts/2026-07-24-Golang-Maps-How-Swiss-Tables-Replaced-the-Old-Bucket-Design/" rel="noopener noreferrer"&gt;Golang Maps: How Swiss Tables Replaced the Old Bucket Design&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Map nằm trên đường đi nóng của gần như mọi chương trình Go, và Go 1.24 đã thay thiết kế bên trong của nó bằng Swiss Table. Thiết kế cũ dùng một mảng bucket, mỗi bucket chứa tối đa 8 cặp khóa–giá trị kèm byte siêu dữ liệu; khi bucket đầy, bộ chạy cấp phát một bucket tràn và nối chuỗi vào. Cách này ổn định, nhưng chuỗi tràn buộc chương trình đuổi theo con trỏ qua các vùng bộ nhớ không liền nhau, gây trượt bộ nhớ đệm, và hệ số tải chỉ đạt khoảng 81% trước khi chi phí va chạm trở nên đáng kể.&lt;/p&gt;
&lt;p&gt;Swiss Table tách giá trị băm làm hai: một phần quyết định vị trí, phần còn lại là vân tay ngắn lưu trong siêu dữ liệu. Dữ liệu được xếp thành các nhóm 8 ô gồm byte điều khiển, ô khóa và ô giá trị; khi tra cứu, chương trình quét byte điều khiển trước, chỉ so sánh khóa đầy đủ ở những ô có vân tay khớp, và dừng ngay khi gặp ô trống. Phần lớn ô bị loại ở bước siêu dữ liệu rẻ này, đường dò phẳng giúp tận dụng bộ nhớ đệm tốt hơn, và hệ số tải tăng lên khoảng 87,5%. Thay vì một bảng lớn phải băm lại toàn bộ khi mở rộng, Go dùng nhiều bảng nhỏ với một lớp thư mục định tuyến để việc mở rộng diễn ra cục bộ và tăng dần, vẫn giữ ngữ nghĩa của &lt;code&gt;range&lt;/code&gt;. Thời gian chạy tổng thể giảm khoảng 2–3%, còn thao tác trên map lớn nhanh hơn 30–35%. Đổi lại, mẫu xóa nhiều để lại bia mộ làm đường dò dài ra cho tới khi được dọn.&lt;/p&gt;
&lt;h2 id="the-new-rules-of-context-engineering-for-claude-5-models"&gt;&lt;a href="https://x.com/trq212/status/2080710971228918066" rel="noopener noreferrer"&gt;The new rules of context engineering for Claude 5 models&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Khi bạn gửi tin nhắn cho Claude, lời nhắc chỉ là một phần nhỏ trong ngữ cảnh mà nó nhận được; phần lớn được lắp ghép từ system prompt, skill, tệp CLAUDE.md, bộ nhớ và nhiều nguồn khác, và việc thiết kế phần đó gọi là context engineering. Trong bài viết này, một kỹ sư Anthropic cho biết họ đã xóa hơn 80% system prompt của Claude Code cho các mô hình như Claude Opus 5 và Claude Fable 5 mà không làm giảm điểm trên các bài đánh giá lập trình. Lý do là trước đây họ ràng buộc Claude quá mức: trong cùng một yêu cầu, system prompt, skill và lời nhắc của người dùng có thể mâu thuẫn, chẳng hạn &amp;ldquo;viết tài liệu khi phù hợp&amp;rdquo; bên cạnh &amp;ldquo;KHÔNG thêm chú thích&amp;rdquo;, khiến mô hình phải cân nhắc thêm, trong khi mô hình mới đủ khả năng tự phán đoán.&lt;/p&gt;
&lt;p&gt;Bài viết đối chiếu các thói quen cũ với cách làm mới. Thay vì đặt luật cứng, hãy để Claude tự phán đoán; chỉ dẫn mới chỉ nói &amp;ldquo;viết mã nguồn giống mã nguồn xung quanh&amp;rdquo;. Thay vì đưa ví dụ dùng công cụ, vốn bó hẹp không gian khám phá, hãy thiết kế tham số công cụ biểu đạt hơn. Thay vì dồn mọi thứ lên đầu, hãy tiết lộ dần: phần kiểm chứng và rà soát mã nguồn được tách thành skill riêng, một số công cụ chỉ được nạp khi cần. Bộ nhớ nay được lưu tự động, và tài liệu tham chiếu có thể là hiện vật HTML hay bộ kiểm thử thay vì tệp markdown đơn giản. Hãy giữ CLAUDE.md gọn, dành phần lớn cho những điểm dễ vấp trong kho mã nguồn thay vì những gì Claude tự nhìn ra được.&lt;/p&gt;
&lt;h2 id="the-git-history-command-deserves-more-attention"&gt;&lt;a href="https://lalitm.com/post/git-history/" rel="noopener noreferrer"&gt;The git history command deserves more attention&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Làm nhiều thay đổi song song trong Git thường phải nhảy qua lại giữa các nhánh và rebase đầy rủi ro, nên &lt;code&gt;jj&lt;/code&gt; (Jujutsu) được nhắc tới nhiều như một lựa chọn thay thế. Lalit Maganti cho rằng lệnh thử nghiệm &lt;code&gt;git history&lt;/code&gt;, ra mắt qua Git 2.54 (tháng 4/2026, với &lt;code&gt;reword&lt;/code&gt; và &lt;code&gt;split&lt;/code&gt;) và Git 2.55 (tháng 6/2026, với &lt;code&gt;fixup&lt;/code&gt;), mang lại nhiều lợi ích tương tự mà không cần đổi cả quy trình, nhưng lại ít được bàn tới. Với &lt;code&gt;fixup&lt;/code&gt;, bạn đưa thay đổi vào vùng chờ bằng &lt;code&gt;git add&lt;/code&gt; rồi chạy &lt;code&gt;git history fixup &amp;lt;commit&amp;gt;&lt;/code&gt; để gộp chúng vào một commit cũ; mọi nhánh cục bộ nằm dưới commit đó đều được rebase theo, vượt xa &lt;code&gt;git rebase --update-refs&lt;/code&gt;. Lệnh &lt;code&gt;reword&lt;/code&gt; mở trình soạn thảo để sửa thông điệp của một commit cũ rồi dựng lại cả chồng commit phía trên cùng các nhánh, còn &lt;code&gt;split&lt;/code&gt; chia một commit thành hai bằng cách chọn từng khối thay đổi theo kiểu tương tác.&lt;/p&gt;
&lt;p&gt;Điểm chung của cả ba lệnh là tính nguyên tử: chúng không bao giờ để kho ở trạng thái hỏng dở dang, vì sẽ từ chối mọi thao tác có thể sinh xung đột. Đó cũng là giới hạn lớn nhất so với &lt;code&gt;jj&lt;/code&gt;, vốn coi xung đột là đối tượng hạng nhất và cho phép giải quyết sau; ngoài ra &lt;code&gt;fixup&lt;/code&gt; không làm việc được khi có commit gộp, và Git chưa có nhật ký thao tác để hoàn tác dễ dàng. Tài liệu của Git cho biết giới hạn về xung đột có thể được gỡ bỏ trong tương lai. Dù vậy, đây vẫn là bước tiến đáng kể vì nó đưa những lợi ích khiến người ta tìm đến công cụ khác vào thẳng bản phân phối chuẩn của Git.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Bài viết đã được viết lại bởi Claude Code với Opus 5.5 vào ngày 27/09/2026.&lt;/em&gt;&lt;/p&gt;</description></item><item><title>Newsletter #130</title><link>https://miti99.com/post/2026/09/08/</link><pubDate>Tue, 08 Sep 2026 00:00:00 +0700</pubDate><guid>https://miti99.com/post/2026/09/08/</guid><description>&lt;p&gt;&lt;em&gt;Mời bạn thưởng thức Newsletter #130.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="how-anthropic-runs-large-scale-code-migrations-with-claude-code"&gt;&lt;a href="https://claude.com/blog/ai-code-migration" rel="noopener noreferrer"&gt;How Anthropic runs large-scale code migrations with Claude Code&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Chuyển một dự án lớn sang ngôn ngữ khác từng là việc kéo dài nhiều năm, nên thường chỉ được làm khi không còn lựa chọn nào khác. Anthropic cho thấy bài toán này đã đổi khác: Bun chuyển một triệu dòng mã nguồn từ Zig sang Rust trong chưa đầy hai tuần, tiêu tốn khoảng 5,9 tỷ token đầu vào và 690 triệu token đầu ra (tương đương 165.000 đô theo giá API). Đổi lại, toàn bộ bộ kiểm thử hiện có đều vượt qua trước khi hợp nhất, tệp nhị phân nhỏ hơn 19%, hiệu năng tăng 2–5%, và một phép đo bộ nhớ giảm từ 6.745MB xuống 609MB. Một ví dụ khác là 165.000 dòng Python được chuyển sang TypeScript chỉ trong một cuối tuần.&lt;/p&gt;
&lt;p&gt;Quy trình gồm sáu bước: viết bộ quy tắc dịch kèm bản đồ phụ thuộc, thử quy tắc trên nhóm tệp mẫu, dịch toàn bộ bằng các agent chạy song song, chạy vòng lặp biên dịch với agent sửa lỗi và agent phản biện, kiểm thử nhanh để bắt lỗi lúc chạy, rồi đối chiếu hành vi với mã gốc. Bài học cốt lõi là không sửa mã nguồn mà sửa vòng lặp sinh ra nó: lỗi lặp lại thì cập nhật bộ quy tắc rồi sinh lại, thay vì vá tay từng tệp. Muốn vậy cần có trước một &amp;ldquo;trọng tài&amp;rdquo; khách quan như trình biên dịch hay bộ kiểm thử, và hàng đợi công việc có thể tiếp tục lại được. Chi phí cũng nên phân bổ có chủ đích: mô hình nhỏ như Sonnet dịch số lượng lớn, mô hình lớn như Fable hay Opus viết quy tắc và rà soát.&lt;/p&gt;
&lt;h2 id="the-vfs"&gt;&lt;a href="https://internals-for-interns.com/posts/linux-kernel-vfs/" rel="noopener noreferrer"&gt;The VFS&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Bạn đọc một tệp trên ext4, một tệp trong &lt;code&gt;/proc&lt;/code&gt; chỉ tồn tại trong bộ nhớ nhân, hay một tệp nằm trên máy khác qua NFS — tất cả đều dùng chung những lời gọi hệ thống và cùng ngữ nghĩa file descriptor. Lớp làm nên điều đó là VFS (Virtual File System), nằm giữa tầng syscall và các hệ thống tệp thật. Bài viết giải thích VFS như một ứng dụng của nguyên tắc &amp;ldquo;lập trình theo giao diện, không theo hiện thực&amp;rdquo;, mà trong C thì giao diện chỉ là struct chứa con trỏ hàm. Có bốn đối tượng chính: superblock đại diện cho một hệ thống tệp đã gắn kết, inode là một đối tượng bên trong nó (tệp, thư mục, liên kết), dentry gắn một cái tên với inode — vì bản thân inode không biết tên của mình — và file là một lần mở cụ thể của tiến trình, giữ vị trí đọc riêng. Các bảng &lt;code&gt;super_operations&lt;/code&gt;, &lt;code&gt;inode_operations&lt;/code&gt; và &lt;code&gt;file_operations&lt;/code&gt; chứa hành vi riêng của từng hệ thống tệp; mục nào không cài đặt thì VFS dùng hiện thực mặc định.&lt;/p&gt;
&lt;p&gt;Phần lớn công việc thực ra do mã dùng chung của VFS đảm nhiệm. Khi phân giải một đường dẫn như &lt;code&gt;/etc/hostname&lt;/code&gt;, nhân đi qua từng thành phần và chỉ gọi &lt;code&gt;lookup&lt;/code&gt; của hệ thống tệp khi dcache chưa có câu trả lời. Bộ đệm này nhớ cả những tên không tồn tại (negative dentry) và được đọc mà không cần khóa nhờ RCU. Việc đọc dữ liệu cũng tương tự: hầu hết chỉ là sao chép byte từ page cache, không chạm tới đĩa. Còn file descriptor chỉ là chỉ số trong mảng &lt;code&gt;struct file *&lt;/code&gt; của mỗi tiến trình.&lt;/p&gt;
&lt;h2 id="text-editor-data-structures"&gt;&lt;a href="https://cdacamar.github.io/data%20structures/algorithms/benchmarking/text%20editors/c&amp;#43;&amp;#43;/editor-data-structures/" rel="noopener noreferrer"&gt;Text Editor Data Structures&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Khi tự viết một trình soạn thảo văn bản, câu hỏi đầu tiên là lưu nội dung tệp trong bộ nhớ ra sao. Cameron DaCamara bắt đầu bằng việc liệt kê ràng buộc: chèn và xóa hiệu quả, undo/redo nhanh, hỗ trợ UTF-8 và chỉnh sửa nhiều con trỏ cùng lúc. Từ đó ông lần lượt loại các phương án. Một chuỗi lớn duy nhất thì gọn và dễ hiển thị, nhưng chậm với tệp trên 1MB, việc cấp phát lại có thể biến thao tác O(n) thành O(n²), và undo/redo rất tốn bộ nhớ. Gap buffer mà Emacs dùng thì đơn giản nhưng không hợp với nhiều con trỏ. Rope cho thao tác O(lg n) nhưng cơ chế sao chép khi ghi làm bộ nhớ phình to. Piece table truyền thống, từng dùng trong Microsoft Word, lại gặp nút thắt ở mảng liên tục trong các phiên làm việc dài. Piece tree của VSCode kết hợp ưu điểm của rope và piece table nhờ cây đỏ-đen.&lt;/p&gt;
&lt;p&gt;Tác giả chọn hướng đó nhưng làm thành piece tree bất biến, gọi là fredbuf. Cây đỏ-đen thuần hàm không cần con trỏ cha và cho phép chụp lại trạng thái bất cứ lúc nào, nên undo/redo chỉ còn là hai danh sách liên kết lưu các ảnh chụp cây. Việc xử lý CRLF được tách ra ngoài cây, và một lớp TreeWalker giúp duyệt ký tự và kiểm tra tính đúng đắn. Hai bài học rút ra: phân tích ràng buộc từ đầu giúp tránh công sức thừa, và công cụ gỡ lỗi như trực quan hóa cây là thứ giúp dự án về đích, nhất là với phép xóa trên cây đỏ-đen bất biến vốn rất khó. Thư viện dùng giấy phép MIT, chỉ cần C++20.&lt;/p&gt;
&lt;h2 id="scaling-to-1-million-concurrent-sandboxes-in-seconds"&gt;&lt;a href="https://modal.com/blog/scaling-to-1-million-concurrent-sandboxes-in-seconds" rel="noopener noreferrer"&gt;Scaling to 1 million concurrent sandboxes in seconds&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Modal viết lại toàn bộ nền tảng sandbox và chứng minh kết quả bằng một con số ấn tượng: một triệu sandbox được tạo trong chưa đầy 60 giây. Hệ thống cũ, giống Kubernetes, dựa vào tính nhất quán mạnh trên toàn backend: mọi quyết định cần điều phối toàn cục, và số lần ghi cơ sở dữ liệu tăng theo số sandbox, nên không thể mở rộng theo chiều ngang khi nhu cầu lên tới hàng triệu sandbox chạy đồng thời và hàng chục nghìn sandbox được tạo mỗi giây.&lt;/p&gt;
&lt;p&gt;Cách làm mới là phân tán hóa. Thay vì một bộ lập lịch tuần tự ở trung tâm, nhiều máy chủ lập lịch chạy song song với thuật toán cân bằng tải trên dữ liệu trong bộ nhớ. Mỗi worker trở thành nguồn sự thật của chính nó, định kỳ công bố trạng thái lên Redis thay vì ghi vào kho dữ liệu tập trung, và Postgres bị loại hẳn khỏi đường tạo sandbox. Các lời gọi RPC từng tăng theo số sandbox được gộp thành lời gọi theo lô. Nhờ vậy, mỗi lần tạo sandbox chỉ cần hai chặng mạng và một thao tác CPU, không cần điều phối trung tâm, với thời gian khởi động trung vị dưới 500 mili-giây và độ trễ lập lịch chỉ vài chục mili-giây. Công việc kéo dài nhiều tháng, bắt đầu bằng đợt làm bản mẫu dồn dập của bốn kỹ sư, sau đó hiện thực lại mọi tính năng, hệ thống giám sát và môi trường chạy container — trong quá trình đó còn phát hiện và xử lý cả tranh chấp khóa &lt;code&gt;rtnl&lt;/code&gt; trong nhân Linux khi khởi tạo hàng loạt container.&lt;/p&gt;
&lt;h2 id="making-768-servers-look-like-1"&gt;&lt;a href="https://planetscale.com/blog/making-768-servers-look-like-1" rel="noopener noreferrer"&gt;Making 768 servers look like 1&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Khi ứng dụng phải xử lý hàng triệu truy vấn mỗi giây trên dữ liệu cỡ petabyte, một máy chủ cơ sở dữ liệu không còn đủ. PlanetScale chỉ ra ba giới hạn của cách làm truyền thống: thao tác ghi luôn dồn về một máy vì nhật ký ghi trước (WAL) là nút thắt dù có bao nhiêu bản sao; bản sao chỉ nhân đôi dữ liệu chứ không chia nhỏ nó; và sao lưu một cơ sở dữ liệu khổng lồ có thể mất hàng giờ tới hàng ngày. Lời giải là chia mảnh (sharding) để phân tán cả dữ liệu lẫn truy vấn ra nhiều máy chủ chính. Ví dụ trong bài: một petabyte chia thành 256 mảnh, mỗi mảnh khoảng 4TB gồm một máy chính và hai bản sao, tổng cộng 768 máy chủ.&lt;/p&gt;
&lt;p&gt;Để lập trình viên không phải gánh sự phức tạp đó, một tầng proxy làm bộ định tuyến đứng giữa ứng dụng và các mảnh — Vitess cho MySQL và Neki cho Postgres. Khác với các công cụ gộp kết nối như PgBouncer, bộ định tuyến này phải phân tích câu lệnh SQL, hiểu cách dữ liệu phân bố, lập kế hoạch gửi truy vấn tới đúng mảnh và gộp kết quả lại. Cấu trúc phân bố được khai báo bằng JSON (VSchema), chẳng hạn bảng &lt;code&gt;user&lt;/code&gt; chia mảnh theo giá trị băm của cột &lt;code&gt;id&lt;/code&gt;. Cuối cùng, ứng dụng chỉ kết nối tới một địa chỉ như &lt;code&gt;mydb.pscale.com&lt;/code&gt;: DNS trỏ tới bộ cân bằng tải mạng, bộ này phân phối kết nối qua nhiều bộ định tuyến, và ứng dụng không hề biết phía sau có 768 máy chủ.&lt;/p&gt;
&lt;h2 id="branchavoidant-programming"&gt;&lt;a href="https://easylang.online/blog/branchless" rel="noopener noreferrer"&gt;Branch‑Avoidant Programming&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;CPU hiện đại đoán trước nhánh nào sẽ được chọn để giữ đường ống lệnh luôn đầy; khi đoán sai, nó phải xả đường ống và làm lại, cái giá không hề nhỏ. Bài viết minh họa bằng bài toán lọc những số nhỏ hơn 500 từ một mảng ngẫu nhiên — trường hợp bộ dự đoán nhánh gần như không thể đoán đúng. Mẹo là bỏ câu lệnh &lt;code&gt;if&lt;/code&gt;: luôn ghi giá trị vào mảng đích, rồi chỉ tăng chỉ số khi điều kiện đúng bằng &lt;code&gt;smlen += (numbers[i] &amp;lt; 500);&lt;/code&gt;. Có ghi thừa, nhưng rẻ hơn nhiều so với đoán sai nhánh. Với 100.000 số lặp 1.000 lần, trên Apple M1 bản dùng &lt;code&gt;if&lt;/code&gt; mất 0,345 giây còn bản không nhánh chỉ 0,036 giây, nhanh gần 10 lần; trên Intel Xeon là 0,576 so với 0,110 giây. Mã máy cho thấy lý do: lệnh nhảy &lt;code&gt;b.gt&lt;/code&gt; (ARM) hay &lt;code&gt;jg&lt;/code&gt; (x86) được thay bằng lệnh có điều kiện &lt;code&gt;cinc&lt;/code&gt; hay &lt;code&gt;setle&lt;/code&gt;, giữ luồng điều khiển thẳng tắp.&lt;/p&gt;
&lt;p&gt;Trình biên dịch không tự làm điều này vì không biết đặc điểm dữ liệu của bạn, và cũng không thể giả định rằng một phép ghi vốn nằm trong điều kiện là an toàn khi thực hiện vô điều kiện. Vì vậy lập trình viên phải chủ động áp dụng. Tác giả gợi ý quicksort là ứng viên rất phù hợp, vì bước phân hoạch về bản chất là di chuyển dữ liệu có điều kiện quanh phần tử chốt.&lt;/p&gt;
&lt;h2 id="your-redis-leaderboard-is-probably-breaking-ties-wrong"&gt;&lt;a href="https://dev.to/trungdlp/your-redis-leaderboard-is-probably-breaking-ties-wrong-39k4" rel="noopener noreferrer"&gt;Your Redis Leaderboard Is Probably Breaking Ties Wrong&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Bảng xếp hạng dùng sorted set của Redis là bài toán quen thuộc, nhưng ít ai để ý trường hợp hai người chơi bằng điểm. Khi đó Redis xếp theo thứ tự từ điển của tên thành viên, tức ID người chơi quyết định ai đứng trên — thứ tự luôn xác định nhưng chẳng có ý nghĩa gì với người chơi. Các cách sửa nhanh đều có vấn đề: dấu thời gian bị trùng trong cùng mili-giây và lệch đồng hồ giữa các máy, còn nhồi hai giá trị vào một số thực kiểu double lại thu hẹp khoảng giá trị an toàn của cả hai.&lt;/p&gt;
&lt;p&gt;Podium, một dịch vụ bảng xếp hạng mã nguồn mở, chọn luật rõ ràng: bằng điểm thì ai đạt mức điểm đó trước xếp trên. Mỗi bảng xếp hạng có một bộ đếm khởi tạo bằng giá trị lớn nhất của số nguyên có dấu 64-bit; khi người chơi đạt điểm mới, một script Lua giảm bộ đếm và ghép 19 chữ số đó vào trước ID công khai, nên người đến trước có tiền tố lớn hơn và đứng trên. Script cập nhật nguyên tử cả bốn phần trạng thái: sorted set giảm dần, sorted set tăng dần (các chữ số được đảo để người đến trước vẫn đứng đầu), hash ánh xạ ID sang tên nội bộ và bộ đếm. Nếu điểm gửi lên trùng điểm hiện tại, tên nội bộ được giữ nguyên, nên việc gửi lại có tính lũy đẳng. Cái giá là độ trễ tăng khoảng 11–17% cho thao tác ghi và mỗi thành viên tốn 287 byte thay vì 99 byte theo đo đạc cục bộ — đánh đổi để có một luật xếp hạng ổn định mà người chơi hiểu được.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Bài viết đã được viết lại bởi Claude Code với Opus 5.5 vào ngày 27/09/2026.&lt;/em&gt;&lt;/p&gt;</description></item><item><title>Newsletter #129</title><link>https://miti99.com/post/2026/09/07/</link><pubDate>Mon, 07 Sep 2026 00:00:00 +0700</pubDate><guid>https://miti99.com/post/2026/09/07/</guid><description>&lt;p&gt;&lt;em&gt;Mời bạn thưởng thức Newsletter #129.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="hacking-the-method-name"&gt;&lt;a href="https://maxxedev.github.io/2026/08/05/hacking-the-method-name.html" rel="noopener noreferrer"&gt;Hacking the Method Name&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Trong Java, khi cần tên thuộc tính dưới dạng chuỗi để đọc CSV, lấy kết quả truy vấn hay gọi REST API, ta thường viết cứng &lt;code&gt;&amp;quot;title&amp;quot;&lt;/code&gt; hoặc &lt;code&gt;&amp;quot;author&amp;quot;&lt;/code&gt; vào mã nguồn — cách này dễ sai chính tả và trình biên dịch không hề kiểm tra. Bài viết giới thiệu kỹ thuật lấy tên phương thức trực tiếp từ method reference, ví dụ &lt;code&gt;nameOf(Book::title)&lt;/code&gt;, để tên luôn đồng bộ với mã nguồn. Mấu chốt là lớp &lt;code&gt;SerializedLambda&lt;/code&gt; có từ Java 8: tác giả khai báo functional interface &lt;code&gt;Getter&amp;lt;T, R&amp;gt;&lt;/code&gt; mở rộng cả &lt;code&gt;Function&lt;/code&gt; lẫn &lt;code&gt;Serializable&lt;/code&gt;, nên khi tuần tự hóa một method reference kiểu này, JVM sẽ thay nó bằng một đối tượng &lt;code&gt;SerializedLambda&lt;/code&gt; chứa thông tin mô tả phương thức. Một &lt;code&gt;ObjectOutputStream&lt;/code&gt; tùy biến bật &lt;code&gt;enableReplaceObject(true)&lt;/code&gt;, ghi ra một luồng rỗng và ghi đè &lt;code&gt;replaceObject()&lt;/code&gt; để chặn lấy đối tượng đó, rồi đọc tên qua &lt;code&gt;getImplMethodName()&lt;/code&gt;. Từ &lt;code&gt;nameOf()&lt;/code&gt; có thể dễ dàng viết thêm &lt;code&gt;beanNameOf()&lt;/code&gt; để bỏ tiền tố &lt;code&gt;get&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Hạn chế là kỹ thuật chỉ gọn với getter không tham số. Muốn áp dụng cho phương thức nhiều tham số như &lt;code&gt;save(title, author)&lt;/code&gt;, bạn phải tự khai báo từng interface có thể tuần tự hóa như &lt;code&gt;Function2&lt;/code&gt;, &lt;code&gt;Function3&lt;/code&gt; kèm các bản nạp chồng &lt;code&gt;nameOf()&lt;/code&gt; tương ứng, rất lặp lại. Vì vậy tác giả giới thiệu thư viện &lt;code&gt;safety-mirror&lt;/code&gt;, vốn đã làm sẵn phần việc nhàm chán này và còn trả về hẳn đối tượng &lt;code&gt;java.lang.reflect.Method&lt;/code&gt; thay vì chỉ tên phương thức.&lt;/p&gt;
&lt;h2 id="control-the-ideas-not-the-code"&gt;&lt;a href="https://antirez.com/news/169" rel="noopener noreferrer"&gt;Control the ideas, not the code&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;antirez, tác giả Redis, cho rằng trong thời đại LLM, lập trình viên nên kiểm soát ý tưởng và thiết kế của phần mềm thay vì đọc từng dòng mã do AI sinh ra — nhưng không phải chỉ ra lệnh &amp;ldquo;làm sản phẩm cho tôi&amp;rdquo; rồi phó mặc. Ông đưa ra ba lý do: lượng mã sinh ra mỗi ngày quá lớn để duyệt (ai duyệt nổi 5.000 dòng mỗi ngày?); LLM giỏi viết mã tối ưu cục bộ nhưng yếu hơn ở ý tưởng lớn, nên hỏi mô hình về thiết kế rồi đánh giá nó sẽ nhanh hơn soi từng hàm; và ngày làm việc chỉ có tám tiếng, đọc mã lấy đi thời gian cho việc quan trọng nhất là định hướng phần mềm, nghĩ tính năng, mẹo tối ưu và kiểm thử. Dự án DwarfStar của ông, được viết hoàn toàn tự động, cho thấy bạn vẫn phải hiểu sâu cách mọi thứ vận hành và biết chọn thiết kế tốt nhất.&lt;/p&gt;
&lt;p&gt;Với Redis, ông vẫn duyệt mã AI sinh ra vì tôn trọng người sẽ sửa tay, nhưng thừa nhận việc đó gần như vô ích; nếu được tự do, ông sẽ dành thời gian ấy để kiểm thử và nhờ LLM viết tệp &lt;code&gt;DESIGN.md&lt;/code&gt; mô tả bằng ngôn ngữ tự nhiên ý tưởng và thiết kế của từng cấu trúc dữ liệu. Riêng với lập trình viên trẻ chưa đủ kinh nghiệm xây dựng mô hình tư duy, ông khuyên họ tự học viết chương trình, như cài đặt một trình thông dịch, một cơ sở dữ liệu nhỏ hay một bảng băm.&lt;/p&gt;
&lt;h2 id="good-tools-are-invisible"&gt;&lt;a href="https://www.gingerbill.org/article/2026/07/10/good-tools-are-invisible/" rel="noopener noreferrer"&gt;Good Tools Are Invisible&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;gingerBill, tác giả ngôn ngữ Odin, lập luận rằng công cụ tốt phải &amp;ldquo;vô hình&amp;rdquo;: khi đã thành thạo, nó lùi vào hậu cảnh để bạn chỉ nghĩ về công việc. Ông phản đối thói quen biến khuyết điểm của công cụ thành &amp;ldquo;trò giải đố vui&amp;rdquo; rồi quảng bá như bằng chứng công cụ ấy tuyệt vời. Ví dụ là vim: có người hào hứng kể việc dựng macro cho một lần sửa văn bản, trong khi ông làm xong trong một phút bằng nhiều con trỏ của Sublime. Gốc rễ của sự bao biện là khi công cụ trở thành bản sắc cá nhân: thừa nhận nó có lỗi giống như thừa nhận điều gì đó về bản thân. Ông cũng phân biệt &lt;em&gt;cảm thấy&lt;/em&gt; hiệu quả với hiệu quả thật — thước đo trung thực là thời gian thực tế bỏ ra và số lỗi mắc phải, không phải cảm giác thông minh.&lt;/p&gt;
&lt;p&gt;Ông mở rộng sang vài tranh luận quen thuộc. GUI khó điều hướng bằng bàn phím là do người làm công cụ chưa chịu cài đặt, không phải giới hạn cố hữu; sai lầm chung là coi hạn chế hiện tại của một loại công cụ là bản chất của nó. Văn hóa cấu hình tới tận cùng góp phần khiến Linux mãi chưa phổ biến trên máy tính để bàn: thiết lập mặc định tốt là trách nhiệm của người làm công cụ và là cách tôn trọng thời gian người dùng. Đường cong học tập dốc là chi phí chứ không phải ưu điểm. Kết luận: dùng công cụ nào cũng được, miễn là nó biến mất khỏi nhận thức.&lt;/p&gt;
&lt;h2 id="mysteries-of-telegram-dc"&gt;&lt;a href="https://dev.moe/en/3025" rel="noopener noreferrer"&gt;Mysteries of Telegram DC&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Coxxs điều tra năm trung tâm dữ liệu (DC1–DC5) của Telegram: DC1 và DC3 đặt tại Miami, DC2 và DC4 tại Amsterdam, DC5 tại Singapore. Mỗi tài khoản gắn với một DC ngay lúc đăng ký, không đổi theo số điện thoại hay vị trí và không được tự chọn. Một bot thống kê từng cho thấy DC2 và DC3 không có ai, dẫn tới suy đoán rằng chúng chỉ là DC phụ. Tác giả thử cả ba cách xác định DC phổ biến trên một tài khoản DC2 mới: gọi &lt;code&gt;auth.sendCode&lt;/code&gt; qua giao thức MTProto và đọc lỗi &lt;code&gt;PHONE_MIGRATE_2&lt;/code&gt;, đọc trường &lt;code&gt;dc_id&lt;/code&gt; trong siêu dữ liệu ảnh đại diện, và phân tích tên miền Web CDN. Cách thứ ba cho kết quả sai, vì DC2 và DC3 &amp;ldquo;mượn&amp;rdquo; tên miền của DC4 và DC1 cùng địa điểm để phục vụ Web CDN, nên người dùng DC2 đều bị nhận nhầm thành DC4 — chính là lỗi của con bot kia.&lt;/p&gt;
&lt;p&gt;Kết luận là DC1, DC2, DC4 và DC5 được phân bổ theo mã quốc gia của số điện thoại lúc đăng ký (chẳng hạn +49 của Đức vào DC2) và đều có rất nhiều người dùng. Ngược lại, DC3 dù vẫn hoạt động nhưng gần như không còn ai: tác giả chỉ tìm được hai tài khoản DC3, và ảnh cũ nằm trên DC3 còn ảnh mới nằm trên DC1 cho thấy chúng đã được chuyển sang DC1 khoảng năm 2020. Kiểm tra hơn 10.000 số điện thoại khắp thế giới bằng phương pháp đăng nhập cũng xác nhận DC3 không nhận người dùng mới. Bài viết kèm bảng tra DC theo mã quốc gia, và tác giả lưu ý nhiều kết luận chỉ là suy đoán vì máy chủ Telegram không mã nguồn mở.&lt;/p&gt;
&lt;h2 id="how-i-use-htmx-with-go"&gt;&lt;a href="https://www.alexedwards.net/blog/how-i-use-htmx-with-go" rel="noopener noreferrer"&gt;How I use HTMX with Go&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Alex Edwards chia sẻ cách ông dùng HTMX cùng Go để thêm tương tác mượt như ứng dụng mà viết rất ít JavaScript, trong khi vẫn giữ được sự nhất quán và an toàn của việc kết xuất HTML phía máy chủ bằng &lt;code&gt;html/template&lt;/code&gt;, thông qua một ứng dụng nhỏ lọc danh sách người dùng theo thời gian thực. Thư mục mẫu được chia thành &lt;code&gt;base.tmpl&lt;/code&gt; cho bố cục chung, &lt;code&gt;pages/&lt;/code&gt; cho nội dung từng trang và &lt;code&gt;partials/&lt;/code&gt; cho các khối HTML dùng lại, tất cả cùng tài nguyên tĩnh được nhúng vào tệp thực thi bằng &lt;code&gt;//go:embed&lt;/code&gt;. Trọng tâm là kiểu &lt;code&gt;htmlRenderer&lt;/code&gt;: nó phân tích sẵn tập mẫu chung (base và mọi partial) lúc khởi động, rồi phương thức &lt;code&gt;render()&lt;/code&gt; nhân bản tập đó, bổ sung mẫu của trang cụ thể và thực thi mẫu được gọi tên. Nhờ vậy, chỉ cần tách các hàng của bảng thành mẫu &lt;code&gt;users:rows&lt;/code&gt; riêng, cùng một tệp mẫu có thể trả về cả trang HTML hoặc chỉ mảnh HTML để HTMX thay vào.&lt;/p&gt;
&lt;p&gt;Server phân biệt yêu cầu từ HTMX qua tiêu đề &lt;code&gt;HX-Request: true&lt;/code&gt; và luôn đặt &lt;code&gt;Vary: HX-Request&lt;/code&gt; để bộ đệm trung gian không trả nhầm nội dung. Khi người dùng bấm nút quay lại, cần đặt &lt;code&gt;historyRestoreAsHxRequest&lt;/code&gt; thành &lt;code&gt;false&lt;/code&gt;, nếu không trình duyệt sẽ nhận về mảnh HTML thay vì cả trang. Chuyển hướng không dùng được phản hồi &lt;code&gt;3xx&lt;/code&gt; thông thường vì trình duyệt tự đi theo trước khi HTMX kịp xử lý; thay vào đó hãy trả phản hồi &lt;code&gt;2xx&lt;/code&gt; kèm tiêu đề &lt;code&gt;HX-Redirect&lt;/code&gt;, vẫn giữ &lt;code&gt;3xx&lt;/code&gt; cho yêu cầu không đến từ HTMX. Cuối cùng, thiết lập &lt;code&gt;responseHandling&lt;/code&gt; cho phép hiển thị lỗi &lt;code&gt;4xx&lt;/code&gt;/&lt;code&gt;5xx&lt;/code&gt; ra toàn trang, riêng lỗi &lt;code&gt;422&lt;/code&gt; vẫn được thay vào đúng vị trí đích.&lt;/p&gt;
&lt;h2 id="quadrupling-code-performance-with-a"&gt;&lt;a href="https://purplesyringa.moe/blog/quadrupling-code-performance-with-a-useless-if/" rel="noopener noreferrer"&gt;Quadrupling code performance with a &amp;ldquo;useless&amp;rdquo; if&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;purplesyringa kể lại lúc tối ưu một bộ nén chuỗi chuyên dụng, nơi vòng lặp dò đường mã hóa tối ưu chỉ có một phép gán &lt;code&gt;j = next_j[i][j]&lt;/code&gt;, biên dịch ra đúng một chỉ thị &lt;code&gt;mov&lt;/code&gt; và trông như không thể tối ưu thêm. Vấn đề là bộ xử lý hiện đại có song song mức chỉ thị, chạy được nhiều chỉ thị cùng lúc kể cả giữa các vòng lặp, nhưng không thể chạy đồng thời các chỉ thị phụ thuộc nhau. Ở đây mỗi vòng phải chờ giá trị &lt;code&gt;j&lt;/code&gt; của vòng trước, nên tốc độ bị giới hạn bởi độ trễ truy cập bộ nhớ chứ không phải thông lượng. Vì các khối dữ liệu ít, &lt;code&gt;next_j[i][j]&lt;/code&gt; thường bằng chính &lt;code&gt;j&lt;/code&gt;, nên tác giả tận dụng dự đoán nhánh: bọc phép gán trong điều kiện &lt;code&gt;if (j != next_j[i][j])&lt;/code&gt;. Khi CPU dự đoán nhánh này hiếm khi xảy ra, nó không thấy sự phụ thuộc giữa các vòng và thực thi phỏng đoán trước; khi điều kiện thật sự đúng, cơ chế xử lý dự đoán sai sẽ hủy kết quả sai và chạy lại với &lt;code&gt;j&lt;/code&gt; đúng.&lt;/p&gt;
&lt;p&gt;Khó khăn là với trình biên dịch, câu &lt;code&gt;if&lt;/code&gt; này hoàn toàn vô dụng và sẽ bị loại bỏ ngay. Tác giả phải ép kiểu qua con trỏ &lt;code&gt;volatile&lt;/code&gt; để khiến điều kiện và phép gán trông như độc lập; về sau độc giả phát hiện chú thích &lt;code&gt;[[unlikely]]&lt;/code&gt; hoặc &lt;code&gt;__builtin_expect&lt;/code&gt; cũng có tác dụng tương tự với LLVM, nhưng &lt;code&gt;volatile&lt;/code&gt; sinh mã tốt hơn và chạy được cả với GCC. Kết quả trên phép đo tổng hợp là nhanh gấp bốn lần (từ 320 xuống 80 micro giây), còn trong thử nghiệm thực tế khoảng gấp đôi, có lẽ do LLVM sinh mã chưa tối ưu.&lt;/p&gt;
&lt;h2 id="reverse-engineering-chatgpt-web-how-openai-built-for-a-billion-users"&gt;&lt;a href="https://performance.dev/chatgpt" rel="noopener noreferrer"&gt;Reverse Engineering ChatGPT Web: How OpenAI Built for a Billion Users&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Dennis Brotzky phân tích từ bên ngoài kiến trúc web của ChatGPT, trang phục vụ khoảng một tỉ người. Điểm nổi bật đầu tiên là sự &amp;ldquo;bình thường&amp;rdquo;: React 19 với React Router 7, TanStack Query, Tailwind CSS và Radix UI, gần như không có khung tự chế. Ứng dụng ra mắt tháng 11/2022 trên Next.js, chuyển sang Remix năm 2024 rồi theo Remix hợp nhất vào React Router 7. Để phục vụ mọi người trên mọi thiết bị, trang dùng kết xuất phía máy chủ theo luồng với các ranh giới Suspense: tài liệu khi chưa đăng nhập chỉ nặng 84 KB sau nén, thời gian trả byte đầu tiên 50–65 mili giây, và 556 cổng tính năng, 144 cấu hình cùng 192 thử nghiệm được tính sẵn phía máy chủ rồi nhúng vào tài liệu. Họ không dùng phông chữ web mà dựa vào phông hệ thống, tách CSS theo tính năng, và toàn bộ quá trình khởi động gồm 160 phần mã có băm nội dung được sắp xếp quanh một câu hỏi duy nhất: người dùng gõ được vào khung soạn tin chưa — có hẳn cờ &lt;code&gt;deferStartupImportsUntilComposerTTFI&lt;/code&gt; cho việc này.&lt;/p&gt;
&lt;p&gt;Kỹ thuật thật sự được dồn vào phần câu trả lời: khung soạn tin là trình soạn thảo ProseMirror, khối mã là CodeMirror, công thức toán dùng KaTeX kèm cây MathML ẩn cho trình đọc màn hình. Ngay lúc tải trang, thử thách proof-of-work của Cloudflare và hệ thống chống lạm dụng Sentinel đã chạy ngầm, còn khách chưa đăng nhập vẫn được cấp định danh ẩn danh thật để giới hạn tần suất mà không cần rào đăng nhập. Họ đo thời điểm sẵn sàng tương tác cho mọi người dùng: không đo được thì không cải thiện được.&lt;/p&gt;
&lt;h2 id="broker-visible-vs-client-local-parallelism"&gt;&lt;a href="https://jack-vanlightly.com/blog/2026/6/3/broker-visible-vs-client-local-parallelism" rel="noopener noreferrer"&gt;Broker-Visible vs Client-Local Parallelism&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Trong một nhánh rẽ của loạt bài về Kafka Share Groups, Jack Vanlightly nhắc rằng share group sinh ra để cung cấp ngữ nghĩa hàng đợi trên log, chứ không chủ yếu để song song hóa việc tiêu thụ. Câu hỏi quan trọng hơn là đơn vị song song nằm ở đâu. Nếu đơn vị song song là chính consumer, mỗi đơn vị đều hiện ra với broker dưới dạng tương tác giao thức, trạng thái cần quản lý và một hoặc nhiều kết nối TCP. Nếu song song nằm ngay trong ứng dụng khách, dưới dạng luồng ảo, tác vụ bất đồng bộ hay luồng hệ điều hành, broker không hề thấy chúng, nên cần ít consumer, ít kết nối và ít trạng thái hơn nhiều. Sự phân chia này có ở mọi hệ thống nhắn tin, không riêng Kafka.&lt;/p&gt;
&lt;p&gt;Để biết cần bao nhiêu song song, tác giả dùng công thức đơn giản: mức song song tổng cộng bằng tốc độ tin nhắn nhân thời gian xử lý trung bình. Với 60.000 tin nhắn mỗi giây và mỗi tin mất một giây, cần 60.000 tin được xử lý cùng lúc; nếu mỗi đơn vị là một consumer tuần tự thì phải có 60.000 consumer, còn nếu thời gian xử lý là mười giây thì con số lên tới 600.000 consumer với hơn một triệu kết nối TCP. Nếu công việc chủ yếu là I/O và mỗi ứng dụng khách xử lý song song được 1.000 tin, chỉ cần 60 consumer. Quản lý luồng ảo rẻ hơn nhiều so với quản lý kết nối TCP và siêu dữ liệu cho từng đơn vị song song; cái giá là ứng dụng khách phức tạp hơn, nhưng đã có thư viện hỗ trợ.&lt;/p&gt;
&lt;h2 id="work-loudly"&gt;&lt;a href="https://ben.balter.com/2026/07/14/work-loudly/" rel="noopener noreferrer"&gt;Work Loudly&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Ben Balter chỉ ra rằng ở văn phòng, một phần khả năng hiện diện của bạn đến miễn phí: người khác thấy bạn đứng trước bảng trắng hay gỡ rối cho đồng nghiệp ở khu bếp. Làm từ xa thì tín hiệu đó về không; công việc không kém giá trị đi mà chỉ kém hiển thị đi, đến mức người quản lý dù ủng hộ bạn cũng không nói được bạn đã làm gì lúc đánh giá. Tổng kết sau khi xong cũng không cứu được, vì phần đáng giá — cách lập luận, những ngõ cụt — đã qua mất. Giải pháp là &amp;ldquo;làm việc thật to&amp;rdquo;: làm cho công việc hiển thị ngay trong lúc làm, như mở issue trước khi bắt tay vào việc, viết mô tả pull request giải thích cách tiếp cận, chia sẻ bản nháp khi mới xong 40%, và hỏi ở kênh chung thay vì tin nhắn riêng; nếu nó chỉ nằm trong đầu bạn hoặc trong tin nhắn riêng thì chưa đủ &amp;ldquo;to&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Tác giả ví việc này như chiếc chuông đeo khi đi rừng có gấu: rung chuông không phải để khoe mà để không ai bị bất ngờ, vì người liên quan phát hiện quyết định của bạn sau khi nó đã phát hành sẽ phản ứng phòng thủ. Công việc hiển thị sớm còn thu hút người khác phát hiện lỗi hộ, chỉ ra giải pháp đã có sẵn và kéo người giỏi tham gia; với người quản lý, đó là cách đánh giá theo kết quả thay vì theo ai trông có vẻ đang trực tuyến. Phép thử cuối cùng: người quản lý có viết được bản đánh giá cho bạn chỉ từ dấu vết bạn để lại không?&lt;/p&gt;
&lt;h2 id="building-service-topology-at-scale-architecture-challenges-and-lessons-learned"&gt;&lt;a href="https://netflixtechblog.com/building-service-topology-at-scale-architecture-challenges-and-lessons-learned-f4b792f3f0d8" rel="noopener noreferrer"&gt;Building Service Topology at Scale: Architecture, Challenges, and Lessons Learned&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Đội kỹ thuật Netflix chia sẻ quá trình xây dựng Service Topology, bản đồ phụ thuộc giữa các dịch vụ theo thời gian gần thực, giúp gỡ lỗi nhanh hơn và xác định phạm vi ảnh hưởng khi có sự cố. Họ chọn xử lý theo luồng thay vì theo lô, vì bản đồ cũ một giờ lúc ba giờ sáng chỉ là &amp;ldquo;khảo cổ&amp;rdquo;, và dùng cơ chế đối áp của reactive streams để khi tầng sau quá tải, tín hiệu chậm lại truyền ngược lên tận consumer Kafka và dữ liệu chờ trong Kafka thay vì bị mất. Nhật ký luồng mạng chỉ thấy từng chặng rời rạc như App A → bộ cân bằng tải → App B, nên đường ống có ba tầng: tầng một lọc và gom theo cửa sổ năm phút, tầng hai phân phối lại để các chặng qua cùng thành phần trung gian gặp nhau và ghép thành quan hệ App A → App B, tầng ba làm giàu siêu dữ liệu và ghi vào cơ sở dữ liệu đồ thị. Các tầng giao tiếp bằng Server-Sent Events thay cho gRPC vốn tốn CPU, và dùng băm nhất quán để tự cân bằng tải khi số máy thay đổi.&lt;/p&gt;
&lt;p&gt;Phần đáng học nhất là khó khăn trên môi trường thật: consumer Kafka tụt lại hàng giờ, nút nóng nhận lưu lượng gấp trăm lần máy khác, và thu gom rác tốn CPU hơn cả logic nghiệp vụ, buộc đội dùng cấu trúc dữ liệu có thể thay đổi trên đường xử lý nóng, trái với thông lệ Scala. Bài học của họ: quy mô thay đổi mọi thứ, sửa xong một nút nghẽn sẽ lộ ra nút nghẽn tiếp theo, và &amp;ldquo;thông lệ tốt nhất&amp;rdquo; chỉ là điểm khởi đầu.&lt;/p&gt;
&lt;h3 id="bonus"&gt;Bonus&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;Images:&lt;/strong&gt;
&lt;img src="https://substackcdn.com/image/fetch/$s_!lpxK!,w_1100,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9fb99a2-ec71-476d-a9cb-6c1a43339d90_2484x3002.png" alt="MCP vs A2A vs ACP: How AI Agents Actually Talk to Each Other"&gt;
&lt;img src="https://substackcdn.com/image/fetch/$s_!LZtJ!,w_1100,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6456348c-5d92-4524-8dd6-d75b4dc7feaf_1280x1547.png" alt="LLM vs RAG vs Agent evals"&gt;
&lt;img src="https://substackcdn.com/image/fetch/$s_!ULeT!,w_1100,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe81b4572-820e-4790-8861-8efe14f38700_2252x2752.png" alt="How Distributed Tracing Works at the High Level?"&gt;
&lt;img src="https://substackcdn.com/image/fetch/$s_!6UyI!,w_1100,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9f8ea7bf-2170-4466-92f3-edd7eab11de4_3000x3900.png" alt="The Life of a Redis Query"&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Bài viết đã được viết lại bởi Claude Code với Opus 5.5 vào ngày 27/09/2026.&lt;/em&gt;&lt;/p&gt;</description></item><item><title>Newsletter #128</title><link>https://miti99.com/post/2026/08/28/</link><pubDate>Fri, 28 Aug 2026 00:00:00 +0700</pubDate><guid>https://miti99.com/post/2026/08/28/</guid><description>&lt;p&gt;&lt;em&gt;Mời bạn thưởng thức Newsletter #128.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="some-new-agentic-patterns"&gt;&lt;a href="https://blog.fsck.com/2026/07/05/new-patterns/" rel="noopener noreferrer"&gt;Some new agentic patterns&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Jesse Vincent, người tạo ra bộ SDK agent mã nguồn mở Lace, chia sẻ những mô hình (pattern) mới mà công ty Prime Radiant của anh đang dùng để đưa agent AI vào công việc hằng ngày. Các agent ở đây không chờ được hỏi mới trả lời mà chủ động làm việc ngay trong Slack: Scribble theo dõi các vấn đề phát sinh và tự cập nhật tài liệu nội bộ, Nora hỗ trợ mảng go-to-market và tự đề xuất cơ hội như các hội thảo nên đăng ký diễn thuyết, còn Sen là nhóm agent trợ lý cá nhân lo phân loại email, lên lịch và tra cứu thông tin.
Mô hình đáng chú ý nhất được tác giả gọi là &amp;ldquo;agentic user in the loop&amp;rdquo;: khi phát triển Sen 2.0, Claude Code làm việc trực tiếp với một agent khác tên Ada-sen mà không cần con người đứng giữa chuyển lời. Claude đề xuất thay đổi, Ada đọc đặc tả, nêu câu hỏi, kiểm thử thực tế rồi phản hồi, và hai bên cứ thế lặp lại, có khi xây xong cả tính năng qua đêm. Về bảo mật, tác giả dựa vào nguyên tắc chia tách quyền hạn: agent chính không được tự liên lạc ra ngoài, thông tin đăng nhập nằm trong 1Password và chỉ được chèn vào request qua một proxy trung gian, mọi subagent muốn dùng chúng đều phải xin phép một agent trọng tài, nhờ đó bí mật không bao giờ lọt vào transcript. Dù vậy, anh thừa nhận rủi ro &amp;ldquo;Lethal Trifecta&amp;rdquo; do Simon Willison đặt tên vẫn còn nguyên: một agent vừa đọc được dữ liệu riêng tư, vừa liên lạc được ra ngoài, vừa tiếp xúc nội dung không đáng tin thì luôn cần biện pháp giảm thiểu.&lt;/p&gt;
&lt;h2 id="why-i-stopped-arguing-with-people"&gt;&lt;a href="https://wangcong.org/2026-06-30-why-i-stopped-arguing-with-people.html" rel="noopener noreferrer"&gt;Why I Stopped Arguing With People&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Cong Wang, một kỹ sư phần mềm, kể lại hành trình từ chỗ thích tranh luận để chứng minh mình đúng về kỹ thuật đến lúc nhận ra phần lớn các cuộc tranh luận ấy chẳng đạt được mục đích. Anh mượn ý từ Đạo Đức Kinh của Lão Tử: &amp;ldquo;có&amp;rdquo; và &amp;ldquo;không&amp;rdquo; sinh ra nhau, nên hễ có người đúng thì phải có người sai rõ ràng đứng cạnh. Thắng một cuộc tranh luận nghĩa là tạo ra một kẻ thua, và chiến thắng đó rỗng tuếch. Theo anh, đa số tranh luận không bảo vệ ý tưởng mà bảo vệ cái tôi: khi đối phương thấy mình bị tấn công cá nhân, họ sẽ càng cố thủ, và lập luận càng sắc bén thì quan điểm của họ càng cứng lại.&lt;/p&gt;
&lt;p&gt;Tác giả cũng chỉ ra rằng con người cảm nhận trước rồi mới tìm lý lẽ bào chữa cho cảm nhận đó, nên logic gần như bất lực trước một quan điểm xuất phát từ cảm xúc. Người ta hiếm khi học từ lời khuyên không được yêu cầu; họ học từ hậu quả, và việc sửa sai người khác thường chỉ để lại sự oán giận. Ngoại lệ duy nhất là khi ai đó chủ động nhờ giúp: lúc ấy hàng phòng thủ hạ xuống và việc học mới thật sự diễn ra. Thay vì cố thắng, anh khuyên xem sự bất đồng như một lợi thế cạnh tranh, vì cơ hội kinh doanh thường nằm đúng ở chỗ mọi người chưa đồng ý với nhau. Cuối cùng, người duy nhất ta thay đổi được là chính mình, bằng cách khiêm tốn xin góp ý và thật lòng tiếp thu nó.&lt;/p&gt;
&lt;h2 id="understanding-is-the-new-bottleneck"&gt;&lt;a href="https://www.geoffreylitt.com/2026/07/02/understanding-is-the-new-bottleneck.html" rel="noopener noreferrer"&gt;Understanding is the new bottleneck&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Trong bài nói tại hội nghị AI Engineer tháng 7/2026, Geoffrey Litt, kỹ sư thiết kế tại Notion, cho rằng khi agent AI sinh ra ngày càng nhiều mã nguồn, điểm nghẽn mới không còn là tốc độ viết mã mà là khả năng hiểu của con người. Theo anh, hiểu mã nguồn không chủ yếu để kiểm tra agent làm đúng hay sai, vì việc đó agent đã làm khá tốt, mà để con người đủ thông thạo hệ thống và tiếp tục góp ý tưởng qua rất nhiều vòng lặp làm việc với agent. Bỏ qua bước hiểu sẽ tích tụ thành &amp;ldquo;nợ nhận thức&amp;rdquo; và gây rắc rối về lâu dài.&lt;/p&gt;
&lt;p&gt;Litt đưa ra ba kỹ thuật. Thứ nhất là tài liệu giải thích mã nguồn: skill &lt;code&gt;/explain-diff&lt;/code&gt; do anh viết tạo ra bản giải thích có bối cảnh nền, phần trực giác đi trước chi tiết kỹ thuật, hình minh hoạ tương tác; kèm theo là các câu hỏi kiểm tra đóng vai trò &amp;ldquo;bộ điều tốc&amp;rdquo;, buộc người đọc hiểu thật trước khi chia sẻ mã. Thứ hai là &amp;ldquo;micro-world&amp;rdquo;, lấy cảm hứng từ ý tưởng &amp;ldquo;sống trong Mathland&amp;rdquo; của nhà giáo dục Seymour Papert: nhờ agent dựng những môi trường tương tác nhỏ như trình gỡ lỗi, bảng điều khiển hay mô phỏng để con người tự khám phá và hình thành trực giác về hệ thống. Thứ ba là không gian chung, nơi agent lên kế hoạch cùng con người trên các trang cộng tác để cả đội cùng suy nghĩ thay vì mỗi người một ngả. Anh kết lại bằng tầm nhìn của Alan Kay từ 50 năm trước: máy tính sinh ra để tăng cường khả năng con người chứ không chỉ để tự động hoá.&lt;/p&gt;
&lt;h2 id="when-to-repeat-yourself"&gt;&lt;a href="https://newsletter.francofernando.com/p/when-to-repeat-yourself" rel="noopener noreferrer"&gt;When to repeat yourself&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Franco Fernando đặt lại câu hỏi về nguyên tắc DRY (Don&amp;rsquo;t Repeat Yourself): gộp mã nguồn trùng lặp luôn có chi phí ẩn mà nhiều lập trình viên bỏ qua. Lớn nhất là &amp;ldquo;thuế phối hợp&amp;rdquo;: khi nhiều đội dùng chung một đoạn mã, mỗi thay đổi đều kéo theo họp thống nhất, duyệt thiết kế và thương lượng lúc gộp nhánh, trong khi một đội sửa bản riêng của mình thì rất nhanh. Vì vậy câu hỏi đúng là chi phí phối hợp một thành phần chung có đắt hơn chi phí để các bản sao dần lệch nhau hay không. Tác giả phân biệt trùng lặp ngẫu nhiên, tức những đoạn mã trông giống nhau nhưng thuộc hai nghiệp vụ khác nhau và sẽ tiến hoá theo hai hướng, với trùng lặp bản chất, tức cùng một quy tắc nghiệp vụ bị lặp ở nhiều nơi; chỉ loại sau mới đáng gộp.&lt;/p&gt;
&lt;p&gt;Anh khuyên nên kiên nhẫn, vì chờ thêm để hiểu rõ vấn đề thì rẻ còn trừu tượng hoá quá sớm thì đắt. Mỗi cách chia sẻ mã đều có đánh đổi: thư viện dễ dựng nhưng tạo phụ thuộc và xung đột phiên bản gián tiếp; microservice có hợp đồng API rõ ràng nhưng thêm độ trễ mạng, gánh nặng vận hành và rủi ro về tính sẵn sàng; kế thừa sinh ra trừu tượng cứng nhắc, còn composition linh hoạt hơn nhưng phức tạp hơn. Kết luận là trong phạm vi một đội thì DRY thường thắng, nhưng khi vượt qua ranh giới tổ chức thì phải tính lại, và nên ưu tiên phương án dễ đảo ngược, bởi gộp các bản trùng lặp về sau dễ hơn nhiều so với tách một trừu tượng đã bị ràng buộc chặt.&lt;/p&gt;
&lt;h2 id="let-ai-burn"&gt;&lt;a href="https://www.wheresyoured.at/let-ai-burn/" rel="noopener noreferrer"&gt;Let AI Burn&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Ed Zitron lập luận rằng ngành AI là một bong bóng dựng trên sự thổi phồng và dòng tiền chạy vòng, nên nếu nó vỡ thì chính phủ không nên giải cứu. Theo tác giả, tình huống này khác hẳn khủng hoảng 2008: ngân hàng khi đó là hạ tầng sống còn của nền kinh tế, còn các công ty AI thì không, và ngoài SoftBank với hơn 40 tỷ USD cho OpenAI vay thì cũng không có rủi ro sụp đổ dây chuyền tương tự. Riêng năm 2026, OpenAI và Anthropic đã huy động hơn 300 tỷ USD nhưng vẫn lỗ nặng; hai công ty này chiếm tới 89% doanh thu của nhóm startup AI lớn nhất, còn phần còn lại của ngành chỉ tạo ra khoảng 20 tỷ USD mỗi năm.
Tác giả bác bỏ phép so sánh quen thuộc với bong bóng dot-com: cáp quang thời đó xây thừa vẫn dùng được nhiều năm, còn GPU hẹp mục đích và nhanh mất giá. Các hyperscaler dự kiến chi 765 tỷ USD vốn đầu tư trong 2026 và hơn 1.000 tỷ USD trong 2027, riêng OpenAI tiêu khoảng 50 tỷ USD cho hạ tầng tính toán trong năm nay. Zitron cũng bác đề xuất của Sam Altman trao cho chính phủ Mỹ 5% cổ phần trị giá 42 tỷ USD, vì con số ấy còn chưa bằng chi phí tính toán một năm và không giải quyết được việc thua lỗ. Kết luận của ông: hãy để thị trường tự đào thải, vì một gói cứu trợ sẽ phải bơm tiền mãi mà không có điểm dừng.&lt;/p&gt;
&lt;h2 id="when-ai-costs-more-than-the-engineer"&gt;&lt;a href="https://tomtunguz.com/ai-spend-breakeven-2029/" rel="noopener noreferrer"&gt;When AI Costs More Than the Engineer&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Tomasz Tunguz chỉ ra một sự đảo ngược trong bài toán chi phí của ngành phần mềm: ở các công ty AI hàng đầu, tiền chi cho năng lực tính toán đã vượt tiền lương. Anthropic tiêu khoảng 2 triệu USD tính toán cho mỗi nhân sự mỗi năm, gấp 2,3 lần chi phí nhân sự tính đủ (khoảng 500 nghìn USD mỗi người). Đổi lại, doanh thu trên mỗi nhân sự của Anthropic đạt 14 triệu USD và OpenAI đạt 6,5 triệu USD, cao nhất toàn cầu và vượt xa mức 250-600 nghìn USD thường thấy ở các công ty SaaS.&lt;/p&gt;
&lt;p&gt;Phần còn lại của thị trường vẫn ở rất xa. Nhóm 1% công ty phần mềm dẫn đầu chi khoảng 89 nghìn USD cho AI mỗi kỹ sư mỗi năm, tương đương 40% mức lương 224 nghìn USD, trong khi công ty ở mức trung vị chỉ chi 137 USD. Tác giả dựng ba kịch bản cho năm 2029: kịch bản thấp là 106 nghìn USD mỗi kỹ sư (41% lương), kịch bản cơ sở 363 nghìn USD (140%) và kịch bản cao 596 nghìn USD (230%), tức ngang tỷ lệ của Anthropic hiện nay. Kết quả phụ thuộc vào hai lực kéo ngược chiều. Một bên là giá token đã giảm khoảng 10 lần mỗi năm suốt ba năm qua, các mô hình open-weight thu hẹp khoảng cách chất lượng với chi phí thấp hơn, và doanh nghiệp có thể giới hạn mức dùng theo vai trò. Bên kia là giá mô hình tiên tiến giữ nguyên, còn các quy trình agentic đốt token nhiều hơn hẳn, đến mức Goldman Sachs dự báo lượng token tiêu thụ tăng 24 lần vào năm 2030.&lt;/p&gt;
&lt;h2 id="the-best-code-is-the-one-you-shiftdelete"&gt;&lt;a href="https://ayende.com/blog/204067-a/the-best-code-is-the-one-you-shift-delete/" rel="noopener noreferrer"&gt;The best code is the one you shift+delete&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Oren Eini, tác giả RavenDB, cho rằng đo tốc độ sinh mã của mô hình lập trình là nhìn sai chỗ; giá trị thật là nó giúp ta làm những việc trước đây quá tốn công nên chẳng ai buồn làm. Ông kể về một sự cố ở môi trường thực tế với khoảng 25-30 MB log nén. Tìm lỗi thì dễ, cái khó là tương quan các sự kiện trên dòng thời gian, còn đưa cả file log cho mô hình đọc thì vừa không khả thi vừa đắt. Ông đưa mười dòng đầu và yêu cầu mô hình viết script trích xuất, tổng hợp rồi hiển thị thành bảng; chưa đầy một phút đã có công cụ để hỏi tiếp, chẳng hạn vẽ biểu đồ thay đổi index theo thời gian. Nguyên nhân lộ ra: khách hàng chạy nhiều phiên bản ứng dụng, mỗi bản có bộ index riêng và liên tục ghi đè lên nhau. Ông chưa từng đọc script ấy và xoá sạch khi xong việc.&lt;/p&gt;
&lt;p&gt;Trường hợp thứ hai ngược lại: refactor cách thực thi truy vấn trong Corax, loại mã sẽ nằm trong sản phẩm hàng chục năm. Ở đây mô hình viết còn ông cầm lái và duyệt từng dòng. Ông cảnh báo rằng sau vài giờ thử, hoàn tác rồi thử lại, bạn rất dễ ngẩng lên và thấy hai nghìn dòng thay đổi mà mình chưa thực sự viết. Để giữ quyền tác giả, ông dựa vào kiểm thử hồi quy cho hành vi cũ, kiểm thử mới cho hành vi mới, cùng một harness dùng xong vứt chạy song song bản cũ và bản mới rồi trực quan hoá kết quả. Những công cụ tạm như vậy trước đây tốn vài giờ đến nhiều tuần, nay chỉ còn vài phút.&lt;/p&gt;
&lt;h3 id="bonus"&gt;Bonus&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;Bài viết:&lt;/strong&gt;
&lt;a href="https://zalo.me/vi/founder" rel="noopener noreferrer"&gt;Chia sẻ suy nghĩ về sản phẩm và người làm sản phẩm&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
 &lt;p&gt;Vương Quang Khải, nhà sáng lập Zalo, chia sẻ góc nhìn về sản phẩm: ba giá trị phổ quát là hữu ích, thẩm mỹ và trực quan, còn Zalo chọn thêm đơn giản, tin cậy và riêng tư, và việc khó nhất là đánh đổi giữa những giá trị tốt nhưng xung đột nhau. Với người làm sản phẩm, ông đề cao ba năng lực thấu hiểu người dùng, ra quyết định đánh đổi và chăm chút tiểu tiết, tỏ ra dè dặt với việc ra quyết định thuần theo dữ liệu hay thử nghiệm A/B, và nhấn mạnh rằng trên hết phải thật lòng yêu sản phẩm mình làm.&lt;/p&gt;

&lt;/blockquote&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Bài viết đã được viết lại bởi Claude Code với Opus 5.5 vào ngày 27/09/2026.&lt;/em&gt;&lt;/p&gt;</description></item><item><title>Newsletter #127</title><link>https://miti99.com/post/2026/07/29/</link><pubDate>Wed, 29 Jul 2026 00:00:00 +0700</pubDate><guid>https://miti99.com/post/2026/07/29/</guid><description>&lt;p&gt;&lt;em&gt;Mời bạn thưởng thức Newsletter #127.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="a-return-to-two-pizza-culture"&gt;&lt;a href="https://www.allthingsdistributed.com/2026/06/return-to-two-pizza-culture.html" rel="noopener noreferrer"&gt;A return to two-pizza culture&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Werner Vogels nhìn lại mô hình nhóm “hai chiếc pizza” từng giúp Amazon tăng tốc: nhóm đủ nhỏ để mọi người hiểu công việc của nhau, sở hữu sản phẩm từ đầu đến cuối và tự quyết những việc có thể đảo ngược mà không phải chờ phê duyệt. Khi công ty mở rộng lên hàng trăm dịch vụ, phụ thuộc chéo, tầng quản lý và vòng xét duyệt dễ bào mòn chính sự linh hoạt đó. Theo tác giả, các tác nhân lập trình như Kiro đang thay đổi bài toán, vì một sản phẩm mẫu giờ có thể ra đời trong vài giờ thay vì vài tháng.&lt;/p&gt;
&lt;p&gt;Qua câu chuyện nhóm Amazon Quick bản máy tính, nơi sản phẩm mẫu đầu tiên được dựng chỉ trong một buổi tối rồi nhanh chóng thu hút thêm người đóng góp, Vogels đề xuất điều chỉnh quy trình Working Backwards. Khi đã tin vào vấn đề của khách hàng nhưng chưa chắc về giải pháp, hãy xây dựng sản phẩm mẫu trước, tự sử dụng hằng ngày, sửa lỗi ngay khi gặp và chỉ viết tài liệu sau khi đã học được từ trải nghiệm thực tế. Việc viết vẫn quan trọng để làm rõ tư duy, nhưng không còn là cách duy nhất để biến ý tưởng thành thứ cụ thể. Ngay cả khi nhóm lớn lên hàng trăm người, việc giữ cấu trúc gồm nhiều nhóm nhỏ có trách nhiệm trọn vẹn giúp tốc độ đổi mới không bị trì trệ. Công cụ AI không thay thế phán đoán của con người, mà rút ngắn vòng phản hồi giữa ý tưởng và thực tế.&lt;/p&gt;
&lt;h2 id="a-peek-into-reddit"&gt;&lt;a href="https://lyra.horse/blog/2026/06/reddit-spam-internals/" rel="noopener noreferrer"&gt;A peek into Reddit&amp;rsquo;s anti-spam internals&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Năm 2021, một lỗi hiển thị ngắn ngủi khiến lý do gỡ nội dung nội bộ của Reddit xuất hiện trước người kiểm duyệt. Từ những ảnh chụp màn hình đó, cùng mã nguồn Reddit cũ và các tài liệu công khai, tác giả phác họa cách nền tảng này chống thư rác. Không có bộ lọc duy nhất nào cả: Spamurai, hệ thống luật chính từ năm 2020, kết hợp tuổi và điểm uy tín tài khoản, báo cáo của người dùng, nhà cung cấp mạng, dấu vân tay trình duyệt qua TLS và user-agent, tên miền email cùng thói quen đăng bài. Bên cạnh đó là danh sách tên miền bị cấm từ năm 2012, các thế hệ REV1 và REV2, bộ lọc từ khóa, việc tải trang được liên kết để tìm mã định danh chung giữa các tên miền, và dịch vụ phân loại ảnh bên ngoài.&lt;/p&gt;
&lt;p&gt;Bài viết cũng cho thấy từng tín hiệu riêng lẻ có thể mong manh đến mức nào. Với Perspective API của Google, chỉ cần chèn vài cặp chữ cái ngẫu nhiên, điểm thư rác của một đoạn văn đã giảm từ 86% xuống 1%. Vì vậy hệ thống thực tế phải tổng hợp nhiều nguồn dữ liệu và học thêm từ hành động của người kiểm duyệt. Tác giả cho rằng giờ đây công bố các chi tiết này đã an toàn, vì Perspective API đóng cửa trong năm 2026 và thư rác do mô hình ngôn ngữ lớn tạo ra buộc Reddit phải thay đổi đáng kể. Đây là lời nhắc rằng cơ chế chống lạm dụng luôn phải thích nghi theo kỹ thuật của kẻ tấn công.&lt;/p&gt;
&lt;h2 id="understanding-the-go-runtime-profiling"&gt;&lt;a href="https://internals-for-interns.com/posts/go-runtime-profiling/" rel="noopener noreferrer"&gt;Understanding the Go Runtime: Profiling&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Bài viết giải thích cơ chế bên trong của năm loại hồ sơ hiệu năng trong Go: CPU, vùng nhớ động, chờ, tranh chấp khóa và goroutine. Dù trông khác nhau, tất cả đều ghi lại ngăn xếp lời gọi và xuất ra cùng một định dạng &lt;code&gt;pprof&lt;/code&gt; nén: mỗi mẫu chỉ giữ danh sách mã vị trí cùng các giá trị đo, còn vị trí, hàm và chuỗi được lưu trong các bảng dùng chung để hàng nghìn ngăn xếp giống nhau không bị lặp dữ liệu. Điểm khác biệt thực sự nằm ở cách mỗi loại hồ sơ ghi nhận ngăn xếp.&lt;/p&gt;
&lt;p&gt;Hồ sơ CPU thu thập theo dòng: hệ điều hành gửi tín hiệu ngắt luồng khoảng 100 lần mỗi giây, ngăn xếp được ghi vào bộ đệm vòng không khóa rồi một goroutine nền liên tục đưa vào bảng tổng hợp, chấp nhận bỏ một số mẫu để không làm dừng luồng đang chạy. Hồ sơ vùng nhớ động tổng hợp tại chỗ: bộ cấp phát lấy mẫu khoảng một lần cho mỗi 512 KiB, gộp ngay vào bảng nhóm theo ngăn xếp để ước lượng cả lượng cấp phát lẫn lượng còn sử dụng. Hồ sơ chờ và tranh chấp khóa dùng lại hạ tầng đó nhưng đo thời gian chờ, một bên ghi goroutine kết thúc chờ, bên kia ghi khóa gây tranh chấp khi được nhả, rồi nhân kết quả lên để ước lượng tổng thực. Riêng hồ sơ goroutine là ảnh chụp theo yêu cầu, lấy ngăn xếp của mọi goroutine đang sống qua cơ chế hai pha để giảm thời gian tạm dừng chương trình. Bài học chung là runtime âm thầm xử lý rất nhiều phức tạp để việc đo hiệu năng trông thật đơn giản.&lt;/p&gt;
&lt;h2 id="incident-report-cve-2026-lgtm"&gt;&lt;a href="https://nesbitt.io/2026/06/26/incident-report-cve-2026-lgtm.html" rel="noopener noreferrer"&gt;Incident Report: CVE-2026-LGTM&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Đây là một báo cáo sự cố châm biếm về cuộc tấn công chuỗi cung ứng phần mềm giả tưởng, trong đó gói độc hại &lt;code&gt;foxhole-lz4&lt;/code&gt; vượt qua bảy cổng bảo mật dùng AI, mỗi cổng thất bại một kiểu. Cổng phát hành tin vào dòng chữ trắng bị giấu khẳng định gói đã được kiểm tra thủ công. Một trình quét bị phân tâm bởi hình minh họa nhúng trong gói, ba trình quét thương mại hết cửa sổ ngữ cảnh vì nội dung độn trước khi đọc tới mã độc. Hệ thống duy nhất phát hiện đúng hành vi đánh cắp thông tin lại bị trợ lý AI của kho mã gạt đi như mã đo đạc thông thường, còn người dùng báo cáo thật sự thì bị bot phân loại giới hạn truy cập vì “hoạt động đáng ngờ”.&lt;/p&gt;
&lt;p&gt;Rồi hai tác nhân đánh giá tốn hơn 41.000 USD để tranh cãi với nhau, rồi tác nhân khắc phục sự cố ký cả một “hiệp ước” với tác nhân của kẻ tấn công. Cuối cùng, chính một tệp bẫy do con người đặt sẵn mới chặn được cuộc tấn công. Nguyên nhân gốc được tóm gọn: bảy mô hình xếp nối tiếp, sáu cái giả định cái khác đã đọc mã, cái thứ bảy đọc xong thì xin lỗi. Bài viết phê phán việc trao quyền rộng cho tác nhân tự động mà thiếu kiểm chứng: nhiều lớp dùng cùng mô hình nền chỉ tạo ảo giác đa dạng; dữ liệu không đáng tin cần tách khỏi chỉ dẫn; lỗi của mô hình không được hiểu thành “không có phát hiện”; và con người trong vòng kiểm soát phải là cơ chế vận hành thật chứ không chỉ là điều khoản trong hợp đồng.&lt;/p&gt;
&lt;h2 id="cors-what-is-it-protecting"&gt;&lt;a href="https://sanyamserver.online/posts/cors/" rel="noopener noreferrer"&gt;CORS: What is it protecting?&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Bài viết làm rõ một hiểu lầm phổ biến: CORS là cơ chế bảo mật do trình duyệt thực thi, không phải hàng rào bảo vệ máy chủ. Với yêu cầu khác nguồn, trình duyệt gắn tiêu đề &lt;code&gt;Origin&lt;/code&gt;, đối chiếu phản hồi với &lt;code&gt;Access-Control-Allow-Origin&lt;/code&gt; và chỉ cho mã JavaScript đọc nội dung khi nguồn được phép. Máy chủ vẫn đã nhận và xử lý yêu cầu, trình duyệt chỉ vứt bỏ phản hồi; thử bằng &lt;code&gt;curl&lt;/code&gt; hay Postman sẽ thấy máy chủ trả lời bình thường. Các yêu cầu phức tạp hơn, như dùng phương thức khác GET/POST/HEAD, tiêu đề tùy chỉnh hay nội dung JSON, sẽ có bước kiểm tra trước bằng &lt;code&gt;OPTIONS&lt;/code&gt; để hỏi máy chủ chấp nhận nguồn, phương thức và tiêu đề nào.&lt;/p&gt;
&lt;p&gt;Điều CORS thực sự bảo vệ là việc đọc dữ liệu: một trang độc hại không thể lấy thông tin tài khoản ngân hàng của bạn dù trình duyệt đang giữ cookie đăng nhập. Tuy nhiên, CORS không ngăn được CSRF. Một yêu cầu POST dạng biểu mẫu không cần kiểm tra trước, nên máy chủ đã thực thi xong và gây thay đổi dữ liệu trước khi trình duyệt chặn việc đọc phản hồi. Để phòng CSRF, cần bảo vệ ở phía yêu cầu bằng thuộc tính cookie &lt;code&gt;SameSite&lt;/code&gt;, mã chống CSRF hoặc kiểm tra &lt;code&gt;Origin&lt;/code&gt; và &lt;code&gt;Referer&lt;/code&gt; trên máy chủ. Nói cách khác, CORS và chống CSRF giải quyết hai bài toán khác nhau và không nên bị nhầm lẫn.&lt;/p&gt;
&lt;h2 id="data-access-patterns-that-makes-your-cpu-really-angry"&gt;&lt;a href="https://blog.weineng.me/posts/slowest_add/" rel="noopener noreferrer"&gt;Data Access Patterns That Makes Your CPU Really Angry&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Bài viết thực hiện một thí nghiệm ngược đời: tìm thứ tự truy cập chậm nhất để cộng các phần tử của một mảng 2^26 phần tử. Quét tuần tự nhanh nhất, khoảng 132 triệu chu kỳ, nhờ bộ nhớ đệm và cơ chế nạp trước của CPU, còn truy cập ngẫu nhiên, vốn được xem là trường hợp tệ nhất, mất khoảng 1,57 tỷ chu kỳ. Tác giả lần lượt thử các mẫu truy cập cách nhau một dòng bộ nhớ đệm, cách nhau một trang nhớ, và kết hợp cả hai. Nhảy theo trang đặc biệt hiệu quả vì bộ nạp trước phần cứng không vượt qua ranh giới trang 4KB, đồng thời khiến nhiều địa chỉ tranh nhau cùng một tập trong bộ nhớ đệm L1.&lt;/p&gt;
&lt;p&gt;Mẫu tệ nhất là bước nhảy tám trang, tức khoảng 32KB giữa hai lần đọc, mất khoảng 2,06 tỷ chu kỳ, chậm hơn truy cập ngẫu nhiên khoảng 33%. Nó cùng lúc làm bão hòa bộ nhớ đệm, vô hiệu hóa việc nạp trước, xóa bỏ khả năng tái sử dụng dòng bộ nhớ đệm và phá tính cục bộ của các mục nhập bảng trang, làm tăng chi phí dịch địa chỉ. Tác giả còn thử gom truy cập theo hàng và ngân hàng DRAM để giảm khả năng xử lý song song, dù ánh xạ địa chỉ vật lý không được công bố nên phần này chỉ mang tính xấp xỉ. Hiểu vì sao một mẫu truy cập làm CPU chậm như vậy giúp lập trình viên nhận ra các giới hạn của bộ nhớ đệm, bộ nạp trước và cơ chế dịch địa chỉ trong mã nguồn hằng ngày.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Bài viết đã được viết lại bởi Claude Code với Opus 5.5 vào ngày 27/09/2026.&lt;/em&gt;&lt;/p&gt;</description></item><item><title>Newsletter #126</title><link>https://miti99.com/post/2026/07/23/</link><pubDate>Thu, 23 Jul 2026 00:00:00 +0700</pubDate><guid>https://miti99.com/post/2026/07/23/</guid><description>&lt;p&gt;&lt;em&gt;Mời bạn thưởng thức Newsletter #126.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="2-portable-executables"&gt;&lt;a href="https://anteiku.fun/papers/an-introduction-to-modern-malware-development-for-red-teams/02_portable_executables/" rel="noopener noreferrer"&gt;2. Portable Executables&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Bài viết là chương thứ hai trong loạt tài liệu nhập môn về phát triển mã độc hiện đại dành cho đội red team, tập trung vào định dạng Portable Executable (PE), cấu trúc chung của tệp thực thi, thư viện liên kết động và trình điều khiển trên Windows. Tác giả lần lượt đi qua DOS Header, DOS Stub, PE Header, Optional Header, bảng phân đoạn cùng các phân đoạn quen thuộc như &lt;code&gt;.text&lt;/code&gt;, &lt;code&gt;.data&lt;/code&gt;, &lt;code&gt;.rdata&lt;/code&gt;, &lt;code&gt;.rsrc&lt;/code&gt; và &lt;code&gt;.reloc&lt;/code&gt;. Điểm cốt lõi là PE mô tả đồng thời cách tệp nằm trên đĩa và cách Windows Loader ánh xạ nó vào bộ nhớ: các trường trong Header cho biết kiến trúc, điểm bắt đầu thực thi, mức căn chỉnh, kích thước ảnh và vị trí những bảng dữ liệu quan trọng, còn thuộc tính của từng phân đoạn quyết định nội dung cùng quyền đọc, ghi hoặc thực thi khi chương trình chạy.&lt;/p&gt;
&lt;p&gt;Phần sau chuyển sang góc nhìn của các sản phẩm bảo mật khi phân tích tĩnh, tức là đánh giá tệp trước khi nó được thực thi. Những dấu hiệu thường được xem xét gồm hàm băm, chữ ký số, độ hỗn loạn của dữ liệu, điểm bắt đầu nằm ở vị trí bất thường, phân đoạn mang quyền &lt;code&gt;RWX&lt;/code&gt;, chênh lệch lớn giữa kích thước trên đĩa và trong bộ nhớ, tên phân đoạn lạ, bảng nhập khẩu thưa thớt và các tổ hợp API đáng ngờ. Với lập trình viên mới, bài viết cho thấy vì sao hiểu rõ cấu trúc PE là nền tảng chung cho cả phân tích phần mềm độc hại lẫn xây dựng cơ chế phát hiện những tệp bị đóng gói hoặc làm rối.&lt;/p&gt;
&lt;h2 id="how-4-bytes-of-padding-make-array-clearing-49-faster"&gt;&lt;a href="https://blog.andr2i.com/posts/2026-06-22-optimization-catalog-how-4-bytes-of-padding-make-array-clearing-49-faster" rel="noopener noreferrer"&gt;How 4 bytes of padding make array clearing 49% faster&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Bài viết thuộc loạt &amp;ldquo;Optimization catalog&amp;rdquo;, phân tích một hiện tượng căn chỉnh bộ nhớ trong Go: khi mảng &lt;code&gt;uint32&lt;/code&gt; nằm ở độ lệch 4 byte so với ranh giới 8 byte, thao tác xóa mảng chậm đi rõ rệt. Trên bộ xử lý Intel được đo, chỉ cần thêm 4 byte đệm để mảng được căn chỉnh 8 byte là thông lượng tăng khoảng 49%; trên AMD mức cải thiện chỉ khoảng 9%, còn máy ARM gần như không bị ảnh hưởng. Mã hợp ngữ cho thấy toàn bộ việc xóa do một lệnh &lt;code&gt;REP STOSQ&lt;/code&gt; đảm nhận. Dựa trên bộ đếm hiệu năng, tác giả phỏng đoán đường xử lý nhanh của Intel ghi trọn từng dòng bộ nhớ đệm mà không cần đọc trước; khi mảng lệch 4 byte, cứ mỗi ranh giới 64 byte lại có một lần ghi 8 byte cắt ngang, nên không dòng nào được phủ trọn và đường xử lý nhanh không thể kích hoạt.&lt;/p&gt;
&lt;p&gt;Các phép đo tiếp theo cho thấy SIMD, dù dùng hàm nội tại thử nghiệm của Go hay hợp ngữ viết tay, không vượt trội so với &lt;code&gt;STOSQ&lt;/code&gt;. Biến thể ghi thẳng ra bộ nhớ có lúc nhanh hơn nhưng kết quả dao động mạnh, vì phụ thuộc việc nhân hệ điều hành cấp phát các trang liền mạch hay rời rạc. Bài viết cũng gợi ý gắn số thế hệ cho từng phần tử để hiếm khi phải xóa thật, nhưng thẻ thế hệ làm mảng lớn hơn và có thể khiến dữ liệu không còn vừa bộ nhớ đệm. Bài học rút ra là hiệu quả tối ưu phụ thuộc bộ xử lý, lệnh máy và kích thước dữ liệu, nên luôn cần đo trên phần cứng thực tế.&lt;/p&gt;
&lt;h2 id="engineering-high-performance-parsers-with-data-oriented-design"&gt;&lt;del&gt;&lt;a href="https://www.arshad.fyi/writings/engineering-high-performance-parsers" rel="noopener noreferrer"&gt;Engineering High-Performance Parsers with Data-Oriented Design&lt;/a&gt;&lt;/del&gt;&lt;/h2&gt;&lt;p&gt;Bài viết chia sẻ nguyên tắc thiết kế phía sau Yuku, một trình phân tích cú pháp JavaScript và TypeScript viết bằng Zig, với luận điểm chính: khi ngữ pháp đã đúng, hiệu năng gần như được quyết định bởi cách biểu diễn cây cú pháp trong bộ nhớ chứ không phải thuật toán phân tích. Thay vì các nút cấp phát rời rạc và liên kết bằng con trỏ, vốn gây hàng chục nghìn lần cấp phát và những lần truy cập bộ nhớ khó đoán, Yuku lưu nút trong một mảng phẳng và dùng chỉ số &lt;code&gt;u32&lt;/code&gt; làm tham chiếu. Chỉ số chỉ bằng nửa con trỏ và cả cây được giải phóng trong một thao tác, còn thuật toán đệ quy vẫn quen thuộc.&lt;/p&gt;
&lt;p&gt;Tác giả áp dụng cùng tư duy cho nhiều phần khác: tách tải trọng và vị trí nguồn thành các cột riêng để mỗi lượt duyệt chỉ chạm vào dữ liệu cần thiết; lưu danh sách con có độ dài thay đổi bằng cặp vị trí và độ dài; tái sử dụng vùng đệm tạm qua các lời gọi đệ quy; biểu diễn chuỗi như phạm vi byte trong mã nguồn; mã hóa thông tin toán tử ngay trong bit của thẻ và nén bảng Unicode bằng cách loại bỏ mẫu trùng lặp. Các ràng buộc về kích thước và bố cục được kiểm tra ngay lúc biên dịch để ngăn suy giảm ngoài ý muốn. Vì cây chỉ chứa chỉ số và độ lệch, chính biểu diễn trong bộ nhớ có thể dùng làm định dạng truyền dữ liệu, cho phép chuyển cây sang JavaScript bằng một vùng đệm liên tục thay vì tuần tự hóa qua JSON.&lt;/p&gt;
&lt;h2 id="how-to-learn-go-fast-a-practical-roadmap-for-senior-backend-developers"&gt;&lt;a href="https://dev.to/nazar-boyko/how-to-learn-go-fast-a-practical-roadmap-for-senior-backend-developers-18l5" rel="noopener noreferrer"&gt;How To Learn Go Fast: A Practical Roadmap For Senior Backend Developers&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Bài viết đưa ra lộ trình học Go cho lập trình viên phía máy chủ đã có kinh nghiệm, đặc biệt là người chuyển từ PHP, Laravel hoặc Symfony. Tác giả cho rằng không cần học lại kiến thức nền về API, cơ sở dữ liệu hay hàng đợi, mà nên tận dụng chúng và tập trung vào cách Go tổ chức chương trình: mã nguồn minh bạch, lỗi là giá trị, giao diện nhỏ được đáp ứng ngầm, phụ thuộc được nối rõ ràng và thư viện chuẩn thường được ưu tiên hơn một bộ khung toàn diện.&lt;/p&gt;
&lt;p&gt;Lộ trình bắt đầu với cú pháp, kiểu dữ liệu, gói và xử lý lỗi, rồi nhanh chóng chuyển sang xây dựng công cụ dòng lệnh, dịch vụ HTTP và hệ thống xử lý công việc đồng thời, vì làm dự án nhỏ từ sớm giúp hiểu ngôn ngữ nhanh hơn việc chỉ đọc hướng dẫn cú pháp. Kế hoạch 12 tuần đi từ nền tảng đến PostgreSQL, kiểm thử tích hợp, phát hiện tranh chấp dữ liệu, ngữ cảnh hủy, đo hiệu năng, quan sát hệ thống và hoàn thiện một dự án đưa vào hồ sơ năng lực; kiểm thử, xử lý lỗi và vận hành được học song song chứ không bị xem là phần phụ. Bài viết cũng có phiên bản rút gọn vài ngày cho người cần sớm làm việc với mã nguồn Go, nhưng vẫn nhấn mạnh thói quen sản xuất như đặt thời hạn cho lời gọi mạng, tắt dịch vụ an toàn, đọc mã nguồn thực tế và đo trước khi tối ưu. Theo tác giả, năng lực Go ở cấp cao thể hiện qua khả năng xây dựng, triển khai, chẩn đoán và duy trì hệ thống thực tế.&lt;/p&gt;
&lt;h2 id="how-to-prepare-for-a-golang-interview-a-practical-guide-for-mid--senior-engineers"&gt;&lt;a href="https://dev.to/nazar-boyko/how-to-prepare-for-a-golang-interview-a-practical-guide-for-mid-senior-engineers-200p" rel="noopener noreferrer"&gt;How To Prepare For A Golang Interview: A Practical Guide For Mid &amp;amp; Senior Engineers&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Bài viết là bản đồ ôn tập toàn diện cho phỏng vấn Go ở cấp trung và cao cấp, nhấn mạnh rằng nhà tuyển dụng đánh giá khả năng suy luận và thiết kế chứ không chỉ khả năng nhớ cú pháp. Nội dung đi từ giá trị mặc định, gói, lát cắt, ánh xạ, con trỏ và giao diện đến xử lý lỗi, &lt;code&gt;defer&lt;/code&gt;, &lt;code&gt;panic&lt;/code&gt; và các bẫy bộ nhớ thường gặp. Những chủ đề hay dùng để phân loại ứng viên được giải thích kỹ, chẳng hạn mảng nền dùng chung giữa các lát cắt, giao diện chứa con trỏ rỗng nhưng bản thân không rỗng, quy tắc đóng kênh và cách chọn giữa khóa tương hỗ với kênh, vì đây cũng là nguồn lỗi phổ biến trong thực tế.&lt;/p&gt;
&lt;p&gt;Phần dành cho ứng viên cao cấp mở rộng sang bộ lập lịch G-M-P, mô hình bộ nhớ, phân tích thoát, bộ thu gom rác, rò rỉ goroutine, hủy công việc bằng ngữ cảnh, phát hiện tranh chấp dữ liệu và đo đạc bằng &lt;code&gt;pprof&lt;/code&gt;. Bài viết còn bao phủ kiểm thử, cơ sở dữ liệu, HTTP, tắt dịch vụ an toàn, thiết kế hệ thống và kiến thức vận hành ngoài phạm vi ngôn ngữ. Theo tác giả, một câu trả lời tốt cần nêu được lựa chọn, đánh đổi và cách xác minh bằng kiểm thử hoặc đo đạc. Cuối bài là danh sách lỗi phổ biến, các bài tập có giới hạn thời gian như nhóm xử lý công việc, giới hạn tốc độ, cơ chế thử lại, cùng bảng kiểm giúp ứng viên tự đánh giá khả năng viết mã đúng và xử lý tình huống sản xuất.&lt;/p&gt;
&lt;h2 id="shard-your-locks-benchmarking-6-go-cache-designs"&gt;&lt;a href="https://strebkov.dev/posts/shard-your-locks/" rel="noopener noreferrer"&gt;Shard your locks: benchmarking 6 Go cache designs&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Bài viết so sánh sáu cách xây dựng bộ nhớ đệm &lt;code&gt;string → string&lt;/code&gt; đồng thời chỉ bằng thư viện chuẩn Go: ánh xạ không khóa (làm mốc), một &lt;code&gt;Mutex&lt;/code&gt;, một &lt;code&gt;RWMutex&lt;/code&gt;, &lt;code&gt;sync.Map&lt;/code&gt;, ánh xạ chia 256 mảnh và sao chép khi ghi. Các phép đo chạy với nhiều tỷ lệ đọc/ghi và từ một đến tám lõi. Kết quả cho thấy một khóa duy nhất không mở rộng được: ở tám lõi, tải chỉ đọc với &lt;code&gt;Mutex&lt;/code&gt; còn chậm hơn khi chạy một lõi. &lt;code&gt;RWMutex&lt;/code&gt;, lựa chọn theo phản xạ khi thao tác đọc bị tranh chấp, chỉ tăng khoảng gấp đôi rồi chững lại vì bộ đếm người đọc trở thành điểm tranh chấp mới, và còn chậm hơn &lt;code&gt;Mutex&lt;/code&gt; khi có nhiều thao tác ghi.&lt;/p&gt;
&lt;p&gt;Thiết kế chia mảnh, mỗi mảnh có ánh xạ và khóa riêng, là phương án duy nhất luôn đứng gần đầu ở mọi loại tải và nhanh hơn một khóa duy nhất tới tám lần, vì các thao tác trên những khóa khác nhau có thể chạy song song. Sao chép khi ghi dẫn đầu ở tải chỉ đọc nhưng gần như tụt về không ngay khi có thao tác ghi, vì mỗi lần ghi phải sao chép toàn bộ ánh xạ, nên chỉ hợp với dữ liệu hiếm khi cập nhật. Bài viết cũng chỉ ra rằng phân bố khóa lệch có thể giúp thao tác đọc nhanh hơn nhờ tính cục bộ của bộ nhớ đệm, nhưng lại dồn tranh chấp vào vài mảnh khi ghi. Số mảnh 256 là điểm cân bằng trong phép đo này, không phải hằng số chung; trước khi chọn cấu trúc đồng thời, cần đo với phân bố khóa, phần cứng và tải thực tế.&lt;/p&gt;
&lt;h2 id="the-join-that-should-be-a-bug"&gt;&lt;a href="https://kronotop.com/blog/the-join-that-should-be-a-bug/" rel="noopener noreferrer"&gt;The .join() That Should Be a Bug&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Bài viết giải thích cách Kronotop phục vụ hàng nghìn kết nối trong khi gần như mọi lệnh đều phải chờ lời gọi mạng tới FoundationDB hoặc thao tác đọc ghi trên hệ thống tệp. Tác giả so sánh hai mô hình quen thuộc: Redis dùng một luồng cho mọi kết nối, giữ được rất nhiều kết nối nhưng không cho phép bất kỳ lệnh nào chặn; PostgreSQL dùng một tiến trình cho mỗi kết nối, cho phép viết mã tuần tự có chặn nhưng tốn tài nguyên khi số kết nối tăng. Kronotop chọn hướng thứ ba, tách việc giữ kết nối khỏi phần công việc phải chờ để kết hợp ưu điểm của cả hai.&lt;/p&gt;
&lt;p&gt;Một nhóm nhỏ luồng vòng lặp sự kiện Netty nhận dữ liệu, phân tích lệnh và gửi phản hồi mà không bao giờ chặn. Công việc đọc đĩa hoặc gọi mạng được chuyển sang luồng ảo của Java, nơi lời gọi &lt;code&gt;.join()&lt;/code&gt;, vốn thường bị xem là lỗi trong mã bất đồng bộ, có thể chờ theo phong cách tuần tự mà không chiếm giữ luồng hệ điều hành bên dưới. Khi có kết quả, việc ghi phản hồi luôn được đưa về đúng vòng lặp Netty sở hữu kết nối, giúp quy tắc đồng thời luôn rõ ràng. Mỗi kết nối đồng thời là một phiên làm việc, lưu trạng thái xác thực, không gian tên và giao dịch đang mở; khi kết nối bị đóng hoặc đặt lại, giao dịch và tài nguyên liên quan được hủy và dọn dẹp an toàn.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Bài viết đã được viết lại bởi Claude Code với Opus 5.5 vào ngày 27/09/2026.&lt;/em&gt;&lt;/p&gt;</description></item><item><title>Newsletter #125</title><link>https://miti99.com/post/2026/07/13/</link><pubDate>Mon, 13 Jul 2026 00:00:00 +0700</pubDate><guid>https://miti99.com/post/2026/07/13/</guid><description>&lt;p&gt;&lt;em&gt;Mời bạn thưởng thức Newsletter #125.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="275---khi-big-tech-chuyển-mình-thành-ai-factory"&gt;&lt;a href="https://newsletter.grokking.org/p/275-khi-big-tech-chuyen-minh-thanh" rel="noopener noreferrer"&gt;275 - Khi Big Tech chuyển mình thành AI Factory&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Bài viết chia sẻ góc nhìn thực tế về quá trình một tập đoàn công nghệ lớn chuyển mình thành “AI factory”. Tác giả chia tự động hóa bằng tác nhân thành năm cấp độ: từ việc kỹ sư sao chép mã nguồn do chatbot tạo ra, đến tác nhân hỗ trợ theo từng chỉ dẫn, tự đi từ đặc tả tới yêu cầu hợp nhất kèm kiểm thử, rồi con người chỉ còn điều phối khâu đánh giá và triển khai, và cuối cùng là tác nhân tự triển khai, giám sát môi trường thật và xử lý sự cố. Mỗi bậc đòi hỏi tác nhân chủ động hơn, nên doanh nghiệp phải đầu tư vào môi trường vận hành an toàn, kiểm thử, phân quyền và cơ chế chặn các thao tác nguy hiểm.&lt;/p&gt;
&lt;p&gt;Vai trò của kỹ sư vì thế dịch chuyển từ tự tay làm từng việc sang “đóng gói” công việc thành đặc tả có ranh giới, mục tiêu và tiêu chí nghiệm thu rõ ràng, điều khá khó với các kỹ sư mới vào nghề vốn quen viết mã theo kiểu mày mò. Đánh giá mã nguồn vẫn cần con người vì nhiều yêu cầu nằm trong bối cảnh ẩn; các hệ thống đánh giá tự động mới được chấp nhận khoảng một nửa số góp ý và tốn nhiều token khi mở rộng. Ngôn ngữ lập trình dần trở nên dễ thay thế, nhưng kiến thức về hệ điều hành, mạng, bảo mật, hạ tầng, cấu trúc dữ liệu và xử lý đồng thời vẫn là nền tảng để kỹ sư thẩm định và chịu trách nhiệm về kết quả do AI tạo ra.&lt;/p&gt;
&lt;h2 id="the-8020-rule-for-your-whole-life"&gt;&lt;a href="https://yewjin.substack.com/p/always-be-curious" rel="noopener noreferrer"&gt;The 80/20 rule for your whole life&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Tác giả Yew Jin Lim đề xuất áp dụng nguyên tắc 80/20 cho cả cuộc sống: dành phần lớn nguồn lực xây dựng một nền tảng ổn định, còn phần nhỏ làm “phòng thí nghiệm” cho các thử nghiệm có kiểm soát. Nguyên tắc xuyên suốt là đánh giá cả danh mục chứ không phán xét từng vị thế riêng lẻ. Ví dụ đến từ chính trải nghiệm của tác giả: nhiều dự án tự chọn tại Google phần lớn không đi đến đâu nhưng cộng lại đã định hình năng lực của anh; tài khoản hưu trí tăng khoảng mười lần nhờ đóng góp tự động và kỷ luật không can thiệp; còn việc thiền tập sâu dần nhờ thực hành mỗi ngày.&lt;/p&gt;
&lt;p&gt;Ba thói quen cốt lõi được rút ra là mở rộng kiến thức theo sự tò mò mà không đòi hỏi lợi ích ngay, làm chủ quyết định bằng cách tách cảm xúc khỏi từng kết quả, và kiên trì tích lũy để lãi kép phát huy tác dụng. Tác giả phân biệt rõ giao dịch ngắn hạn với đầu tư dài hạn và nhấn mạnh rằng khi nền tảng vẫn vững, nhiều lúc “không làm gì” lại là lựa chọn tốt nhất. Thử nghiệm không cần luôn thành công, miễn là mỗi lần đủ nhỏ để thất bại trở thành học phí thay vì thảm họa và phần nền tảng không bị đem ra đánh cược.&lt;/p&gt;
&lt;h2 id="three-bad-managers"&gt;&lt;a href="https://randsinrepose.com/archives/three-bad-managers/" rel="noopener noreferrer"&gt;Three Bad Managers&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Tác giả kể về ba người quản lý thông minh, thành công nhưng lại rất tệ trong việc quản lý con người, từ đó phân biệt lãnh đạo (vạch ra hướng đi cho tổ chức) với quản lý (giúp nhân viên hiểu mình đang ở đâu và vận hành công việc hằng ngày). “Người nghệ sĩ” là một người hướng nội tài năng, chỉ quan tâm đến sản phẩm sáng tạo và thờ ơ trước các vấn đề về con người. “Nhà độc tài” dùng quyền lực và sự áp đảo để chi phối thảo luận, chẳng hạn khơi lại những hướng thiết kế mà đội đã cân nhắc kỹ thay vì tin vào chuyên môn của họ. “Người cầm dao” thì thất thường đến mức mọi nỗ lực giao tiếp bằng lời hay bằng văn bản đều không hiệu quả.&lt;/p&gt;
&lt;p&gt;Với mỗi người, tác giả chọn một cách thích nghi riêng: viết sẵn bản đánh giá chi tiết trước buổi gặp riêng với người nghệ sĩ, chuẩn bị chuyên môn thật kỹ để có thể phản biện ngang hàng với nhà độc tài, và đơn giản là tránh xa để tự làm việc với người cầm dao. Thông điệp cốt lõi là nhân viên hầu như không thể thay đổi cấp trên, nhất là khi họ ở vị trí cao, nhưng có thể thay đổi cách chuẩn bị, truyền đạt và phối hợp. Mỗi mối quan hệ công việc là một sự điều chỉnh, và khả năng thích nghi với từng người là một kỹ năng thiết yếu, không đồng nghĩa với việc chấp nhận cách quản lý kém.&lt;/p&gt;
&lt;h2 id="cross-team-pragmatics"&gt;&lt;a href="https://philcalcado.com/2026/06/01/cross_team_pragmatics.html" rel="noopener noreferrer"&gt;Cross-Team Pragmatics&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Phil Calçado phân tích vì sao phối hợp giữa các đội thường trở nên chậm chạp và căng thẳng khi tổ chức lớn dần. Các nguyên nhân gốc rễ khá dễ đoán: văn hóa đánh giá hiệu suất cá nhân khiến mỗi đội tối ưu cục bộ, mục tiêu chiến lược mâu thuẫn nhau khi truyền qua nhiều cấp (cắt giảm chi phí hạ tầng trong khi đội sản phẩm phải tăng doanh thu), trách nhiệm không đi kèm quyền sở hữu, và những bất đồng nhỏ liên tục bị đẩy lên lãnh đạo cấp cao thay vì được giải quyết nhanh. Giải pháp bền vững là một chiến lược kỹ thuật thống nhất, gắn cách doanh nghiệp cạnh tranh với cấu trúc đội ngũ, phạm vi sở hữu, cơ chế khuyến khích và cách ra quyết định.&lt;/p&gt;
&lt;p&gt;Trong lúc chờ những thay đổi chậm rãi đó, tác giả đề xuất vài công cụ thực dụng để xử lý triệu chứng. Hiến chương đội biến các giả định ngầm thành ranh giới, phạm vi trách nhiệm và cách tương tác rõ ràng, giảm việc phải thương lượng lại liên tục. Phân tích dòng giá trị đi theo luồng công việc thực tế thay vì sơ đồ tổ chức để lộ ra những lần bàn giao thừa. Nguyên tắc 80% yêu cầu mỗi đội tự hoàn thành phần lớn công việc mà không bị đội khác chặn trên đường găng, còn mô hình Team Topologies giúp chủ động chọn kiểu tương tác giữa các đội. Các công cụ này chỉ mang lại ổn định tạm thời; việc lâu dài vẫn là liên tục điều chỉnh tổ chức theo cách giá trị thực sự được tạo ra.&lt;/p&gt;
&lt;h2 id="technological-involution"&gt;&lt;a href="https://rohan.ga/blog/technological-involution/" rel="noopener noreferrer"&gt;Technological Involution&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Bài viết lập luận rằng ngành công nghệ đang rơi vào trạng thái “nội quyển”: nhiều nhân lực và vốn hơn nhưng ý tưởng ngày càng đồng dạng. Theo tác giả, những ý tưởng hiển nhiên đã được khai thác gần hết, phần mềm thông thường dần bị hàng hóa hóa, còn văn hóa khởi nghiệp ở Thung lũng Silicon khiến nhà sáng lập tiếp nhận câu chuyện từ những người có ảnh hưởng thay vì tự hình thành niềm tin. Sự nổi tiếng tạo ra niềm tin chứ không phải giá trị thật, và xã hội cũng dần mất đi sự lạc quan về tương lai công nghệ. Trong khi đó, những bài toán khó nằm ở phần cứng, vi mạch và sự phức tạp trong vận hành doanh nghiệp, nơi phần lớn nhà sáng lập hiện nay còn thiếu kinh nghiệm thực tế.&lt;/p&gt;
&lt;p&gt;AI rất giỏi lập trình nhưng có xu hướng đưa ra câu trả lời gần với số đông thay vì những góc nhìn khác thường nhưng có tác động lớn. Tác giả cho rằng chính giới hạn này lại để dành cơ hội cho sự sáng tạo của con người: người hiểu sâu lĩnh vực mình muốn thay đổi, kiểm nghiệm giả thuyết bằng tư duy nguyên lý đầu tiên và dám theo đuổi quan điểm riêng sẽ ngày càng có giá trị. Lối thoát khỏi nội quyển là quay về động lực nội tại, xây dựng thứ gì đó vì nó thực sự hấp dẫn về mặt trí tuệ thay vì tối ưu theo các chỉ số khởi nghiệp, và sự thành công của các hình thức truyền thông mới là minh chứng rằng sản phẩm có chủ đích vẫn có thể lan tỏa nhờ giá trị nội tại.&lt;/p&gt;
&lt;h2 id="fintech-engineering-handbook"&gt;&lt;a href="https://w.pitula.me/fintech-engineering-handbook/" rel="noopener noreferrer"&gt;Fintech Engineering Handbook&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Cẩm nang tổng hợp các nguyên tắc và mẫu thiết kế khi xây dựng hệ thống mà tiền là đối tượng trung tâm, xoay quanh ba yêu cầu: không tự tạo ra dữ liệu hay tiền, không làm mất dữ liệu, và không tin tưởng bất kỳ thành phần nào. Về biểu diễn tiền, tác giả so sánh số dấu phẩy động (hầu như không nên dùng), số có độ chính xác tùy ý, số nguyên theo đơn vị nhỏ nhất và số hữu tỉ, đồng thời cảnh báo rằng số trong JSON thường bị trình phân tích đọc thành số dấu phẩy động. Giao dịch được ghi bằng sổ cái kép, trong đó tiền chỉ di chuyển giữa các tài khoản và số dư được suy ra từ các bút toán; thời điểm phát sinh, thời điểm ghi sổ và thời điểm thanh toán cũng cần được lưu riêng.&lt;/p&gt;
&lt;p&gt;Phần thực thi bao quát tạm giữ tiền để tránh chi tiêu hai lần, tính lũy đẳng với khóa định danh tường minh, khả năng tiếp tục sau sự cố và giao nhận ít nhất một lần. Với nhà cung cấp bên ngoài, hệ thống cần kiểm tra lược đồ ở ranh giới, lưu mọi yêu cầu và phản hồi, xác minh chữ ký của thông báo đẩy rồi truy vấn lại nguồn chính thức, và đối soát độc lập để phát hiện chênh lệch. Bản ghi đã hạch toán là bất biến, sai sót được sửa bằng bút toán bù trừ, còn thao tác nhạy cảm cần người khác phê duyệt. Các ví dụ về rút tiền mã hóa, nạp tiền bằng thẻ và chuyển đổi tài sản cho thấy các nguyên tắc này phối hợp với nhau qua nhiều bước và tình huống lỗi.&lt;/p&gt;
&lt;h2 id="how-to-write-an-effective-software-design-document"&gt;&lt;a href="https://refactoringenglish.com/excerpts/write-an-effective-design-doc/" rel="noopener noreferrer"&gt;How to Write an Effective Software Design Document&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Michael Lynch hướng dẫn cách viết tài liệu thiết kế phần mềm để phát hiện quyết định sai trước khi bắt tay vào triển khai và giúp các bên thống nhất cách giải quyết vấn đề. Tài liệu đáng đầu tư khi dự án có nhiều người phối hợp, kéo dài hơn ba tháng, sẽ chạy lâu dài trên môi trường thật, cần nhiều đội cùng tham gia, yêu cầu còn mơ hồ hoặc có rủi ro nghiêm trọng. Để quyết định nên viết gì, tác giả đưa ra một câu hỏi đơn giản: “cái giá của việc sai là bao nhiêu?”. Những lựa chọn khó đảo ngược về kiến trúc, lưu trữ dữ liệu, bảo mật hay quyền riêng tư cần được phân tích kỹ, còn chi tiết giao diện dễ sửa thì không đáng tốn thời gian.&lt;/p&gt;
&lt;p&gt;Một tài liệu tốt thường có mục tiêu và bối cảnh, phạm vi cùng những gì nằm ngoài phạm vi, sơ đồ luồng dữ liệu và kiến trúc, chỉ tiêu mức dịch vụ đo lường được, các cân nhắc về bảo mật và quyền riêng tư, tiến độ với các mốc cụ thể, cùng danh sách vấn đề còn mở và đã giải quyết. Tài liệu nên tập trung vào những bài toán khó và việc thu thập phản hồi thay vì mô tả mọi chi tiết triển khai, vì khi đó bạn gần như đã viết xong mã nguồn. Tác giả cũng khuyên dùng công cụ vẽ sơ đồ có thể chỉnh sửa, định nghĩa thuật ngữ lạ, ghi lại các phương án thay thế để trả lời trước câu hỏi của người đọc, và viết sao cho cả những bên ngoài nhóm kỹ thuật cũng hiểu được.&lt;/p&gt;
&lt;h2 id="the-cost-yagni-was-never-about"&gt;&lt;a href="https://newsletter.kentbeck.com/p/the-cost-yagni-was-never-about" rel="noopener noreferrer"&gt;The Cost YAGNI Was Never About&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Kent Beck giải thích rằng YAGNI chưa bao giờ là chuyện tiết kiệm công sức viết mã nguồn, mà là nguyên tắc về thời điểm cam kết với một thiết kế. Xây dựng cấu trúc trước khi tính năng thực sự cần đến tạo ra hai khoản chi phí độc lập. Khoản thứ nhất là mất quyền lựa chọn: nhóm đặt cược vào một dự đoán khi chưa đủ thông tin, và khi yêu cầu thực tế khác đi thì phải tốn công đi vòng hoặc gỡ bỏ phần cấu trúc suy đoán đó. Ngay cả khi đoán đúng, nhóm vẫn thiệt vì đã từ bỏ cơ hội xây đúng cấu trúc vào lúc có thông tin tốt hơn; như tác giả nói, chờ đợi không phải lười biếng mà là đang nắm giữ một tài sản.&lt;/p&gt;
&lt;p&gt;Khoản thứ hai là giá trị hiện tại ròng: chi phí bị đẩy lên sớm trong khi giá trị từ tính năng lại đến muộn, nên thiệt hại xảy ra bất kể dự đoán có chính xác hay không. Khi AI khiến việc tạo mã nguồn gần như miễn phí, lý do tiết kiệm công sức của YAGNI không còn, nhưng cả hai khoản chi phí trên vẫn nguyên vẹn. Thậm chí AI còn khiến việc sinh ra các khung phần mềm mang tính suy đoán trở nên quá dễ dàng. Vì vậy, hãy xây dựng cấu trúc khi nhu cầu đã xuất hiện và mang lại đủ thông tin để thiết kế đúng; YAGNI là kỷ luật về kinh tế chứ không phải sự tằn tiện.&lt;/p&gt;
&lt;h2 id="a-cve-dispute"&gt;&lt;a href="https://daniel.haxx.se/blog/2026/06/24/a-cve-dispute/" rel="noopener noreferrer"&gt;a CVE dispute&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Daniel Stenberg kể về tranh chấp CVE đầu tiên của curl kể từ khi dự án trở thành cơ quan có quyền cấp mã lỗ hổng. Với khoảng 30 tỷ bản cài đặt libcurl, mỗi CVE có thể kéo theo một lượng lớn công việc đánh giá, vá lỗi và phát hành lại trên toàn hệ sinh thái. Vì vậy, nhóm curl phân loại báo cáo theo rủi ro thực tế và đôi khi từ chối cấp CVE cho những lỗi có mức độ “thấp hơn cả mức thấp”, nhằm cảnh báo đầy đủ về vấn đề thật nhưng tránh gây báo động cho các kịch bản gần như chỉ tồn tại trên lý thuyết.&lt;/p&gt;
&lt;p&gt;Lỗi bị tranh chấp nằm ở hàm so khớp tên máy chủ với chứng chỉ ký tự đại diện, khi tên máy chủ trong địa chỉ bắt đầu bằng dấu chấm. Để khai thác, nhiều điều kiện bất thường phải cùng xảy ra: người dùng truy cập một tên không hợp lệ với DNS, tên đó được phân giải bằng cách khác như tệp hosts cục bộ, máy chủ giả mạo có chứng chỉ phù hợp và kẻ tấn công phải kiểm soát được máy đó. curl đã sửa lỗi từ tháng 12/2025 nhưng không coi đây là lỗ hổng. Một nhà nghiên cứu bảo mật khiếu nại lên MITRE, và sau nhiều vòng xem xét trong năm 2026, MITRE đồng ý rằng đây chỉ là một lỗi đã được sửa, không phải lỗ hổng vì đòi hỏi kẻ tấn công cục bộ có đặc quyền. Câu chuyện cho thấy không phải lỗi nào cũng cần CVE, và quy trình tranh chấp giúp một bên độc lập xem xét lại quyết định.&lt;/p&gt;
&lt;h2 id="how-llms-actually-work"&gt;&lt;a href="https://www.0xkato.xyz/how-llms-actually-work/" rel="noopener noreferrer"&gt;How LLMs Actually Work&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Bài viết giải thích cách mô hình ngôn ngữ lớn hoạt động mà không đi sâu vào toán học. Văn bản đầu vào được bộ tách từ chia thành các token và chuyển thành mã số, sau đó bảng nhúng biến mỗi mã thành một véc-tơ mang ý nghĩa đã học, nên những token có nghĩa gần nhau sẽ có véc-tơ gần nhau. Vì cơ chế chú ý không tự biết thứ tự, mô hình cần thêm thông tin vị trí; các mô hình hiện đại dùng RoPE, xoay véc-tơ truy vấn và khóa theo một góc phụ thuộc vào vị trí của token. Trong mỗi khối transformer, cơ chế chú ý so khớp truy vấn với khóa để quyết định lấy bao nhiêu thông tin từ véc-tơ giá trị của các token khác, còn nhiều đầu chú ý chạy song song giúp theo dõi cùng lúc các quan hệ ngữ pháp, tham chiếu và mẫu lặp.&lt;/p&gt;
&lt;p&gt;Sau bước trao đổi giữa các token, mạng truyền thẳng xử lý riêng từng véc-tơ và chứa phần lớn tham số cũng như kiến thức của mô hình. Kết nối dư giúp thông tin tích lũy qua nhiều lớp mà không bị ghi đè, còn chuẩn hóa giữ cho độ lớn véc-tơ ổn định khi huấn luyện. Cuối cùng, véc-tơ của token cuối được chuyển thành phân phối xác suất trên toàn bộ từ vựng; mô hình lấy mẫu token tiếp theo, nối vào chuỗi rồi lặp lại, với nhiệt độ quyết định độ đa dạng của đầu ra. Các mô hình hiện đại dùng chung bộ khung này và chỉ khác nhau ở cấu hình, dữ liệu và cách huấn luyện.&lt;/p&gt;
&lt;h3 id="bonus"&gt;Bonus&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;Images:&lt;/strong&gt;
&lt;img src="https://substackcdn.com/image/fetch/$s_!LlLN!,w_1100,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe4d255e1-536e-4f66-ab48-e8baa1bdd4a4_2484x3002.png" alt="RAG vs Graph RAG vs Agentic RAG"&gt;
&lt;img src="https://substackcdn.com/image/fetch/$s_!15Bw!,w_1100,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b8f9d64-3eae-4237-b49f-ffde033f851a_2484x3002.png" alt="Redis Data Structures Every Engineer Should Know"&gt;
&lt;img src="https://substackcdn.com/image/fetch/$s_!-ILW!,w_1100,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F19ca7f08-d615-4769-8599-2aa9aaa5d6bd_2508x3000.png" alt="Design Patterns Cheat Sheet"&gt;
&lt;img src="https://substackcdn.com/image/fetch/$s_!ydOn!,w_1100,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe46aad28-2935-4b87-8cf4-72787b48a9d0_2250x3376.png" alt="Design Patterns Cheat Sheet"&gt;
&lt;img src="https://substackcdn.com/image/fetch/$s_!_6cl!,w_1100,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea738d9d-1ae5-4a26-9a50-79ec7720fd80_2360x2664.jpeg" alt="The Testing Pyramid"&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Bài viết đã được viết lại bởi Claude Code với Opus 5.5 vào ngày 27/09/2026.&lt;/em&gt;&lt;/p&gt;</description></item><item><title>Newsletter #124</title><link>https://miti99.com/post/2026/07/12/</link><pubDate>Sun, 12 Jul 2026 00:00:00 +0700</pubDate><guid>https://miti99.com/post/2026/07/12/</guid><description>&lt;p&gt;&lt;em&gt;Mời bạn thưởng thức Newsletter #124.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="riding-technology-waves"&gt;&lt;a href="https://staysaasy.com/strategy/2026/06/22/riding-technology-waves.html" rel="noopener noreferrer"&gt;Riding Technology Waves&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Các làn sóng công nghệ như Internet, điện toán đám mây, thiết bị di động và AI ngày càng lớn và đến đột ngột hơn; chúng tạo ra doanh nghiệp khổng lồ nhưng cũng xóa sổ nhiều ngành cũ, trong khi in 3D hay thực tế ảo lại thoái trào. Để định hướng sớm mà không bị cuốn theo sự cường điệu, tác giả đề xuất trả lời lần lượt bốn câu hỏi: công nghệ này có thực sự quan trọng, tức có người dùng thật không; nó vận hành thế nào, ở mức giải thích được cho người ngoài ngành; nó mở ra khả năng mới nào, mô tả bằng sự thật cụ thể thay vì khẩu hiệu; và những khả năng đó có tác động đến sản phẩm, chi phí hay khách hàng của doanh nghiệp không.&lt;/p&gt;
&lt;p&gt;Hai cái bẫy phổ biến nằm ở hai thái cực. Lười thay đổi khiến doanh nghiệp phớt lờ tín hiệu thật, dù sai lầm này thường tự điều chỉnh khi công nghệ đã hiển nhiên. Nguy hiểm hơn là nỗi sợ bị xem là lạc hậu: lãnh đạo không hiểu công nghệ chạy theo đám đông, đặt cược vào thứ chưa có người dùng hoặc hủy cả lộ trình để gắn nhãn AI trong khi khách hàng cần tính năng khác. Theo tác giả, xoay trục mù quáng là sai lầm chiến lược mặc định trong thời kỳ biến động. Còn sai lầm lớn nhất là bỏ lỡ một làn sóng hiển nhiên rồi vì cái tôi mà phủ nhận; khi đó cần thừa nhận vấn đề, nhận trách nhiệm, dồn nguồn lực và người giỏi nhất vào việc bắt kịp, rồi trực tiếp theo sát đến khi sửa xong.&lt;/p&gt;
&lt;h2 id="find-a-new-role"&gt;&lt;a href="https://larahogan.me/blog/identify-new-role/" rel="noopener noreferrer"&gt;Find a new role&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Khi cân nhắc chuyển việc hay đổi vai trò, Lara Hogan đề xuất lập bốn danh sách, có thể điền vào một bảng 2×2: những điều bắt buộc không thể thương lượng; những điều mong muốn, quan trọng nhưng chấp nhận thiếu một vài thứ; những yếu tố không quan trọng ở thời điểm này, dù có thể từng quan trọng hoặc quan trọng với người khác; và một câu ngắn nêu điều duy nhất bạn muốn tối ưu hơn mọi thứ khác. Cách làm này biến một quyết định nghề nghiệp mơ hồ thành các tiêu chí cụ thể để tìm kiếm, phỏng vấn và so sánh cơ hội.&lt;/p&gt;
&lt;p&gt;Tác giả khuyên không đưa chức danh vào bất kỳ danh sách nào, vì cùng một chức danh có thể mang ý nghĩa rất khác giữa các tổ chức; hãy đánh giá nội dung thực tế của vai trò. Danh sách bắt buộc phải thật ngắn: điều gì bạn vẫn chấp nhận nếu thiếu thì thuộc về danh sách mong muốn, và gần như chắc chắn bạn sẽ phải đánh đổi ít nhất một điều mong muốn. Các tiêu chí nên phản ánh nhu cầu hôm nay, không phải điều bạn từng muốn hay có thể cần trong tương lai xa; mức lương có thể nhường chỗ cho bảo hiểm sức khỏe, trách nhiệm có thể nhường chỗ cho cân bằng giữa công việc và cuộc sống. Câu “tối ưu cho” là công cụ quan trọng nhất: hãy dán nó cạnh máy tính, chia sẻ với người bạn tin cậy và dùng nó để tự hỏi cơ hội nào giúp bạn tiến gần mục tiêu ấy nhất. Bài tập này có thể làm lại mỗi khi hoàn cảnh thay đổi.&lt;/p&gt;
&lt;h2 id="i-stored-a-website-in-a-favicon"&gt;&lt;a href="https://www.timwehrle.de/blog/i-stored-a-website-in-a-favicon" rel="noopener noreferrer"&gt;I Stored a Website in a Favicon&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Sau lần giấu hai byte vào chuột máy tính, Tim Wehrle thử lưu cả nội dung một trang web vào favicon. Ý tưởng rất đơn giản: favicon chỉ là ảnh, ảnh là các điểm ảnh, và mỗi điểm ảnh có ba kênh màu đỏ, xanh lá, xanh dương, tức ba byte. Tác giả chuyển đoạn HTML thành byte UTF-8 bằng &lt;code&gt;TextEncoder&lt;/code&gt;, thêm bốn byte đầu ghi độ dài dữ liệu để bộ giải mã biết chính xác nơi kết thúc, vì cuối ảnh có thể còn điểm ảnh thừa, rồi ghi lần lượt từng byte vào các kênh màu. Với 208 byte nội dung cộng phần đầu, tổng cộng 212 byte cần 71 điểm ảnh và vừa trong một ảnh PNG 9×9, sử dụng 87% dung lượng; kết quả trông giống như nhiễu.&lt;/p&gt;
&lt;p&gt;Để đọc lại, trình duyệt tải favicon và vẽ lên canvas, rồi JavaScript lấy các giá trị RGB, khôi phục mảng byte, đọc độ dài và giải mã UTF-8 thành đoạn HTML ban đầu. Điểm cần lưu ý là favicon chỉ chứa nội dung trang và không tự thực thi được; vẫn cần một đoạn mã khởi động nhỏ để giải mã và thay trang hiện tại bằng nội dung tái tạo. Tác giả thừa nhận thử nghiệm không có giá trị thực tế, vì dung lượng rất nhỏ và có nhiều cách tốt hơn để phân phối một tài liệu HTML, nhưng nó minh họa rõ rằng mọi định dạng tệp rốt cuộc chỉ là byte và có thể được dùng ngoài mục đích quen thuộc. Bài viết cũng gợi ý các hướng khác như nhúng mã đánh dấu trực tiếp vào favicon SVG, dùng các đoạn chú thích văn bản của PNG, hoặc định dạng ICO.&lt;/p&gt;
&lt;h2 id="antipatterns-never-left-we-just-stopped-calling-them-by-name"&gt;&lt;a href="https://dev.to/leonpennings/antipatterns-never-left-we-just-stopped-calling-them-by-name-969" rel="noopener noreferrer"&gt;AntiPatterns Never Left, We Just Stopped Calling Them by Name&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Năm 1998, cuốn sách AntiPatterns lập danh mục những kiểu thất bại lặp lại trong phát triển phần mềm, mỗi kiểu có tên gọi, triệu chứng và lối thoát. Leon Pennings cho rằng chúng chỉ khoác tên gọi mới và khó nhận ra vì hệ thống vẫn chạy và vượt qua kiểm thử; thành công trước mắt khiến lựa chọn kiến trúc được xem là đúng, trong khi không ai thử phương án đơn giản hơn để so sánh. Vì vậy, sự phổ biến không chứng minh giải pháp phù hợp với bối cảnh, và gọi đúng tên kiểu thất bại giúp đội ngũ nhìn thấy vấn đề bị che khuất.&lt;/p&gt;
&lt;p&gt;Tác giả chia các ví dụ hiện đại thành bốn nhóm. Thứ nhất là tổ chức hệ thống theo tầng kỹ thuật thay vì theo nghiệp vụ, dẫn đến khái niệm nghiệp vụ bị phân mảnh, mô hình miền thiếu hành vi và phụ thuộc sâu vào khung phần mềm. Thứ hai là áp dụng công nghệ mà không đánh giá, như dùng Kubernetes hay Kafka làm chiếc búa vạn năng. Thứ ba là phương thuốc tái tạo chính căn bệnh: mã nguồn rối thành mạng lưới vi dịch vụ rối, độ phức tạp chỉ chuyển sang ranh giới mạng, hay đội nền tảng trở thành bức tường vận hành mới. Thứ tư là người chịu hậu quả không được quyết định, như vai trò trung gian trong Scrum ngăn cách lập trình viên với người dùng. Ở phần kết, tác giả cho rằng tự viết vài chục dòng mã nguồn đôi khi rẻ hơn về lâu dài so với một phụ thuộc lớn. Thông điệp không phải phản đối khung phần mềm, vi dịch vụ hay Scrum, mà là đánh giá mọi lựa chọn theo bối cảnh.&lt;/p&gt;
&lt;h2 id="5-more-advanced-java-tips-that-senior-engineers-actually-use"&gt;&lt;a href="https://dev.to/cyclopt_dimitrisk/5-more-advanced-java-tips-that-senior-engineers-actually-use-bbp" rel="noopener noreferrer"&gt;5 More Advanced Java Tips That Senior Engineers Actually Use&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Bài viết giới thiệu năm khả năng của Java 21 trở lên. Structured Concurrency gom một nhóm tác vụ đồng thời thành một đơn vị có chung vòng đời: khi một tác vụ lỗi, các tác vụ còn lại bị hủy, việc hủy từ luồng cha được truyền xuống, và khi khối &lt;code&gt;try&lt;/code&gt; kết thúc thì không còn gì chạy ngầm, khác hẳn cách tự quản lý nhiều &lt;code&gt;CompletableFuture&lt;/code&gt; độc lập vốn dễ gây rò rỉ công việc. Record Patterns cho phép tách cấu trúc bản ghi lồng nhau ngay trong biểu thức &lt;code&gt;switch&lt;/code&gt;; kết hợp với kiểu niêm phong, trình biên dịch kiểm tra đầy đủ mọi trường hợp, nên khi thêm một kiểu con mới, những chỗ chưa xử lý sẽ báo lỗi biên dịch.&lt;/p&gt;
&lt;p&gt;Sequenced Collections thống nhất cách lấy phần tử đầu, cuối và khung nhìn đảo ngược cho danh sách, tập hợp và ánh xạ có thứ tự; &lt;code&gt;reversed()&lt;/code&gt; trả về khung nhìn chứ không sao chép, rất tiện khi xử lý nhật ký sự kiện theo thời gian. Foreign Function and Memory API (Project Panama) thay thế &lt;code&gt;Unsafe&lt;/code&gt; và JNI bằng cách truy cập bộ nhớ ngoài vùng quản lý và gọi hàm gốc hoàn toàn bằng Java, có kiểm tra biên và giải phóng tài nguyên xác định nhờ mô hình Arena. Cuối cùng, Gatherers lấp khoảng trống của Stream API: nếu Collectors định nghĩa cách một luồng dữ liệu kết thúc thì Gatherers cho phép tự viết phép biến đổi trung gian có trạng thái như chia lô, cửa sổ trượt, loại bỏ phần tử trùng liên tiếp hay tính trung bình động mà vẫn giữ chuỗi xử lý dễ đọc. Theo tác giả, các tính năng này ăn khớp với nhau thành một hệ thống nhất quán.&lt;/p&gt;
&lt;h2 id="go"&gt;&lt;a href="https://dev.to/mihirmohapatra/gos-type-system-structs-interfaces-and-life-without-inheritance-2lb7" rel="noopener noreferrer"&gt;Go&amp;rsquo;s Type System — Structs, Interfaces, and Life Without Inheritance&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Sau sáu năm với Java và Kotlin, tác giả nhận ra điều thay đổi tư duy thiết kế nhiều nhất khi học Go là ngôn ngữ này không có kế thừa lớp. Struct chỉ chứa dữ liệu, không có hàm dựng hay từ khóa &lt;code&gt;implements&lt;/code&gt;; một hàm như &lt;code&gt;NewOrder&lt;/code&gt; chỉ là quy ước, và vẫn có thể tạo struct trực tiếp với các giá trị mặc định. Phương thức được khai báo riêng với bộ nhận theo giá trị, làm việc trên bản sao, hoặc theo con trỏ, có thể thay đổi đối tượng gốc; vì vậy phương thức cần sửa trạng thái phải dùng bộ nhận con trỏ. Thay cho kế thừa, Go dùng nhúng struct: trường và phương thức của struct bên trong được đưa lên struct bên ngoài, nhưng đây là quan hệ kết hợp “có một” chứ không phải “là một”.&lt;/p&gt;
&lt;p&gt;Điểm khác biệt lớn nhất là giao diện được thỏa mãn ngầm: bất kỳ kiểu nào có đủ phương thức đều tự động đáp ứng giao diện, kể cả kiểu không do bạn viết. Điều này đảo chiều thiết kế: giao diện thường nhỏ và do phía sử dụng định nghĩa theo đúng hành vi cần thiết, như &lt;code&gt;io.Reader&lt;/code&gt; và &lt;code&gt;io.Writer&lt;/code&gt; chỉ có một phương thức, thay vì bắt mọi kiểu tuân theo một hợp đồng lớn ngay từ đầu. Nhờ đó, các kiểu độc lập và không có hệ thống phân cấp mong manh. Tác giả cũng thẳng thắn nêu những điểm chưa quen: khó tìm tất cả kiểu triển khai một giao diện, nhúng không mang lại đa hình như kế thừa, và giá trị mặc định có thể khiến dữ liệu chưa khởi tạo trông như hợp lệ, nên cần chủ động kiểm tra các trường bắt buộc.&lt;/p&gt;
&lt;h2 id="conduit-the-gateway-i-built-to-forget-about"&gt;&lt;a href="https://dev.to/adamthedeveloper/conduit-the-gateway-i-built-to-forget-about-2ann" rel="noopener noreferrer"&gt;Conduit: The Gateway I Built to Forget About&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Tác giả từng xây dựng một cổng API bằng Go đứng trước ba dịch vụ với hai cơ chế xác thực: phiên tự thiết kế có chữ ký HMAC cho người dùng và trò chuyện, JWT cho khu vực quản trị. Cổng chạy ổn định nhưng khó mở rộng, vì mỗi ngoại lệ về đường dẫn công khai lại là thêm một nhánh điều kiện trong mã Go và phải triển khai lại. Conduit giải quyết bằng cách tách phần ổn định khỏi phần hay thay đổi: Go phụ trách tiếp nhận HTTP, định tuyến, chuyển tiếp, CORS và bảo vệ SSRF; một môi trường Deno cách ly chạy các mô-đun chính sách viết bằng TypeScript cho xác thực, ghi nhật ký và biến đổi yêu cầu, chỉ cần sửa tệp mà không biên dịch lại.&lt;/p&gt;
&lt;p&gt;Hai tiến trình trao đổi qua Unix socket theo hợp đồng ảnh chụp và bản vá. Go gửi ảnh chụp yêu cầu có thể tuần tự hóa; mô-đun chỉ trả về phần thay đổi tối thiểu thay vì sở hữu hay sửa trực tiếp đối tượng yêu cầu, còn nội dung lớn hơn 64 KiB vẫn nằm trong Go và được tham chiếu bằng mã định danh. Thiết kế cũng phân biệt rõ các chế độ lỗi: một bước xử lý quá thời gian chỉ ghi cảnh báo, còn khi cả môi trường Deno ngừng hoạt động thì từng tuyến chọn đóng để trả lỗi 503 hoặc mở để chuyển tiếp mà bỏ qua mô-đun. Theo tác giả, sự đơn giản đến từ ranh giới trách nhiệm rõ ràng chứ không phải số dòng mã. Conduit hướng tới vài dịch vụ có chính sách biên thay đổi thường xuyên, không nhằm thay thế Envoy, Kong hay xử lý luồng dữ liệu rất lớn.&lt;/p&gt;
&lt;h2 id="the-reflect-package"&gt;&lt;a href="https://internals-for-interns.com/posts/go-runtime-reflect" rel="noopener noreferrer"&gt;The Reflect Package&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Go là ngôn ngữ biên dịch tĩnh ra mã máy, vậy mà gói &lt;code&gt;reflect&lt;/code&gt; vẫn đọc được tên trường, kiểu và cả thẻ struct lúc chạy. Bài viết giải thích rằng &lt;code&gt;reflect&lt;/code&gt; không tính toán gì mà chỉ đọc siêu dữ liệu trình biên dịch đã ghi sẵn vào tệp thực thi. Một giá trị &lt;code&gt;any&lt;/code&gt; gồm hai con trỏ: một trỏ đến dữ liệu, một trỏ đến mô tả kiểu, và địa chỉ của mô tả này là hằng số trình biên dịch đã biết từ trước. Vì vậy, &lt;code&gt;reflect.TypeOf&lt;/code&gt; về bản chất chỉ là một lần đọc con trỏ, không tra bảng, không cấp phát bộ nhớ.&lt;/p&gt;
&lt;p&gt;Thành phần &lt;code&gt;reflectdata&lt;/code&gt; của trình biên dịch tuần tự hóa mỗi kiểu thành một ký hiệu &lt;code&gt;type:&amp;lt;tên kiểu&amp;gt;&lt;/code&gt;, và trình liên kết đặt chúng vào vùng chỉ đọc; chạy &lt;code&gt;strings&lt;/code&gt; trên tệp thực thi là thấy thẻ struct nằm nguyên văn. Mỗi mô tả gồm phần đầu chung cố định cho mọi kiểu, ghi kích thước, căn chỉnh, loại, giá trị băm, mặt nạ bit cho bộ thu gom rác, hàm so sánh và tên; ngay sau đó là dữ liệu riêng theo loại, với struct là mảng trường gồm tên, kiểu và độ lệch, rồi đến dữ liệu phương thức. Tên và thẻ được lưu một lần trong vùng dùng chung để mọi mô tả cùng trỏ tới, giúp tiết kiệm dung lượng. &lt;code&gt;reflect.Value&lt;/code&gt; ghép mô tả với con trỏ dữ liệu, rồi cộng độ lệch đã lưu để đến đúng trường trong bộ nhớ. Việc đọc có thể thực hiện trên bản sao, nhưng muốn sửa phải truyền con trỏ, vì truyền giá trị vào &lt;code&gt;any&lt;/code&gt; sẽ sao chép nó và thay đổi sẽ không tác động đến đối tượng gốc.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Bài viết đã được viết lại bởi Claude Code với Opus 5.5 vào ngày 27/09/2026.&lt;/em&gt;&lt;/p&gt;</description></item><item><title>Newsletter #123</title><link>https://miti99.com/post/2026/07/11/</link><pubDate>Sat, 11 Jul 2026 00:00:00 +0700</pubDate><guid>https://miti99.com/post/2026/07/11/</guid><description>&lt;p&gt;&lt;em&gt;Mời bạn thưởng thức Newsletter #123.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="our-collective-bike-shed-moment"&gt;&lt;a href="https://muratbuffalo.blogspot.com/2026/06/our-collective-bike-shed-moment.html" rel="noopener noreferrer"&gt;Our Collective Bike Shed Moment&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Bài viết mượn “Định luật về sự tầm thường” của Parkinson: một ủy ban giả tưởng chỉ dành mười phút cho thiết kế lò phản ứng hạt nhân vì không ai hiểu đủ để tranh luận, nhưng lại mất bốn mươi lăm phút bàn về màu sơn nhà để xe đạp. Tác giả cho rằng cả xã hội đang lặp lại cuộc họp ấy trước AI. LLM đã lần lượt vượt qua các rào cản từng bị xem là giới hạn, từ chuyện không biết lập trình đến ảo giác, và ngày càng giỏi về phương pháp hình thức lẫn suy luận, vậy mà nhiều người vẫn liên tục dời tiêu chuẩn đánh giá, còn số đông thì thờ ơ như thể chưa có gì thay đổi.&lt;/p&gt;
&lt;p&gt;Tác giả giải thích sự thờ ơ đó bằng thiên kiến bình thường hóa, khiến con người mặc định tương lai sẽ gần giống hiện tại, và hiệu ứng “vấn đề của người khác” của Douglas Adams, khiến những thay đổi lớn bị chủ động bỏ qua. Học công cụ mới để trụ vững trong vài năm tới là chiến lược cá nhân hợp lý, nhưng không trả lời được câu hỏi về những thập kỷ sắp tới. Điều đáng lo là ngay cả những người lẽ ra phải dẫn dắt cuộc thảo luận, gồm nhà công nghệ, nhà nghiên cứu, nhà kinh tế, nhà hoạch định chính sách, đạo đức, quân sự và giáo dục, vẫn chưa bàn về tác động dài hạn của AI ở độ sâu cần thiết, trong khi sự chú ý tiếp tục dồn vào các tranh cãi bề mặt.&lt;/p&gt;
&lt;h2 id="p99-0ms"&gt;&lt;a href="https://ruurtjan.com/articles/p99-0ms-autocomplete-for-240-million-domain-names" rel="noopener noreferrer"&gt;p99 0 ms* autocomplete for 240 million domain names&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Bài viết trình bày cách Wirewiki tạo cảm giác tự động hoàn thành tức thì trên tập 240 triệu tên miền. Khi người dùng nhấn một phím, trình duyệt tải trước gợi ý cho chuỗi hiện tại cùng mọi ký tự có thể gõ tiếp; lúc thả phím kế tiếp, kết quả thường đã sẵn sàng để hiển thị. Vì bảng chữ cái của tên miền có giới hạn, mỗi phản hồi chỉ chứa tối đa 312 tên miền, khoảng 2,5 kB sau nén. Theo phép đo của tác giả, 99% thao tác gõ có ngân sách ít nhất 121 ms, nên “p99 0 ms” nghĩa là kết quả có trước lúc thả phím, chứ không phải yêu cầu mạng thật sự mất 0 ms.&lt;/p&gt;
&lt;p&gt;Phía máy chủ chia dữ liệu thành hai tầng. Một cây tiền tố trong bộ nhớ lưu sẵn tám gợi ý phổ biến nhất cho mọi tiền tố, lấy từ danh sách Tranco gồm một triệu tên miền. Phần còn lại gồm 240 triệu tên miền từ CZDS được sắp xếp, nén theo sai khác và chia thành các khối cố định trên SSD; một thư mục 27 MB trong bộ nhớ giúp tìm đúng khối, còn hệ điều hành giữ các trang hay dùng trong bộ nhớ đệm. API thường phản hồi trong 2 ms; ở 1.600 yêu cầu mỗi giây, Nginx cùng API vẫn đạt p99 khoảng 15 ms. Khi máy chủ đã đủ nhanh, hành trình mạng qua Cloudflare trở thành yếu tố chi phối độ trễ cảm nhận, như dấu sao trong tiêu đề ngụ ý: một máy chủ duy nhất ở châu Âu chưa thể đạt mục tiêu với người dùng ở xa. Bài học là đo độ trễ theo trải nghiệm người dùng để tối ưu đúng chỗ.&lt;/p&gt;
&lt;h2 id="building-reliable-agentic-ai-systems"&gt;&lt;a href="https://martinfowler.com/articles/reliable-llm-bayer.html" rel="noopener noreferrer"&gt;Building Reliable Agentic AI Systems&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Bài viết kể lại hành trình Bayer xây dựng PRINCE, hệ thống AI giúp nhà nghiên cứu truy vấn hàng chục năm dữ liệu tiền lâm sàng nằm trong các báo cáo PDF. Nền tảng phát triển qua ba giai đoạn: tìm kiếm dữ liệu có cấu trúc, hỏi đáp bằng RAG, rồi xử lý tác vụ phức tạp bằng nhiều tác tử. LangGraph điều phối chuỗi bước làm rõ ý định, lập kế hoạch, nghiên cứu, kiểm tra độ đầy đủ của bằng chứng và soạn câu trả lời. Tác tử nghiên cứu kết hợp RAG trên OpenSearch cho báo cáo phi cấu trúc với Text-to-SQL trên Athena cho các phép lọc, tổng hợp và so sánh trên dữ liệu có cấu trúc, còn các bước phản tư riêng biệt kiểm tra tiến trình, độ đủ của dữ liệu và chất lượng bản nháp.&lt;/p&gt;
&lt;p&gt;Độ tin cậy đến từ khung kỹ thuật bao quanh mô hình, không chỉ từ câu lệnh hay mô hình mạnh hơn. Mỗi bước chỉ nhận phần ngữ cảnh cần thiết; trạng thái được lưu sau từng nút để tiếp tục từ chỗ lỗi thay vì chạy lại toàn bộ; yêu cầu được thử lại có giới hạn ở cấp lời gọi mô hình lẫn cấp bước, rồi chuyển sang mô hình hoặc nhà cung cấp dự phòng khi cần. Trong môi trường được quản lý chặt, niềm tin còn đến từ việc hiển thị các bước trung gian, gắn trích dẫn tới đúng trang và đoạn nguồn, đánh giá liên tục trên cả tập dữ liệu chuẩn lẫn lưu lượng thật, và giữ chuyên gia trong vòng phê duyệt. Cách tổ chức này giúp hệ thống dễ quan sát, kiểm thử, phục hồi và cải tiến hơn hẳn một tác tử đơn khối.&lt;/p&gt;
&lt;h2 id="growing-as-an-engineer-in-a-world-of-ai"&gt;&lt;a href="https://nlopes.dev/writing/growing-as-an-engineer-in-a-world-of-ai" rel="noopener noreferrer"&gt;Growing as an engineer in a world of AI&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Bài viết cho rằng rủi ro lớn nhất với kỹ sư mới vào nghề không phải AI lấy mất việc làm, mà là họ ngừng học khi AI xóa đi chính những trở ngại từng tạo ra việc học. Tự đọc tài liệu, thử, thất bại, gỡ lỗi và tự hình thành câu trả lời giúp kiến thức bền hơn nhờ “khó khăn có lợi” và hiệu ứng tự tạo. Có lời giải nhanh không đồng nghĩa với hiểu vấn đề: nếu chỉ nhận đáp án rồi chuyển sang việc tiếp theo, kỹ sư có thể viết ra mã chạy được nhưng không giải thích nổi lựa chọn, đánh đổi hay nguyên nhân lỗi. Khoản “nợ nhận thức” này thường chỉ lộ ra khi hệ thống gặp sự cố.&lt;/p&gt;
&lt;p&gt;Tác giả không khuyên tránh AI mà xem nó như công cụ khuếch đại việc học: hỏi vì sao, yêu cầu phương án thay thế và tình huống thất bại; tự đọc và phác thảo mô hình ban đầu trước khi nhờ giải thích; viết lại từng phần thay vì sao chép; biến việc đánh giá mã nguồn và viết tài liệu thành bài tập tư duy chủ động. Nền tảng vẫn cần được bồi đắp từ sách, Unix, tài liệu gốc, thảo luận kiến trúc và phân tích sự cố, còn thời gian AI tiết kiệm được nên dành cho những suy nghĩ khó hơn. Sự trưởng thành của kỹ sư trẻ cũng là trách nhiệm của tổ chức: nhà quản lý cần tạo không gian học tập, đánh giá óc phán đoán và hiểu biết hệ thống thay vì chỉ đo sản lượng, đồng thời tiếp tục tuyển và phát triển kỹ sư trẻ.&lt;/p&gt;
&lt;h2 id="note-to-my-younger-self"&gt;&lt;a href="https://yewjin.substack.com/p/note-to-my-younger-self" rel="noopener noreferrer"&gt;Note To My Younger Self&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Sau hai thập kỷ làm việc tại các công ty công nghệ lớn, tác giả đúc kết tám nguyên tắc nghề nghiệp đơn giản nhưng khó duy trì. Hãy yêu công việc thực tế thay vì chạy theo chức danh, xem công việc đầu tiên như phòng thí nghiệm để học cách giải quyết vấn đề; liên tục hỏi “tại sao” để tìm nguyên nhân gốc; xây dựng quan hệ chân thành bằng sự giúp đỡ thật lòng nhưng vẫn giữ ranh giới rõ ràng; tạo niềm vui trong môi trường làm việc; lắng nghe, giải thích phù hợp với người nghe và biết khi nào nên im lặng; ghi lại tác động của mình hằng tuần và chủ động xin phản hồi cụ thể rồi hành động ngay, để có bằng chứng cho sự tiến bộ và điều chỉnh sớm; và khi có thể, chọn người quản lý giúp mình trưởng thành thay vì chọn chức danh hấp dẫn, vì họ ảnh hưởng mạnh đến cách suy nghĩ và quỹ đạo nghề nghiệp dài hạn.&lt;/p&gt;
&lt;p&gt;Tác giả thừa nhận đây không phải quy tắc tuyệt đối và góc nhìn của mình mang thiên kiến của người thành công. Khi công việc tốt, người quản lý tốt, sự tử tế, khả năng được ghi nhận và sức khỏe tinh thần xung đột với nhau, lựa chọn phải dựa vào giai đoạn nghề nghiệp, kỹ năng còn thiếu, hoàn cảnh cá nhân và thời gian đánh đổi. Một câu hỏi hữu ích khi phân vân là điều gì vẫn còn giá trị sau năm năm: dự án thường bị lãng quên, còn kỹ năng, cách suy nghĩ và các mối quan hệ tốt tiếp tục sinh lợi lâu dài.&lt;/p&gt;
&lt;h2 id="google-hits-50-ipv6"&gt;&lt;a href="https://blog.apnic.net/2026/04/28/google-hits-50-ipv6/" rel="noopener noreferrer"&gt;Google hits 50% IPv6&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Google lần đầu ghi nhận khoảng một nửa người dùng truy cập dịch vụ của họ qua IPv6, cho thấy giao thức này đã trở thành thành phần trưởng thành của Internet toàn cầu. Cùng thời điểm, APNIC Labs đo được khoảng 42%, nhưng chênh lệch này không nhất thiết là mâu thuẫn. Mẫu của APNIC đến từ quảng cáo phân phối không đồng đều giữa các nền kinh tế, sau đó được gán trọng số theo ước tính số người dùng Internet của từng nơi, nên con số toàn cầu phụ thuộc mạnh vào tập người dùng được đo và phương pháp tính. Ở cấp từng nền kinh tế, số liệu của APNIC nhìn chung gần với Google và các bên đo lường khác, vì vậy hai tập dữ liệu có thể xem như một khoảng ước lượng hợp lý cho mức triển khai toàn cầu.&lt;/p&gt;
&lt;p&gt;Mức áp dụng IPv6 rất khác nhau giữa các quốc gia, nhà mạng cố định và mạng di động. Chuyển đổi chậm không có nghĩa là thất bại: nhiều nhà cung cấp còn phải khai thác hạ tầng IPv4 đã đầu tư lớn, trong khi mạng mới có thể giảm tổng chi phí khi chọn IPv6 ngay từ đầu, nên đây là bài toán về kỹ thuật, vốn và thị trường chứ không chỉ là chọn giao thức. Internet hiện vận hành song song IPv4 trực tiếp, IPv4 qua NAT hoặc CGNAT và IPv6, vì vậy giữ IPv4 cũng không giúp tránh được độ phức tạp. Khả năng liên thông ngày càng được xử lý ở tầng vận chuyển, các dịch vụ trung gian và nhà cung cấp nội dung hỗ trợ cả hai giao thức, thay vì đòi mọi hệ thống phía sau chuyển đổi cùng lúc.&lt;/p&gt;
&lt;h2 id="the-unwritten-laws-of-software-engineering"&gt;&lt;a href="https://www.manager.dev/newsletter/the-unwritten-laws-of-software-engineering" rel="noopener noreferrer"&gt;The unwritten laws of software engineering&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Bài viết tổng hợp bảy quy tắc vận hành mà kỹ sư thường chỉ thấm sau khi gặp sự cố. Nếu hệ thống hỏng ngay sau một lần triển khai, hãy quay lui để khôi phục ổn định trước rồi mới điều tra, thay vì mất thời gian chứng minh thay đổi của mình vô can. Bản sao lưu chỉ đáng tin khi đã được thử khôi phục định kỳ, và đội ngũ biết rõ lượng dữ liệu có thể mất, ai có quyền thực hiện cùng thời gian phục hồi. Nhật ký cần đủ thông tin, có mã liên kết xuyên dịch vụ và dễ tìm kiếm, nhưng không dài đến mức che khuất tín hiệu quan trọng.&lt;/p&gt;
&lt;p&gt;Mọi thay đổi dữ liệu phải có đường quay lui nhanh, cụ thể và đã được kiểm thử. Phụ thuộc bên ngoài chắc chắn sẽ có lúc chậm, giới hạn yêu cầu hoặc ngừng hoạt động, nên cần hiểu giới hạn tốc độ, tác động khi gián đoạn, cam kết dịch vụ và chuẩn bị phương án như bộ nhớ đệm, hàng đợi hoặc dùng dữ liệu cũ. Với thao tác rủi ro, nguyên tắc bốn mắt giúp phát hiện sai lầm trước khi thực hiện; nếu bạn ngại gọi ai đó vào đêm khuya để cùng kiểm tra, đó chính là dấu hiệu càng không nên làm một mình. Cuối cùng, giải pháp tạm thời thường sống lâu hơn dự kiến, vì vậy phiên bản tối thiểu vẫn phải đơn giản và đủ chất lượng để tồn tại lâu dài, chứ không phải một bản vá mong manh chờ sửa sau.&lt;/p&gt;
&lt;h3 id="bonus"&gt;Bonus&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;Images:&lt;/strong&gt;
&lt;img src="https://substackcdn.com/image/fetch/$s_!mGbr!,w_1100,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8558b1b2-984f-4c42-bb23-e63a2252533c_2650x3068.png" alt="Top Anti-Patterns in Service Architecture"&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Bài viết đã được viết lại bởi Claude Code với Opus 5.5 vào ngày 27/09/2026.&lt;/em&gt;&lt;/p&gt;</description></item><item><title>Newsletter #122</title><link>https://miti99.com/post/2026/07/10/</link><pubDate>Fri, 10 Jul 2026 00:00:00 +0700</pubDate><guid>https://miti99.com/post/2026/07/10/</guid><description>&lt;p&gt;&lt;em&gt;Mời bạn thưởng thức Newsletter #122.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="the-worst-take-in-tech-new-grad-hiring"&gt;&lt;a href="https://www.scottkennedy.us/new-grads" rel="noopener noreferrer"&gt;The worst take in tech: new grad hiring&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Scott Kennedy phản bác quan điểm đang lan rộng rằng doanh nghiệp nên ngừng tuyển sinh viên mới tốt nghiệp vì AI sẽ tự động hóa phần lớn công việc đầu vào. Theo lập luận đó, người mới vốn học nghề qua các nhiệm vụ đơn giản, nay những nhiệm vụ này do AI đảm nhận nên họ khó rèn được óc phán đoán, trong khi ngân sách còn phải chi cho token. Kennedy cho rằng đây là suy nghĩ sai lầm: năm qua, ông thấy nguồn ứng viên mới tốt nghiệp mạnh nhất trong hơn mười năm tuyển dụng, và Replit đã tăng gấp đôi chỉ tiêu tuyển kỹ sư mới ra trường để tận dụng cơ hội này. Những người đam mê, tò mò và có năng lực tốt có thể vượt qua cả người nhiều kinh nghiệm chỉ sau vài năm nếu được hướng dẫn tốt và giao bài toán khó; bỏ qua họ vì lo ngại AI là lựa chọn thiếu trách nhiệm.&lt;/p&gt;
&lt;p&gt;Tác giả còn chỉ ra lợi thế của thế hệ &amp;ldquo;bản địa AI&amp;rdquo;: lớn lên cùng công cụ mới, họ không có thói quen cũ cần thay đổi nên sử dụng các công cụ lập trình bằng AI rất thành thạo, điều ông thường thấy qua phỏng vấn. Dù vậy, công thức thành công cho người mới vẫn không đổi: tò mò để hiểu những gì diễn ra bên dưới và biết gỡ lỗi sâu, kiên trì học từ những lần thử thất bại, chủ động nhờ giúp đỡ khi bế tắc và tìm môi trường nhiều thử thách. AI thay đổi cách làm việc, nhưng không làm mất đi giá trị của khả năng học hỏi, nỗ lực và óc phán đoán.&lt;/p&gt;
&lt;h2 id="parsing-gigabytes-of-json-per-second"&gt;&lt;a href="https://arxiv.org/pdf/1902.08318" rel="noopener noreferrer"&gt;Parsing Gigabytes of JSON per Second&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Nghiên cứu giới thiệu &lt;code&gt;simdjson&lt;/code&gt;, bộ phân tích cú pháp JSON đầu tiên vừa tuân thủ tiêu chuẩn vừa xử lý được hàng gigabyte dữ liệu mỗi giây trên một lõi của bộ xử lý phổ thông. Xuất phát điểm là thực tế JSON có mặt ở khắp nơi trên web, và khi hệ thống phải tiếp nhận khối lượng lớn tài liệu, bước phân tích cú pháp dễ trở thành nút thắt hiệu năng dù bài toán này đã được nghiên cứu từ lâu.&lt;/p&gt;
&lt;p&gt;Điểm khác biệt của &lt;code&gt;simdjson&lt;/code&gt; so với các bộ phân tích có kiểm tra tính hợp lệ khác là việc tận dụng triệt để lệnh SIMD, cho phép bộ xử lý thao tác trên nhiều phần dữ liệu trong cùng một lệnh. Nhờ vậy, nó chỉ cần khoảng một phần tư số lệnh hoặc ít hơn so với RapidJSON, một bộ phân tích hàng đầu, mà vẫn kiểm tra đầy đủ tính hợp lệ của JSON. Nhóm tác giả phát hành phần mềm dưới giấy phép nguồn mở dễ dãi, giúp kết quả dễ được kiểm chứng và cộng đồng dễ dàng áp dụng.&lt;/p&gt;
&lt;h2 id="six-sql-patterns-i-use-to-catch-transaction-fraud"&gt;&lt;a href="https://analytics.fixelsmith.com/posts/sql-fraud-patterns/" rel="noopener noreferrer"&gt;Six SQL patterns I use to catch transaction fraud&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Bài viết cho rằng phát hiện gian lận giao dịch phần lớn là bài toán SQL, thường mang lại giá trị sớm hơn một hệ thống phức tạp. Sáu mẫu áp dụng được cho thẻ tín dụng, thương mại điện tử và thanh toán y tế. Mẫu tần suất gom giao dịch theo cửa sổ thời gian để phát hiện chủ thẻ giao dịch dồn dập; dùng nhiều kích thước cửa sổ giúp bắt cả hành vi thử thẻ trong vài giây lẫn chuỗi giao dịch kéo dài hàng giờ. Mẫu di chuyển bất khả thi dùng hàm &lt;code&gt;LAG&lt;/code&gt; cùng công thức khoảng cách địa lý để phát hiện một thẻ xuất hiện ở hai nơi cách xa trong thời gian phi thực tế. Mẫu số tiền đáng ngờ tìm các khoản tròn nhỏ đặc trưng của thử thẻ hoặc khoản ngay dưới ngưỡng kiểm soát, còn mẫu điểm bán đáng ngờ so hoạt động của từng điểm bán với mức nền của chính nó để phát hiện thiết bị thanh toán bị xâm nhập.&lt;/p&gt;
&lt;p&gt;Mẫu hoạt động ngoài giờ so giao dịch với thói quen chi tiêu của chủ thẻ, nên chỉ đáng tin khi tài khoản có đủ lịch sử. Mẫu cuối dùng hàm cửa sổ tạo sẵn các cột dẫn xuất như thời gian từ giao dịch trước hay tổng tiền trong 24 giờ, để việc kết hợp tín hiệu chỉ còn là các điều kiện lọc đơn giản. Không mẫu nào đủ chính xác khi đứng riêng; giao dịch vi phạm ba hoặc bốn mẫu gần như chắc chắn là gian lận. Hệ thống vẫn cần xử lý đúng giá trị &lt;code&gt;NULL&lt;/code&gt;, có người xem xét cảnh báo sai và lọc phạm vi thời gian trước khi chạy hàm cửa sổ để kiểm soát chi phí.&lt;/p&gt;
&lt;h2 id="we-replaced-redis-with-mysql-for-inventory-reservationsand-it-scaled-2026"&gt;&lt;a href="https://shopify.engineering/scaling-inventory-reservations" rel="noopener noreferrer"&gt;We replaced Redis with MySQL for inventory reservations—and it scaled (2026)&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Shopify chuyển hệ thống giữ chỗ tồn kho từ Redis sang MySQL để dữ liệu giữ chỗ và sổ cái nằm trong cùng một giao dịch ACID. Redis xử lý đồng thời tốt, nhưng việc cập nhật hai hệ thống không mang tính nguyên tử nên có thể gây bán vượt tồn kho hoặc giữ tồn kho sai. Thiết kế mới dùng một hàng cho mỗi đơn vị tồn kho thay vì một cột số lượng, giới hạn trong nhóm tối đa 1.000 hàng cho mỗi cặp sản phẩm và địa điểm để bảng không phình to mà truy vấn vẫn nhanh khi tồn kho lớn. Tính năng &lt;code&gt;SKIP LOCKED&lt;/code&gt; của MySQL 8 cho phép giao dịch bỏ qua hàng đang bị khóa thay vì chờ, giảm mạnh tranh chấp khi nhiều người thanh toán cùng lúc. Khóa chính tổng hợp giúp giảm số khóa trên mỗi hàng, mức cô lập &lt;code&gt;READ COMMITTED&lt;/code&gt; tránh khóa khoảng trống chặn giao dịch bổ sung hàng, còn thứ tự khóa nhất quán loại bỏ bế tắc.&lt;/p&gt;
&lt;p&gt;Tuy vậy, nút thắt thực sự là cạn kết nối: CPU thấp nhưng yêu cầu vẫn xếp hàng. Gắn nhãn quy trình nghiệp vụ và đo tại tầng proxy giúp nhóm phát hiện các thao tác thanh toán khác giữ kết nối quá lâu. Sau khi dọn dẹp và điều chỉnh cấu hình, lượt đọc trên cơ sở dữ liệu chính giảm 50% và số giao dịch giảm 33%, CPU máy ghi dưới 50% ngay cả trong đợt giảm giá chớp nhoáng. Để chuyển đổi an toàn, Shopify ghi song song vào cả Redis và MySQL trong khi Redis vẫn là nguồn chính, so sánh kết quả trên lưu lượng thật rồi chuyển dần nguồn dữ liệu chính theo từng nhóm, luôn giữ khả năng quay lui.&lt;/p&gt;
&lt;h2 id="cell-based-architecture-for-resilient-payment-systems"&gt;&lt;a href="https://americanexpress.io/cell-based-architecture-for-resilient-payment-systems/" rel="noopener noreferrer"&gt;Cell-Based Architecture for Resilient Payment Systems&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;American Express tổ chức nền tảng thanh toán cốt lõi thành các ô độc lập, mỗi ô có dịch vụ, cơ sở dữ liệu và hạ tầng hỗ trợ riêng. Một ô được xác định bằng ranh giới sự cố chứ không phải một loại hạ tầng cụ thể: nó có thể hỏng, được bảo trì hoặc loại khỏi luồng xử lý mà không ảnh hưởng đến các ô khác. Dữ liệu tham chiếu ít thay đổi như tỷ giá được sao chép sẵn vào mọi ô để tránh tra cứu đồng bộ, còn dữ liệu thay đổi thường xuyên thì giao dịch được định tuyến xác định đến ô đang giữ trạng thái chính xác. Global Transaction Router kiểm soát mọi lưu lượng giữa các ô và ngăn dịch vụ ở ô này gọi trực tiếp sang ô khác; đôi khi phải nhân bản dịch vụ, nhưng đổi lại tính cô lập được giữ vững và độ trễ thấp hơn.&lt;/p&gt;
&lt;p&gt;Khi sự cố xảy ra, giao dịch không tiếp tục dở dang ở ô khác mà được khởi động lại từ đầu tại một ô khỏe mạnh với dữ liệu ban đầu. Cách này an toàn nhờ mã giao dịch duy nhất bảo đảm tính lũy đẳng, giúp hệ thống phía sau nhận diện được bản trùng lặp. Lưu lượng được chuyển dần theo tỷ lệ phần trăm thay vì chuyển toàn bộ một lần. Nền tảng cũng giảm tối đa phụ thuộc phụ trợ bằng cách ghi nhật ký và tải cấu hình bất đồng bộ, chấp nhận suy giảm tính năng không quan trọng để luồng thanh toán không bị chặn. Khả năng chống chịu đến từ ranh giới sự cố rõ ràng và kỷ luật kiến trúc, không chỉ từ giám sát và thử lại.&lt;/p&gt;
&lt;h2 id="when-failover-isnt-safe-building-high-availability-postgresql-on-kubernetes"&gt;&lt;a href="https://www.datadoghq.com/blog/engineering/postgresql-ha-kubernetes/" rel="noopener noreferrer"&gt;When failover isn’t safe: Building high-availability PostgreSQL on Kubernetes&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Trong một buổi diễn tập mô phỏng sự cố vùng, Datadog phát hiện cụm PostgreSQL trên Kubernetes của họ không thể chuyển đổi dự phòng an toàn. Khi độ trễ mạng tăng vọt, máy chính vẫn ghi dữ liệu và sao chép bất đồng bộ, trong khi các máy dự phòng tụt lại ngày càng xa. Patroni từ chối nâng cấp chúng thành máy chính vì độ trễ vượt ngưỡng &lt;code&gt;maximum_lag_on_failover&lt;/code&gt;; điều này tránh mất dữ liệu nhưng khiến cụm mắc kẹt, không thể tự khôi phục khả năng ghi. Bài học đầu tiên là tính sẵn sàng bề ngoài không có giá trị nếu không tồn tại ứng viên chuyển đổi dự phòng an toàn.&lt;/p&gt;
&lt;p&gt;Datadog chuyển sang mô hình lai: các máy dự phòng trong nhóm ứng viên dùng sao chép đồng bộ, còn bản sao chỉ đọc vẫn bất đồng bộ để tránh chi phí không cần thiết. Patroni được cấu hình &lt;code&gt;synchronous_mode&lt;/code&gt; cùng &lt;code&gt;synchronous_commit = remote_apply&lt;/code&gt;, nghĩa là giao dịch chỉ được xác nhận sau khi dữ liệu đã được áp dụng ở máy dự phòng. Kiểm chuẩn với mức bền vững cao nhất ghi nhận độ trễ trung bình tăng 53% và thông lượng giảm 34%, nhưng khi triển khai dần qua các trung tâm dữ liệu kèm thời gian theo dõi, ảnh hưởng thực tế ở tầng ứng dụng vẫn chấp nhận được. Khi không còn máy dự phòng đồng bộ, hệ thống chặn ghi thay vì âm thầm mất dữ liệu, biến rủi ro thành lỗi rõ ràng để ứng dụng thử lại hoặc xếp hàng. Để cân bằng độ bền và hiệu năng, cần theo dõi thời gian chờ &lt;code&gt;SyncRep&lt;/code&gt; và tình trạng của máy dự phòng đồng bộ.&lt;/p&gt;
&lt;h2 id="from-christmas-outage-to-1-app-store-ranking-an-aura-frames-postgres-scaling-retrospective"&gt;&lt;a href="https://andyatkinson.com/postgresql-rds-scaling-aws-christmas-day-peak" rel="noopener noreferrer"&gt;From Christmas Outage to #1 App Store Ranking: An Aura Frames Postgres Scaling Retrospective&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Đúng Giáng sinh 2024, cơ sở dữ liệu PostgreSQL của Aura Frames ngừng hoạt động ba giờ vì WAL tăng không giới hạn và lấp đầy ổ lưu nhật ký chuyên dụng. Nguyên nhân là RDS PostgreSQL 14.1 mặc định dùng khe sao chép cho bản sao: khe này buộc máy chính giữ lại WAL cho đến khi mọi bản sao nhận xong, nên khi bản sao chậm, WAL tích tụ đến mức hết dung lượng. Nhóm khắc phục bằng các tham số như &lt;code&gt;max_slot_wal_keep_size&lt;/code&gt; và &lt;code&gt;wal_keep_size&lt;/code&gt; để giới hạn lượng WAL, chấp nhận làm hỏng bản sao chậm thay vì để máy chính sập.&lt;/p&gt;
&lt;p&gt;Để chuẩn bị cho cao điểm 2025, thay vì tiếp tục nâng cấp một máy chủ duy nhất, Aura phân phối nguyên bảng: mười bảng ghi nhiều nhất được chuyển sang bảy máy chính mới, tổng cộng tám cơ sở dữ liệu. Đây là cách làm thực dụng khi chưa cần chia nhỏ các hàng của bảng lớn nhất. Ứng dụng Ruby on Rails vẫn là một khối thống nhất, định tuyến truy vấn bằng Active Record Multiple Databases; sao chép vật lý giúp chuyển dữ liệu đáng tin cậy với 5-10 phút gián đoạn. Tổng tài nguyên CPU và bộ nhớ tăng khoảng 4,7 lần, và Giáng sinh 2025 hệ thống đạt đỉnh 226.000 giao dịch mỗi giây, duy trì hơn 100.000 trong mười giờ với thời gian truy vấn trung bình 25 micro giây. Kiểm thử liên tục, phát hành thử nghiệm và kiểm thử tải giúp giảm rủi ro khi sửa lớp truy cập dữ liệu; việc tạm cấp dư tài nguyên hợp lý cho cao điểm đoán trước được, và sau đó AWS Blue/Green giúp thu nhỏ hạ tầng để khôi phục hiệu quả chi phí.&lt;/p&gt;
&lt;h2 id="gitignore-isnt-the-only-way-to-ignore-files-in-git"&gt;&lt;a href="https://nelson.cloud/.gitignore-isnt-the-only-way-to-ignore-files-in-git/" rel="noopener noreferrer"&gt;.gitignore Isn’t the Only Way To Ignore Files in Git&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Git hỗ trợ ba phạm vi quy tắc bỏ qua tệp, mỗi phạm vi phục vụ một mục đích riêng. &lt;code&gt;.gitignore&lt;/code&gt; được đưa vào kho mã nguồn nên phù hợp với các quy tắc cả nhóm cần chia sẻ. &lt;code&gt;.git/info/exclude&lt;/code&gt; nằm trong thư mục Git của kho và không được theo dõi, chỉ áp dụng cho bản sao kho trên máy hiện tại, rất hợp với ngoại lệ cá nhân như tệp ghi chú hay công cụ riêng không nên xuất hiện trong &lt;code&gt;.gitignore&lt;/code&gt; chung. &lt;code&gt;~/.config/git/ignore&lt;/code&gt; là tệp bỏ qua toàn cục áp dụng cho mọi kho trên máy, phù hợp với các tệp do hệ điều hành hoặc trình soạn thảo tạo ra lặp đi lặp lại, chẳng hạn &lt;code&gt;.DS_Store&lt;/code&gt; trên macOS.&lt;/p&gt;
&lt;p&gt;Có thể đổi vị trí tệp bỏ qua toàn cục bằng &lt;code&gt;git config --global core.excludesFile ~/.gitignore_global&lt;/code&gt; và quay về mặc định bằng &lt;code&gt;git config --global --unset core.excludesFile&lt;/code&gt;. Khi không rõ quy tắc nào đang khiến một tệp bị bỏ qua, &lt;code&gt;git check-ignore -v &amp;lt;tệp&amp;gt;&lt;/code&gt; là cách nhanh nhất để truy nguyên: lệnh hiển thị tệp cấu hình, số dòng và mẫu đã khớp; nếu không in ra gì, tệp đó không bị quy tắc nào bỏ qua.&lt;/p&gt;
&lt;h2 id="the-only-scalable-delete-in-postgres-is-drop-table"&gt;&lt;a href="https://planetscale.com/blog/the-only-scalable-delete" rel="noopener noreferrer"&gt;The only scalable delete in Postgres is DROP TABLE&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Trong PostgreSQL, lệnh &lt;code&gt;DELETE&lt;/code&gt; trên nhiều hàng không giải phóng tài nguyên ngay mà còn tạo thêm việc cho cơ sở dữ liệu. Cơ chế MVCC giữ lại các phiên bản hàng đã chết để những giao dịch đang chạy vẫn thấy dữ liệu phù hợp; autovacuum sau đó chỉ đánh dấu chỗ trống để tái sử dụng chứ không trả dung lượng cho hệ điều hành, còn chỉ mục và truy vấn vẫn phải kiểm tra các hàng chết. Thao tác xóa cũng sinh WAL và phải được sao chép, làm tăng độ trễ ghi, nợ dọn dẹp và độ trễ bản sao; xóa theo dây chuyền qua khóa ngoại còn có thể bất ngờ xóa hàng gigabyte dữ liệu. &lt;code&gt;VACUUM FULL&lt;/code&gt; trả được dung lượng nhưng cần khóa bảng tốn kém.&lt;/p&gt;
&lt;p&gt;Ngược lại, &lt;code&gt;DROP TABLE&lt;/code&gt; và &lt;code&gt;TRUNCATE&lt;/code&gt; cần khóa độc quyền &lt;code&gt;AccessExclusiveLock&lt;/code&gt; nhưng chi phí gần như không phụ thuộc lượng dữ liệu: chúng xóa thẳng tệp vật lý, không tạo hàng chết hay nợ dọn dẹp. Vì vậy, nên thiết kế lược đồ để những lần xóa lớn trở thành thao tác bỏ bảng. Với chính sách lưu giữ dữ liệu dài hạn, phân vùng theo thời gian biến việc xóa thường xuyên thành thỉnh thoảng bỏ một phân vùng. Khi dọn dẹp một lần, có thể sao chép phần cần giữ sang bảng tạm, &lt;code&gt;TRUNCATE&lt;/code&gt; bảng gốc rồi chèn lại, cách này chỉ ghi WAL cho các hàng được giữ; nếu không thể khóa lâu, hãy ghi song song sang bảng mới rồi đổi tên nguyên tử. Khi phần cần giữ lớn hơn nhiều so với phần cần xóa, xóa theo lô nhỏ vẫn là lựa chọn thực tế vì giữ giao dịch ngắn và cho autovacuum thời gian bắt kịp.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Bài viết đã được viết lại bởi Claude Code với Opus 5.5 vào ngày 27/09/2026.&lt;/em&gt;&lt;/p&gt;</description></item><item><title>Newsletter #121</title><link>https://miti99.com/post/2026/07/09/</link><pubDate>Thu, 09 Jul 2026 00:00:00 +0700</pubDate><guid>https://miti99.com/post/2026/07/09/</guid><description>&lt;p&gt;&lt;em&gt;Mời bạn thưởng thức Newsletter #121.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="how-2004-runescape-fit-a-multiplayer-rpg-into-56k-dial-up"&gt;&lt;a href="https://jkm.dev/posts/how-2004-runescape-fit-a-multiplayer-rpg-into-56k-dialup/" rel="noopener noreferrer"&gt;How 2004 RuneScape fit a multiplayer RPG into 56k dial-up&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Bài viết mổ xẻ cách RuneScape năm 2004 vận hành một thế giới nhiều người chơi qua modem 56k, nơi băng thông thực tế chỉ vài KB mỗi giây và client Java applet chỉ có một kết nối TCP. Thay vì gửi dữ liệu tự mô tả, giao thức được thiết kế như một hệ thống chung giữa client và server: hai bên cùng biết bản đồ va chạm, cùng chạy một thuật toán tìm đường và cùng xử lý theo chu kỳ server 600 ms. Nhờ những hiểu biết ngầm không bao giờ phải đi qua mạng đó, một bước đi chỉ tốn khoảng 7 byte chiều lên và 9 byte chiều xuống cho mỗi người quan sát, vì client chỉ gửi các điểm rẽ của đường đi, còn server tự kiểm chứng phần còn lại.&lt;/p&gt;
&lt;p&gt;Phần giá trị nhất là cách bài viết cho thấy tiết kiệm byte không chỉ là nén dữ liệu. Trạng thái phổ biến nhất được làm cực rẻ: mỗi người chơi đứng yên chỉ tốn một bit, nên đứng giữa hai mươi người không di chuyển chỉ cần khoảng năm byte để xác nhận không có gì thay đổi. Người chơi mới xuất hiện trong vùng nhìn được mã hóa bằng tọa độ tương đối 5 bit so với người chơi hiện tại thay vì tọa độ toàn cục 16 bit. Bit packing chỉ dùng cho phần di chuyển nhỏ và dày đặc; phần ngoại hình lớn hơn, ít thay đổi được giữ byte-aligned để server dễ cache và ghép buffer. Bài học thiết kế là wire format nhỏ nhất đến từ việc đồng thiết kế hai đầu hệ thống, đổi lại ta mất tính tự mô tả, khả năng triển khai độc lập và sự dễ gỡ lỗi mà HTTP/JSON hiện đại ưu tiên.&lt;/p&gt;
&lt;h2 id="how-does-struct-take-zero-bytes-in-go"&gt;&lt;a href="https://www.bud1m.com/blog/go-empty-struct/" rel="noopener noreferrer"&gt;How does struct{} take zero bytes in Go&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Bài viết giải thích vì sao &lt;code&gt;struct{}&lt;/code&gt; trong Go có kích thước bằng 0 và thường được dùng để biểu diễn tín hiệu hoặc sự tồn tại thay vì dữ liệu. Với &lt;code&gt;unsafe.Sizeof&lt;/code&gt;, ta thấy giá trị rỗng này không chiếm byte nào; khi làm value trong &lt;code&gt;map[string]struct{}&lt;/code&gt;, ý nghĩa là chỉ quan tâm key có tồn tại hay không, còn trong channel nó chỉ mang tín hiệu mà không kèm dữ liệu. Tác giả cũng lưu ý rằng từ Go 1.24, map dùng Swiss Tables nên &lt;code&gt;map[string]struct{}&lt;/code&gt; và &lt;code&gt;map[string]bool&lt;/code&gt; gần như không còn chênh lệch bộ nhớ đáng kể; chọn &lt;code&gt;struct{}&lt;/code&gt; lúc này chủ yếu để diễn đạt ý định.&lt;/p&gt;
&lt;p&gt;Ở tầng runtime, khi một giá trị zero-size phải cấp phát trên heap, &lt;code&gt;mallocgc&lt;/code&gt; kiểm tra kích thước bằng 0 và trả về địa chỉ của biến toàn cục &lt;code&gt;zerobase&lt;/code&gt; thay vì tạo vùng cấp phát riêng. Giá trị có bị đẩy lên heap hay không do escape analysis quyết định: trong ví dụ, &lt;code&gt;fmt.Println&lt;/code&gt; có thể khiến giá trị escape, còn &lt;code&gt;println&lt;/code&gt; giữ nó trên stack. Cơ chế này dẫn tới hai bẫy cần nhớ. Thứ nhất, nhiều biến &lt;code&gt;struct{}&lt;/code&gt; có thể chung một địa chỉ, và trình biên dịch có thể gộp hoặc không gộp chúng, nên đừng dựa vào so sánh con trỏ để phân biệt các giá trị zero-size. Thứ hai, nếu đặt &lt;code&gt;struct{}&lt;/code&gt; làm field cuối cùng trong một struct khác, runtime sẽ thêm padding để con trỏ tới field đó không trỏ ra ngoài vùng cấp phát; khi cần tối ưu bố cục bộ nhớ, hãy đặt field rỗng ở vị trí khác.&lt;/p&gt;
&lt;h2 id="is-cloudflare-silently-killing-the-web-hosting-industry"&gt;&lt;a href="https://webhosting.today/2026/06/17/is-cloudflare-silently-killing-the-web-hosting-industry/" rel="noopener noreferrer"&gt;Is Cloudflare silently killing the web hosting industry?&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Konrad Keck lập luận rằng Cloudflare không giết ngành hosting bằng cách trở thành một nhà cung cấp hosting truyền thống, mà bằng cách kéo điểm bắt đầu của website sang một quy trình khác. Trước đây khách hàng mua domain, mua shared hosting, vào cPanel, cài WordPress, cấu hình SSL và email, rồi có thể thêm Cloudflare ở lớp CDN và bảo mật. Nhưng khi dự án bắt đầu từ Claude Code, Cursor, Lovable, Bolt, Replit hay các công cụ sinh mã khác, câu hỏi không còn là &amp;ldquo;mua gói hosting nào&amp;rdquo; mà là &amp;ldquo;triển khai ở đâu&amp;rdquo;, và Cloudflare, Vercel, Netlify được thiết kế để chớp lấy đúng khoảnh khắc đó. Cloudflare vì thế cạnh tranh ở quy trình chứ không chỉ ở hạ tầng.&lt;/p&gt;
&lt;p&gt;Hai đòn bẩy được nhấn mạnh là triển khai gần như miễn phí qua Pages, Workers, static assets, R2/D1 với giá theo mức sử dụng, khiến hosting truyền thống mất lợi thế ở landing page, trang tài liệu, frontend và API nhỏ; và domain giá thấp như cửa vào cả hệ sinh thái. Registrar API còn khép kín vòng lặp: agent có thể tạo dự án, gợi ý domain, kiểm tra giá, đăng ký, cấu hình DNS và xuất bản mà người dùng không ghé qua bảng điều khiển hosting nào. Tuy vậy, hosting truyền thống chưa biến mất: WordPress/WooCommerce và PHP cũ, email doanh nghiệp thật, di chuyển hệ thống, hỗ trợ khẩn cấp, thị trường địa phương và yêu cầu tuân thủ vẫn cần dịch vụ có con người. Câu hỏi chiến lược là các nhà cung cấp phải trở thành dịch vụ quản lý chuyên sâu, thay vì chỉ bán điểm khởi đầu mặc định của website.&lt;/p&gt;
&lt;h2 id="don"&gt;&lt;a href="https://staysaasy.com/startups/2026/06/16/stacked-weaknesses.html" rel="noopener noreferrer"&gt;Don&amp;rsquo;t Stack Weaknesses&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Stay SaaSy viết về một lỗi tổ chức dễ bị che khuất ở cấp lãnh đạo: xếp chồng cùng một điểm yếu qua nhiều tầng quản lý. Gần như lãnh đạo nào cũng có điểm yếu đáng kể; một người có thể mạnh chiến lược nhưng yếu vận hành, giỏi kỹ thuật nhưng kém trình bày, và điều đó tự nó chưa phá hỏng tổ chức. Nguy hiểm thật sự là khi lãnh đạo tuyển hoặc phát triển các báo cáo trực tiếp có cùng lỗ hổng. Nếu cả VP lẫn director đều yếu vận hành, khả năng đội bên dưới có quy trình tốt gần như bằng không, vì họ không biết tự làm, không biết tuyển người giỏi phần đó và cũng khó đánh giá chất lượng công việc.&lt;/p&gt;
&lt;p&gt;Lỗi này thường xảy ra vô thức: người tuyển dụng dễ nhận ra và đánh giá cao những kỹ năng giống bản thân, rồi gọi đó là &amp;ldquo;phù hợp giá trị&amp;rdquo; hay &amp;ldquo;phù hợp văn hóa&amp;rdquo;, trong khi thực chất đang nhân bản cùng kiểu năng lực và cùng kiểu điểm mù. Cách tránh bắt đầu bằng việc tự đánh giá trung thực theo sáu nhóm kỹ năng: kiến thức kỹ thuật và lĩnh vực, quản lý, quy trình và vận hành, chiến lược, trình bày, cùng năng lực học hỏi, rồi cố ý tuyển người thật sự giỏi hơn ở những phần bản thân còn yếu. Tác giả cũng cảnh báo đừng tự trấn an bằng câu &amp;ldquo;tôi có thể làm nếu muốn&amp;rdquo;, vì đó thường là cách né tránh việc nhìn nhận năng lực. Một tổ chức không có điểm yếu xếp chồng sẽ bền vững hơn trước những thách thức không lường trước.&lt;/p&gt;
&lt;h2 id="system-design-cheatsheet"&gt;&lt;a href="https://gist.github.com/SuyashDive2005/be5865a9755ca1dd50523f7aeeae4417" rel="noopener noreferrer"&gt;System Design Cheatsheet&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Gist này là một danh sách kiểm tra ngắn cho phỏng vấn và thiết kế hệ thống, bắt đầu từ nguyên tắc: chọn kiến trúc là chọn đúng trận cần đánh và quản lý đánh đổi. Quy trình gồm năm bước quen thuộc: làm rõ phạm vi qua trường hợp sử dụng và ràng buộc; phác thảo kiến trúc mức cao với các thành phần chính như dịch vụ, lưu trữ, bộ nhớ đệm, cân bằng tải; thiết kế chi tiết từng thành phần kèm API và lược đồ dữ liệu; tìm nút thắt; rồi mới mở rộng theo chiều dọc hoặc chiều ngang. Với lập trình viên mới, giá trị lớn nhất là buộc người thiết kế hỏi các câu hỏi định lượng sớm, trước khi chọn công nghệ: bao nhiêu yêu cầu mỗi giây, tỉ lệ đọc/ghi ra sao, dữ liệu lớn đến đâu, độ trễ hay thông lượng quan trọng hơn, và cần ưu tiên tính nhất quán hay tính sẵn sàng.&lt;/p&gt;
&lt;p&gt;Phần mở rộng hệ thống nhắc lại các mảnh ghép nền tảng: bộ nhớ đệm ở nhiều tầng, cân bằng tải, sao chép và phân vùng dữ liệu, MapReduce cho xử lý dữ liệu lớn và lớp nền tảng để từng phần được mở rộng độc lập. Gist cũng liệt kê sáu chủ đề lõi nên nắm: xử lý đồng thời, mạng, trừu tượng hóa, hiệu năng thực tế của phần cứng, ước lượng nhanh để loại sớm phương án không khả thi, và độ tin cậy trong môi trường phân tán. Dù mang tính bảng tóm tắt và có vài ví dụ đã cũ, nó vẫn là một khung tư duy hữu ích: mô tả tải, dữ liệu, chế độ lỗi và đường mở rộng trước khi nhảy vào công nghệ.&lt;/p&gt;
&lt;h2 id="sqlite-improving-performance-with-pre-sort"&gt;&lt;a href="https://andersmurphy.com/2026/06/07/sqlite-improving-performance-with-pre-sort.html" rel="noopener noreferrer"&gt;SQLite improving performance with pre-sort&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Anders Murphy tiếp nối thử nghiệm về UUID4/UUID7 trong SQLite bằng một trường hợp khó hơn: khóa chính là giá trị ngẫu nhiên 160-bit sinh từ &lt;code&gt;SecureRandom&lt;/code&gt;, chẳng hạn mã thông báo phiên, nơi không thể dùng UUID7 vì cần độ ngẫu nhiên tối đa và không muốn lộ thông tin thời gian. Khi chèn theo lô một triệu dòng vào bảng &lt;code&gt;WITHOUT ROWID&lt;/code&gt; có &lt;code&gt;BLOB PRIMARY KEY&lt;/code&gt;, tốc độ chỉ khoảng một trăm nghìn bản ghi mỗi giây và giảm dần khi bảng lớn lên. Nguyên nhân không phải SQLite chậm, mà nằm ở bản chất của B+ Tree: cây luôn được sắp xếp, nên ghi tuần tự thì nhanh, còn khóa ngẫu nhiên làm đụng tới nhiều trang, gây tách trang và cân bằng lại cây liên tục.&lt;/p&gt;
&lt;p&gt;Ý tưởng tối ưu rất đơn giản: vì dữ liệu đã được gom theo lô, hãy sắp xếp từng lô theo khóa trước khi chèn. Do SQLite so sánh BLOB bằng &lt;code&gt;memcmp()&lt;/code&gt;, hàm so sánh phía ứng dụng cần cho ra thứ tự gần giống; tác giả lấy 8 byte đầu của mảng 20 byte và đổi thành số nguyên không dấu để so sánh cho nhanh. Dù tốn thêm chi phí sắp xếp, khi bảng đạt khoảng mười triệu dòng, mỗi lô chèn chỉ mất khoảng 3,8 giây thay vì 11,1 giây, tức nhanh hơn 2-3 lần. Bài học thực dụng: gom theo lô không chỉ giảm số giao dịch mà còn mở ra cơ hội chuẩn bị dữ liệu theo thứ tự thân thiện với bộ máy lưu trữ trước khi ghi xuống.&lt;/p&gt;
&lt;h2 id="why-is-meta-destroying-its-engineering-organization"&gt;&lt;a href="https://newsletter.pragmaticengineer.com/p/why-is-meta-destroying-its-engineering" rel="noopener noreferrer"&gt;Why is Meta destroying its engineering organization?&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Gergely Orosz ghi lại bức tranh căng thẳng về Meta kể từ tháng 4/2026, khi công ty dồn lực cho AI. Meta từng có văn hóa kỹ thuật mạnh: ít quy trình cứng, đề cao tác động cá nhân, hạ tầng ổn định cho phép &amp;ldquo;đi nhanh&amp;rdquo; và kỹ sư được xem là trung tâm tạo lợi nhuận. Nhưng dưới sự dẫn dắt của Mark Zuckerberg và Alexandr Wang, 30-50% kỹ sư cốt lõi ở nhiều đội, kể cả hạ tầng và bảo mật, bị điều sang tổ chức ADO (khoảng 6.500 người) để gán nhãn dữ liệu, đánh giá đầu ra mô hình và làm RLHF. Công ty còn theo dõi thao tác bàn phím, chuột của kỹ sư để lấy dữ liệu huấn luyện mà không cho từ chối (sau đó phải thu hẹp vì phản ứng), đồng thời duy trì áp lực sa thải và đo lượng token AI.&lt;/p&gt;
&lt;p&gt;Điểm đáng chú ý không chỉ là &amp;ldquo;Meta dùng AI quá nhiều&amp;rdquo;, mà là hệ thống động lực bị lệch. Khi lượng token trở thành tín hiệu đánh giá hiệu suất, kỹ sư tối ưu chỉ số đó (60,2 nghìn tỷ token trong 30 ngày) thay vì chất lượng thật. Khi mã do AI sinh ra và chỉ được AI rà soát được đẩy nhanh vào môi trường vận hành trong lúc đội bảo mật bị xáo trộn, rủi ro tích lũy rất nhanh: bài viết liên hệ với sự cố chiếm đoạt tài khoản Instagram ngày 30/5 và việc giám đốc an ninh thông tin của Meta từ chức ngay sau đó. Bài học rộng hơn là đừng nhầm MTTR với chất lượng hệ thống: phục hồi sự cố nhanh không bù được cho hiểu biết con người, rào chắn kiểm soát và trách nhiệm kỹ thuật đang suy giảm.&lt;/p&gt;
&lt;h2 id="cs-6120-advanced-compilers-the-self-guided-online-course"&gt;&lt;a href="https://www.cs.cornell.edu/courses/cs6120/2025fa/self-guided/" rel="noopener noreferrer"&gt;CS 6120: Advanced Compilers: The Self-Guided Online Course&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Trang này là phiên bản tự học của CS 6120, khóa học cấp tiến sĩ tại Cornell do Adrian Sampson giảng về triển khai ngôn ngữ lập trình và trình biên dịch. Nội dung đi từ lõi của trình biên dịch hiện đại như biểu diễn trung gian, phân tích luồng dữ liệu, tối ưu hóa cục bộ và toàn cục, SSA và LLVM, rồi mở sang các chủ đề gần với nghiên cứu như JIT, thu gom rác, song song hóa và trình biên dịch nhanh. Mỗi bài học kết hợp video, ghi chú, bài báo học thuật kinh điển và bài thực hành mở để biến khái niệm trừu tượng thành mã nguồn chạy được.&lt;/p&gt;
&lt;p&gt;Phiên bản tự học giữ cấu trúc tuyến tính của khóa thật nhưng bỏ hạn nộp, chấm điểm và phần thảo luận nội bộ. Người học đi theo thứ tự đề xuất: xem bài giảng, đọc bài báo, rồi làm bài triển khai với LLVM hoặc Bril, một biểu diễn trung gian nhỏ dùng cho giảng dạy, dễ sửa hơn nhiều so với bắt tay ngay vào trình biên dịch sản xuất, và kết thúc bằng một dự án cuối khóa. Đây là tài nguyên tốt để hiểu rằng trình biên dịch không chỉ là bộ phân tích cú pháp cộng bộ sinh mã, mà là chuỗi quyết định về biểu diễn chương trình, phân tích, tối ưu và đánh đổi giữa độ đúng, tốc độ biên dịch và chất lượng mã sinh ra. Cách học hiệu quả nhất là vừa đọc vừa tự viết các lượt biến đổi nhỏ, vì khái niệm trình biên dịch rất dễ hiểu sai nếu chỉ đọc lý thuyết.&lt;/p&gt;
&lt;h2 id="epoll-vs-io_"&gt;&lt;a href="https://sibexi.co/posts/epoll-vs-io_uring/" rel="noopener noreferrer"&gt;epoll vs io_uring in Linux&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Bài viết so sánh &lt;code&gt;epoll&lt;/code&gt; và &lt;code&gt;io_uring&lt;/code&gt; từ trải nghiệm xây dựng TinyGate, một máy chủ ủy quyền ngược. Phiên bản đầu dùng kiến trúc worker đơn giản, dễ hiểu nhưng giới hạn hiệu năng rõ rệt; phiên bản sau chuyển sang &lt;code&gt;epoll&lt;/code&gt; và nhanh hơn nhiều, nhưng vẫn phải trả giá cho mô hình sẵn sàng: &lt;code&gt;epoll&lt;/code&gt; chỉ báo khi mô tả tệp có thể đọc/ghi, còn ứng dụng vẫn phải tự gọi &lt;code&gt;read()&lt;/code&gt; hoặc &lt;code&gt;write()&lt;/code&gt;. Mỗi sự kiện I/O vì thế tốn thêm lời gọi hệ thống ngoài lần đăng ký &lt;code&gt;epoll_ctl&lt;/code&gt;, và với lượng kết nối lớn, chi phí chuyển ngữ cảnh giữa không gian người dùng và nhân hệ điều hành trở nên đáng kể.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;io_uring&lt;/code&gt; đổi sang mô hình hoàn tất: ứng dụng đưa yêu cầu vào hàng đợi gửi nằm trong vùng nhớ chia sẻ, nhân xử lý rồi trả kết quả qua hàng đợi hoàn tất. Một lời gọi &lt;code&gt;io_uring_enter()&lt;/code&gt; có thể nộp và nhận cả lô thao tác thay vì trả phí cho từng &lt;code&gt;read()&lt;/code&gt;/&lt;code&gt;write()&lt;/code&gt;. Bật &lt;code&gt;IORING_SETUP_SQPOLL&lt;/code&gt; còn giảm gần như hết lời gọi hệ thống ở trạng thái ổn định, đổi lại một luồng nhân phải liên tục thăm dò và tiêu thụ CPU. Tác giả khuyên dự án mới trên Linux hiện đại (nhân 5.1 trở lên) nên cân nhắc &lt;code&gt;io_uring&lt;/code&gt;, nhưng cần tính trước các chi tiết: zero-copy đòi hỏi đăng ký vùng đệm đúng cách, lỗi được trả bất đồng bộ qua hàng đợi hoàn tất, và các ví dụ tối giản thường thiếu nhiều kiểm tra cần có trong mã vận hành thật.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Bài viết đã được viết lại bởi Claude Code với Opus 5.5 vào ngày 27/09/2026.&lt;/em&gt;&lt;/p&gt;</description></item><item><title>Newsletter #120</title><link>https://miti99.com/post/2026/07/08/</link><pubDate>Wed, 08 Jul 2026 00:00:00 +0700</pubDate><guid>https://miti99.com/post/2026/07/08/</guid><description>&lt;p&gt;&lt;em&gt;Mời bạn thưởng thức Newsletter #120.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="lessons-from-building-claude-code-how-we-use-skills"&gt;&lt;a href="https://claude.com/blog/lessons-from-building-claude-code-how-we-use-skills" rel="noopener noreferrer"&gt;Lessons from building Claude Code: How we use skills&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Thariq Shihipar, thành viên đội Claude Code tại Anthropic, tổng kết những bài học rút ra sau khi đội ngũ xây dựng và sử dụng hàng trăm skill nội bộ. Điểm cốt lõi là skill không chỉ là một file Markdown chứa hướng dẫn mà là cả một thư mục có thể gồm script, tài nguyên, dữ liệu tham chiếu, hook và cấu hình. Cách tổ chức này cho phép tiết lộ dần thông tin (progressive disclosure): agent đọc &lt;code&gt;SKILL.md&lt;/code&gt; trước, rồi chỉ mở tài liệu hoặc script phụ khi nhiệm vụ thực sự cần, nhờ đó context luôn gọn. Tác giả chia skill thành chín nhóm, từ tài liệu thư viện/API, xác minh sản phẩm, phân tích dữ liệu, tự động hóa quy trình, dựng khung mã nguồn, review mã, CI/CD, runbook đến vận hành hạ tầng.&lt;/p&gt;
&lt;p&gt;Về cách viết, bài khuyên đừng lặp lại những điều Claude vốn đã biết mà tập trung vào phần đẩy nó ra khỏi lối suy nghĩ mặc định, đặc biệt là mục gotchas ghi lại các lỗi, quy ước và ngoại lệ agent hay bỏ sót, được bồi đắp dần qua từng lần gặp lỗi thực tế. Nên nêu mục tiêu thay vì ép từng bước cứng nhắc, cung cấp cơ chế ghi nhớ (log hoặc file JSON) để giữ trạng thái giữa các phiên, và đính kèm script trợ giúp để agent ghép lại thay vì tự dựng lại mọi thao tác bằng suy luận. Với đội lớn, skill có thể được đưa thẳng vào repository hoặc phân phối qua marketplace plugin nội bộ, để mỗi người tự chọn cài skill cần dùng và tránh làm phình context; hook có thể dùng để theo dõi mức độ sử dụng, từ đó loại bỏ những skill quá rộng hoặc ít được gọi.&lt;/p&gt;
&lt;h2 id="bulkhead-pattern---go"&gt;&lt;a href="https://dev.to/kamal_namdeo/bulkhead-pattern-go-362c" rel="noopener noreferrer"&gt;Bulkhead Pattern - Go&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Kamal Namdeo trình bày Bulkhead Pattern trong Go: cô lập tài nguyên theo từng dependency để một dịch vụ chậm hay lỗi không kéo cạn goroutine, kết nối và thời gian chờ của cả hệ thống. Thay vì để mọi lời gọi HTTP dùng chung &lt;code&gt;http.DefaultTransport&lt;/code&gt;, mỗi dịch vụ phía sau có bulkhead riêng gồm ba lớp: semaphore giới hạn số request đang xử lý (dùng &lt;code&gt;TryAcquire&lt;/code&gt; để từ chối ngay khi đầy), connection pool riêng qua &lt;code&gt;MaxConnsPerHost&lt;/code&gt; để một host không chiếm hết kết nối TCP, và các timeout chặt để nhanh chóng giải phóng tài nguyên khi phía bên kia gặp sự cố. Nhờ vậy, khi dịch vụ xác thực bị nghẽn, thanh toán hay S3 vẫn chạy bình thường.&lt;/p&gt;
&lt;p&gt;Phần giá trị nhất là cách xác định kích thước bằng tính toán thay vì cảm tính. Theo Little&amp;rsquo;s Law, số request đồng thời cần có bằng RPS mục tiêu nhân với độ trễ p99, cộng thêm khoảng 30% dự phòng cho lúc tải tăng đột biến; dependency càng chậm thì càng cần nhiều slot để giữ cùng thông lượng. Phạm vi ảnh hưởng khi lỗi được khống chế bằng các timeout như &lt;code&gt;ResponseHeaderTimeout&lt;/code&gt; (khoảng p99 nhân 1,2), &lt;code&gt;Client.Timeout&lt;/code&gt; cho toàn bộ vòng đời request, thời gian bắt tay TCP một đến hai giây và &lt;code&gt;QueueTimeout&lt;/code&gt; tính từ SLA trừ đi độ trễ phía sau. Tác giả cũng nhắc các lỗi Go dễ gặp: &lt;code&gt;runtime.NumCPU()&lt;/code&gt; không phù hợp để chọn mức đồng thời cho tác vụ IO vì goroutine chờ mạng không tốn CPU, cần đọc hết response body trước khi đóng để tái sử dụng kết nối, và đặt &lt;code&gt;MaxIdleConns&lt;/code&gt; bằng tổng giới hạn kết nối rảnh của từng host để các dependency không đẩy kết nối của nhau ra ngoài.&lt;/p&gt;
&lt;h2 id="max-consecutive-ones"&gt;&lt;a href="https://dev.to/jaspreet_singh_86ae1740ac/max-consecutive-ones-13mj" rel="noopener noreferrer"&gt;Max Consecutive Ones&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Jaspreet Singh giải thích bài LeetCode cơ bản: cho một mảng nhị phân, tìm độ dài dãy số &lt;code&gt;1&lt;/code&gt; liên tiếp dài nhất, với lời giải viết bằng Java. Cách vét cạn là tại mỗi vị trí có giá trị &lt;code&gt;1&lt;/code&gt;, quét tiếp về phía sau để đếm dãy hiện tại và cập nhật giá trị lớn nhất. Cách này dễ trình bày khi phỏng vấn nhưng duyệt lại cùng một phần tử nhiều lần, nên độ phức tạp thời gian là &lt;code&gt;O(N²)&lt;/code&gt;, dù bộ nhớ chỉ tốn &lt;code&gt;O(1)&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Lời giải tối ưu dựa trên một nhận xét đơn giản: dãy liên tiếp chỉ kéo dài khi ta còn gặp số &lt;code&gt;1&lt;/code&gt; và bị ngắt ngay khi xuất hiện số &lt;code&gt;0&lt;/code&gt;, nên chỉ cần biết dãy hiện tại dài bao nhiêu. Ta giữ hai biến là độ dài dãy hiện tại và độ dài lớn nhất từng thấy; gặp &lt;code&gt;1&lt;/code&gt; thì tăng biến đếm và cập nhật giá trị lớn nhất, gặp &lt;code&gt;0&lt;/code&gt; thì đặt lại biến đếm về 0. Thuật toán chỉ duyệt mảng một lần, đạt thời gian &lt;code&gt;O(N)&lt;/code&gt; và bộ nhớ &lt;code&gt;O(1)&lt;/code&gt;, không cần vòng lặp lồng nhau. Theo tác giả, nhận ra mẫu &amp;ldquo;duy trì chuỗi hiện tại, cập nhật cực đại, đặt lại khi điều kiện bị phá vỡ&amp;rdquo; giúp giải nhanh nhiều bài phỏng vấn khác về đoạn liên tiếp dài nhất.&lt;/p&gt;
&lt;h2 id="double-bigdecimal-or-fixed-point"&gt;&lt;a href="https://blog.frankel.ch/bigdecimal-vs-double/" rel="noopener noreferrer"&gt;double, BigDecimal, or Fixed-Point?&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Stefano Fago phản biện các khẩu hiệu kiểu &amp;ldquo;tiền thì luôn dùng &lt;code&gt;BigDecimal&lt;/code&gt;&amp;rdquo; hay &amp;ldquo;&lt;code&gt;double&lt;/code&gt; bị hỏng&amp;rdquo;. Theo IEEE 754, &lt;code&gt;double&lt;/code&gt; là số nhị phân gần đúng nên các giá trị thập phân như &lt;code&gt;0.1&lt;/code&gt; không được biểu diễn chính xác, nhưng đó là đánh đổi có chủ đích. Với dữ liệu vốn gần đúng như phân tích, học máy hay mô phỏng, &lt;code&gt;double&lt;/code&gt; thường là lựa chọn đúng vì nhanh và chạy trực tiếp trên phần cứng; điều cần làm là so sánh bằng ngưỡng sai số thay vì &lt;code&gt;==&lt;/code&gt;, xử lý &lt;code&gt;NaN&lt;/code&gt;, &lt;code&gt;-0.0&lt;/code&gt;, và dùng các kỹ thuật như Kahan, Neumaier, cộng theo cặp hoặc &lt;code&gt;Math.fma()&lt;/code&gt; khi muốn giảm sai số tích lũy.&lt;/p&gt;
&lt;p&gt;Ngược lại, &lt;code&gt;BigDecimal&lt;/code&gt; phù hợp khi cần độ chính xác thập phân, quy tắc làm tròn rõ ràng và khả năng kiểm toán, như thuế, hóa đơn hay kế toán, nhưng phải trả giá bằng việc cấp phát đối tượng và phép tính độ chính xác tùy ý. Nó cũng có nhiều bẫy: &lt;code&gt;new BigDecimal(0.1)&lt;/code&gt; mang theo sai số của &lt;code&gt;double&lt;/code&gt; (nên dùng &lt;code&gt;new BigDecimal(&amp;quot;0.1&amp;quot;)&lt;/code&gt; hoặc &lt;code&gt;BigDecimal.valueOf(0.1)&lt;/code&gt;), quên gán lại kết quả vì đối tượng bất biến, hay dùng &lt;code&gt;equals()&lt;/code&gt; vốn so cả scale thay vì &lt;code&gt;compareTo()&lt;/code&gt;. Với hệ thống tài chính hiệu năng cao, số điểm cố định bằng &lt;code&gt;long&lt;/code&gt; là phương án thực dụng: lưu &lt;code&gt;19.99&lt;/code&gt; thành &lt;code&gt;1999&lt;/code&gt; xu, dùng &lt;code&gt;Math.addExact&lt;/code&gt; hoặc &lt;code&gt;multiplyExact&lt;/code&gt; để phát hiện tràn số, rồi chuyển về &lt;code&gt;BigDecimal&lt;/code&gt; ở biên hệ thống. Tác giả cũng cảnh báo việc mất scale khi tuần tự hóa JSON và kết quả không tái lập được với &lt;code&gt;DoubleStream&lt;/code&gt; song song. Kết luận: hãy chọn kiểu số theo yêu cầu độ chính xác, quy tắc làm tròn và ràng buộc hiệu năng của từng miền nghiệp vụ, không theo khẩu hiệu.&lt;/p&gt;
&lt;h2 id="can-java-microservices-be-as-fast-as-go-a-2026-benchmark-update"&gt;&lt;a href="https://medium.com/helidon/can-java-microservices-be-as-fast-as-go-a-2026-benchmark-update-e16a2e262fc4" rel="noopener noreferrer"&gt;Can Java Microservices Be As Fast As Go? A 2026 Benchmark Update&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Sáu năm sau, Mark Nelson quay lại câu hỏi: một microservice Java nhỏ có nhanh ngang Go không? Tác giả so sánh Go 1.26.3 dùng &lt;code&gt;net/http&lt;/code&gt; với Helidon SE 4.4.1 (xử lý request bằng virtual thread) trên Oracle JDK 26.0.1, kèm một biến thể dùng Leyden AOT cache. Dịch vụ cố ý nhỏ và mang tính tổng hợp: endpoint sinh dữ liệu đầu vào 7, 128, 2048 hoặc 8192 byte, thực hiện chuyển chữ hoa, chữ thường, đảo chuỗi và tính CRC32 với &lt;code&gt;WORK_FACTOR=10&lt;/code&gt;, rồi trả JSON. Hai bên chạy lần lượt, có làm nóng riêng và tắt ghi log. Trước khi đo, tác giả phát hiện Helidon có độ trễ tối thiểu khoảng 44–48 ms với phản hồi lớn trên kết nối HTTP/1.1 dùng lại; chỉ cần bật &lt;code&gt;tcpNoDelay(true)&lt;/code&gt; là vấn đề biến mất.&lt;/p&gt;
&lt;p&gt;Kết quả đáng chú ý nằm ở hình dạng đường cong chứ không phải câu &amp;ldquo;Java thắng Go&amp;rdquo;. Với dữ liệu nhỏ và mức đồng thời thấp, cả ba cấu hình ở cùng khoảng hiệu năng. Khi số kết nối đồng thời và kích thước dữ liệu tăng, Java mở rộng tốt hơn rõ rệt: ở 2 KB, Go đạt đỉnh khoảng 17 nghìn request mỗi giây, trong khi JVM thường đạt khoảng 39,5 nghìn và Leyden AOT khoảng 41,6 nghìn. Leyden AOT có thông lượng đỉnh cao nhất ở mọi kích thước dữ liệu, còn Go không thắng ô đo nào dù bám sát ở các trường hợp nhỏ nhất. Tác giả nhấn mạnh không nên dùng các con số này để áp chính sách ngôn ngữ cho cả công ty mà hãy tự đo trên khối lượng công việc thực tế; runtime, framework, phần cứng, làm nóng, ghi log và thiết lập socket thường quan trọng không kém ngôn ngữ.&lt;/p&gt;
&lt;h2 id="stop-using-conventional-commits"&gt;&lt;a href="https://sumnerevans.com/posts/software-engineering/stop-using-conventional-commits/" rel="noopener noreferrer"&gt;Stop Using Conventional Commits&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Sumner Evans phản đối Conventional Commits vì cho rằng định dạng này đặt trọng tâm sai chỗ. Theo tác giả, thông tin quan trọng nhất của một commit message là scope, tức khu vực mã nguồn, hệ thống con, package hay dịch vụ bị thay đổi. Người đọc lịch sử commit thường đang rebase, gỡ lỗi hoặc điều tra sự cố, nên họ cần biết ngay phần nào của hệ thống vừa đổi. Thế nhưng Conventional Commits lại đưa loại thay đổi như &lt;code&gt;fix&lt;/code&gt;, &lt;code&gt;feat&lt;/code&gt;, &lt;code&gt;chore&lt;/code&gt; lên đầu và để scope là tùy chọn. Trong khi đó, loại thay đổi thường đã hiển nhiên từ phần mô tả, và đôi khi còn gây tranh cãi vì một commit có thể vừa sửa lỗi, vừa tái cấu trúc, vừa thêm tính năng.&lt;/p&gt;
&lt;p&gt;Bài viết lần lượt bác bỏ các lợi ích mà Conventional Commits hứa hẹn. Tự động sinh changelog từ lịch sử commit cho kết quả kém vì changelog phục vụ người dùng còn commit log phục vụ lập trình viên, hai đối tượng cần mức chi tiết khác nhau. Tự động tăng phiên bản theo semantic versioning dễ sai khi có revert, thay đổi phá vỡ tương thích bị phát hiện muộn hoặc được sửa lại sau đó. Dựa vào loại commit để kích hoạt pipeline còn tiềm ẩn rủi ro bảo mật, vì tự động hóa nên căn cứ vào các file thực sự thay đổi chứ không phải dòng tiêu đề do người gõ. Thay vào đó, tác giả khuyến nghị kiểu commit bắt đầu bằng scope như Linux, FreeBSD, Git, Go hay Node.js đang dùng, dạng &lt;code&gt;scope: mô tả&lt;/code&gt;, ví dụ &lt;code&gt;net/http/cookiejar: add godoc links&lt;/code&gt;, với scope phản ánh cách chia tự nhiên của từng dự án.&lt;/p&gt;
&lt;h2 id="stop-using-jwts"&gt;&lt;a href="https://gist.github.com/samsch/0d1f3d3b4745d778f78b230cf6061452" rel="noopener noreferrer"&gt;Stop using JWTs!&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Sam Schlinkert lập luận rằng không nên dùng JWT để duy trì phiên đăng nhập của người dùng. Đặc tả JWT vốn được thiết kế cho token sống rất ngắn, khoảng năm phút trở xuống, trong khi phiên đăng nhập cần vòng đời dài hơn và khả năng thu hồi. Ý tưởng &amp;ldquo;xác thực không trạng thái&amp;rdquo; thực chất không an toàn: để làm đúng, ta vẫn phải lưu trạng thái ở đâu đó thông qua danh sách chặn, xoay vòng hay refresh token. Khi đã phải có kho dữ liệu, cookie session truyền thống đơn giản và linh hoạt hơn. Tác giả cũng cho biết giới chuyên gia bảo mật không tin tưởng bản thân đặc tả JWT, vì phiên bản ban đầu từng cho phép tạo token giả.&lt;/p&gt;
&lt;p&gt;Gist còn đính chính một hiểu nhầm phổ biến: Google không dùng JWT cho phiên trình duyệt mà dùng cookie session thông thường với các bản ghi được ký và mã hóa, còn JWT chỉ xuất hiện trong kịch bản đăng nhập một lần (SSO). Tác giả cảnh báo không lưu thông tin xác thực, kể cả JWT, trong &lt;code&gt;localStorage&lt;/code&gt; vì dễ bị lộ qua tấn công XSS. Nếu thật sự cần token ngắn hạn có chữ ký, nên cân nhắc PASETO, một đặc tả được thiết kế an toàn hơn. Với lập trình viên mới, bài học thực dụng là bắt đầu từ cơ chế session có sẵn của framework, dùng cookie &lt;code&gt;Secure&lt;/code&gt; và &lt;code&gt;HttpOnly&lt;/code&gt; cùng kho lưu phiên phù hợp như Redis hay cơ sở dữ liệu, và chỉ thêm token đặc biệt khi có nhu cầu thực sự.&lt;/p&gt;
&lt;h3 id="bonus"&gt;Bonus&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;Images:&lt;/strong&gt;
&lt;img src="https://substackcdn.com/image/fetch/$s_!N2N1!,w_1100,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5edb76e4-d060-48d2-bd73-afe04f1cff5a_1284x1536.jpeg" alt="The AI Agent Stack, Explained"&gt;
&lt;img src="https://substackcdn.com/image/fetch/$s_!wI2g!,w_1100,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F148840a3-d7df-4308-adad-c227f4d280e8_2360x2960.png" alt="Understanding Git Reset Modes"&gt;
&lt;img src="https://substackcdn.com/image/fetch/$s_!CKOS!,w_1100,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8d2521b0-1dd8-4b28-afbd-34f2bb44ee50_2360x2960.png" alt="How NAT Works"&gt;
&lt;img src="https://substackcdn.com/image/fetch/$s_!HMwY!,w_1100,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6af9e24b-9be2-4a45-9f81-14ec6f4330ca_2484x3002.png" alt="SLMs vs. LLMs, Clearly Explained"&gt;
&lt;img src="https://substackcdn.com/image/fetch/$s_!Z5zI!,w_1100,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5233378c-3c59-40b1-9de2-6515b9d3e928_2484x3002.png" alt="Single Agent vs. Multi-Agent Architecture"&gt;
&lt;img src="https://substackcdn.com/image/fetch/$s_!U8C6!,w_1100,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1b849efb-0467-4a5d-b735-f2296b125e9f_2484x3002.png" alt="7 Permission Modes Every Claude Code User Should Know"&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Videos:&lt;/strong&gt;
&lt;a href="https://www.youtube.com/watch?v=U8lGbSaCCYI" rel="noopener noreferrer"&gt;Chạy LLM cục bộ để học và bảo vệ quyền riêng tư&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
 &lt;p&gt;Video của ByteByteGo giải thích vì sao chạy LLM ngay trên máy cá nhân có lợi cho việc học, thử nghiệm và quyền riêng tư: dữ liệu không phải gửi ra dịch vụ bên ngoài, có thể dùng khi không có mạng và kiểm soát chi phí tốt hơn. Video cũng điểm qua các công cụ như Ollama, LM Studio, MLX-LM, vLLM hay SGLang, tùy mục tiêu từ thử nghiệm cá nhân đến phục vụ suy luận hiệu năng cao.&lt;/p&gt;

&lt;/blockquote&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Bài viết đã được viết lại bởi Claude Code với Opus 5.5 vào ngày 27/09/2026.&lt;/em&gt;&lt;/p&gt;</description></item><item><title>Newsletter #119</title><link>https://miti99.com/post/2026/07/07/</link><pubDate>Tue, 07 Jul 2026 00:00:00 +0700</pubDate><guid>https://miti99.com/post/2026/07/07/</guid><description>&lt;p&gt;&lt;em&gt;Mời bạn thưởng thức Newsletter #119.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="special-cases-in-go"&gt;&lt;a href="https://www.dolthub.com/blog/2026-05-22-special-cases-in-go/" rel="noopener noreferrer"&gt;Special Cases in Go&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Go thường được quảng bá là ngôn ngữ &amp;ldquo;đơn giản&amp;rdquo;, cố tình tránh những trường hợp đặc biệt. Nick Tobey chỉ ra rằng thực tế Go vẫn có những ngoại lệ như vậy, ở những chỗ khá bất ngờ. Hàm &lt;code&gt;init&lt;/code&gt; là ví dụ rõ nhất: nó được gọi tự động khi package được import, không nhận tham số, không trả về giá trị, được khai báo nhiều lần trong cùng một package, nhưng lại không thể gọi thủ công. Nếu cố gọi, trình biên dịch báo lỗi &lt;code&gt;undefined: init&lt;/code&gt; mà không giải thích gì thêm. Tương tự, thư mục &lt;code&gt;internal&lt;/code&gt; tạo ra mức hiển thị hẹp hơn công khai/riêng tư, nhưng quy tắc này không có trong đặc tả ngôn ngữ mà do bộ công cụ Go áp đặt.&lt;/p&gt;
&lt;p&gt;Lý do nằm ở việc đặc tả cố ý để cách diễn giải đường dẫn import phụ thuộc vào cài đặt. Nhờ vậy, các hệ thống build như Bazel có thể dùng quy tắc riêng, còn bộ công cụ Go có thể tiến hóa mà không phải sửa đặc tả, đồng thời thêm các ngoại lệ như &lt;code&gt;internal&lt;/code&gt; hay package kiểm thử có hậu tố &lt;code&gt;_test&lt;/code&gt; sống chung thư mục với package chính. Tác giả cũng lưu ý tên package không bắt buộc trùng tên thư mục, nên IDE phải phân tích mọi package được import mới xác định được một định danh đến từ đâu; với dự án mới, nên đặt tên import tường minh. Bài học rút ra: phần lớn các ngoại lệ này hiếm khi gây phiền trong công việc hằng ngày, nhưng chúng cho thấy sự đơn giản của một ngôn ngữ thường chỉ là độ phức tạp được chuyển sang chỗ khác.&lt;/p&gt;
&lt;h2 id="writing-code-vs-shipping-code-productivity-effects-across-generations-of-ai-coding-tools"&gt;&lt;a href="https://muratbuffalo.blogspot.com/2026/06/writing-code-vs-shipping-code.html" rel="noopener noreferrer"&gt;Writing Code vs. Shipping Code: Productivity Effects Across Generations of AI Coding Tools&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Murat Demirbas phân tích một nghiên cứu của MIT và Wharton, kết hợp dữ liệu đo lường nội bộ của Microsoft với dấu vết công khai của hơn 100.000 lập trình viên trên GitHub, để đo tác động thật của công cụ AI lên năng suất. Nghiên cứu chia công cụ thành ba thế hệ: gợi ý tự động, agent đồng bộ như Claude Code hay Cursor, và agent bất đồng bộ. Mức tăng ở tầng dòng mã rất ấn tượng, nhưng hao hụt dần qua các tầng file, commit, pull request, repository cho tới bản phát hành: với gợi ý tự động, số dòng mã tăng hơn 228% nhưng số bản phát hành chỉ tăng khoảng 10%; với agent đồng bộ, con số tương ứng là 741% và 20%.&lt;/p&gt;
&lt;p&gt;Tác giả hoài nghi mô hình sản xuất tuyến tính mà các nhà kinh tế áp lên phát triển phần mềm, nhưng vẫn thấy nó hữu ích vì làm lộ rõ chi phí phối hợp và kiểm duyệt của con người. Chuyển kết quả sang định luật Amdahl, ông tính ra phần công việc có thể tăng tốc chỉ quanh mức 35% ở cả ba thế hệ công cụ, khớp với tỷ lệ thời gian lập trình viên thật sự viết mã. Agent tạo ra nhiều bản phát hành hơn không phải vì mở rộng phần đó, mà vì tăng tốc mạnh hơn ở chính phần có thể tăng tốc và can thiệp gần đích hơn. Phần còn lại, gồm hiểu bài toán, lên kế hoạch, thống nhất trong nhóm và review, vẫn là nút thắt tuần tự. Muốn phát hành nhanh hơn thật sự, tổ chức phải cải thiện cả những khâu đó chứ không chỉ khâu viết mã.&lt;/p&gt;
&lt;h2 id="when-was-the-last-time-you-did-just-one-thing"&gt;&lt;a href="https://alifeengineered.substack.com/p/when-was-the-last-time-you-did-just" rel="noopener noreferrer"&gt;When Was the Last Time You Did Just One Thing?&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Steve Huynh kể lại lúc chuẩn bị cho một chuyến đi dài: ông tải hàng chục video YouTube, xếp sẵn một loạt podcast, thêm một trò chơi, hai cuốn sách giấy, chiếc Kindle và cả laptop để phòng khi muốn làm việc. Rồi ông chợt nhận ra &amp;ldquo;kho vũ khí&amp;rdquo; chống lại mọi khoảnh khắc trống rỗng ấy sẽ khiến ông bỏ lỡ chính chuyến đi, và nghịch lý là càng cố không lãng phí giây phút nào thì mọi giây phút lại càng trống rỗng. Bản xem trước công khai dừng ở đây, nhưng thông điệp từ tiêu đề và phụ đề đã rõ: làm một việc tại một thời điểm không còn là trạng thái mặc định, mà đã thành một kỹ năng cần rèn luyện.&lt;/p&gt;
&lt;p&gt;Với người làm công nghệ, đây là lời nhắc thực tế về quản lý sự chú ý. Không phải khoảng trống nào cũng cần lấp bằng việc học, nghe podcast hay làm thêm. Khi mọi khe hở đều bị nhồi kích thích mới, ta dần mất khả năng quan sát, nghỉ ngơi đúng nghĩa, suy nghĩ sâu và thật sự hiện diện với việc đang làm. Trông có vẻ hiệu quả chưa chắc đã là sống có chủ đích; để lại khoảng trống cũng là một phần của năng suất, nhất là trong những ngày làm việc vốn đã phải chuyển ngữ cảnh liên tục.&lt;/p&gt;
&lt;h2 id="predicting-ai-job-exposure"&gt;&lt;a href="https://www.ben-evans.com/benedictevans/2026/5/24/ai-job-exposure" rel="noopener noreferrer"&gt;Predicting AI job exposure&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Benedict Evans phản biện các nỗ lực chấm điểm nghề nghiệp, công ty hay ngành nào chịu ảnh hưởng nhiều nhất từ AI. Theo ông, đây là việc dự đoán điều không thể dự đoán, và cách đơn giản nhất để thấy vấn đề là kiểm tra ngược với các làn sóng công nghệ trước. Suốt một thế kỷ, ta tự động hóa kế toán bằng máy tính, cơ sở dữ liệu, bảng tính, ERP, vậy mà số kế toán viên vẫn tăng. Khi phân tích trở nên rẻ và nhanh, người ta làm nhiều phân tích hơn và theo kiểu khác, trong khi quy định mới cũng tạo thêm nhu cầu tuyển dụng.&lt;/p&gt;
&lt;p&gt;Vấn đề thứ hai là công việc có thể không đổi nhưng mô hình kinh doanh bên dưới lại sụp: internet không thay đổi kỹ năng của nhà báo, nhưng phá nguồn thu từ quảng cáo rao vặt vốn nuôi tòa soạn. Thứ ba là những cú sốc khó thấy trước, như dữ liệu vị trí trên điện thoại dẫn tới Uber và thay đổi hẳn nghề taxi. Sâu hơn nữa, các bộ mô tả nghề như O*NET không thể mô tả đầy đủ một công việc thật, giống như thất bại của hệ chuyên gia ngày trước. Evans cho rằng những khung tư duy vẫn hữu ích, nhưng mọi dự báo cụ thể cho từng nghề ở giai đoạn sớm này chỉ đúng nhờ may mắn, vì ta không biết các ngoại lệ có lớn hơn quy luật hay không.&lt;/p&gt;
&lt;h2 id="choosing-values-for-robust-tests"&gt;&lt;a href="https://testing.googleblog.com/2026/06/choosing-values-for-robust-tests.html" rel="noopener noreferrer"&gt;Choosing Values for Robust Tests&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Radion Khait chỉ ra một lỗi kiểm thử rất dễ bỏ sót: bài kiểm thử vẫn đạt không phải vì mã đúng, mà vì giá trị được chọn tình cờ trùng với giá trị mặc định. Ví dụ trong bài là hàm &lt;code&gt;insert&lt;/code&gt; của một lớp map bỏ quên tham số &lt;code&gt;value&lt;/code&gt;, chỉ khởi tạo phần tử với giá trị mặc định. Bài kiểm thử chèn giá trị &lt;code&gt;0&lt;/code&gt; rồi kiểm tra lại vẫn đạt, vì &lt;code&gt;0&lt;/code&gt; đúng bằng giá trị mặc định của kiểu số nguyên. Kết quả là cảm giác an toàn giả: hàm được gọi, nhưng không có gì chứng minh dữ liệu đầu vào thật sự được lưu.&lt;/p&gt;
&lt;p&gt;Từ đó, tác giả đưa ra vài nguyên tắc chọn dữ liệu kiểm thử. Thứ nhất, dùng giá trị khác mặc định: số khác không, chuỗi không rỗng, giá trị enum không nằm ở vị trí đầu; chỉ cần đổi sang &lt;code&gt;5&lt;/code&gt;, bài kiểm thử trên sẽ lộ lỗi ngay. Thứ hai, khi hợp lý, hãy kiểm thử nhiều đầu vào đại diện cho các tình huống khác nhau như rỗng, thiếu, &lt;code&gt;null&lt;/code&gt;, giá trị biên và các trường hợp kích hoạt logic phức tạp, có thể dùng fuzzing để phủ rộng miền đầu vào. Thứ ba, dùng giá trị khác nhau cho từng tham số để phát hiện mã vô tình dùng lại một tham số hoặc đảo thứ tự; kiểm thử tham số hóa giúp thử nhiều đầu vào mà không phải lặp mã.&lt;/p&gt;
&lt;h2 id="ai-demands-more-engineering-discipline-not-less"&gt;&lt;a href="https://charity.wtf/p/ai-demands-more-engineering-discipline" rel="noopener noreferrer"&gt;AI demands more engineering discipline. Not less&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Charity Majors phản bác một suy nghĩ đang lan rộng: nếu AI sinh mã nhanh và đủ tốt thì có nên nới lỏng review, kiểm chứng và quy trình kỹ thuật không? Câu trả lời của bà là ngược lại. Khi chi phí tạo mã giảm mạnh, dòng mã không còn là tài sản quý mà chỉ là một cách hiện thực tạm thời của hiểu biết hiện tại. Sản phẩm thật của một nhóm phần mềm là hiểu biết chung, và thứ có giá trị là những gì giúp ta biết hệ thống đúng hay sai: yêu cầu, bất biến, các kiểu lỗi, kiểm thử, khả năng quan sát và khả năng tái hiện.&lt;/p&gt;
&lt;p&gt;Bà so sánh làn sóng AI với bước chuyển từ những máy chủ được chăm sóc thủ công như thú cưng sang hạ tầng bất biến. Khi hạ tầng có thể xóa đi và dựng lại, kỷ luật kỹ thuật dời sang cấu hình, quy trình khởi tạo, kiểm chứng và vận hành lặp lại được. Phần mềm đang đi theo hướng tương tự: nếu mã có thể tái sinh với giá rẻ, từng dòng mã không còn là thứ lý tưởng để review, trọng tâm dịch sang những tài liệu mô tả hành vi, kiến trúc, đánh đổi và kết quả trong môi trường thật. Não người vốn không giỏi kiểm chứng, còn hệ thống phi tất định lại càng cần truy vết, kiểm thử ngay trên môi trường thật, ghi và phát lại, cùng vòng phản hồi ngắn. AI vì thế không làm phần mềm bớt tính kỹ thuật mà đòi hỏi nhiều kỷ luật hơn.&lt;/p&gt;
&lt;h2 id="nine-questions-i-now-ask-in-interviews-that-i-wish-i"&gt;&lt;a href="https://louisedeason.substack.com/p/nine-questions-i-now-ask-in-interviews" rel="noopener noreferrer"&gt;Nine Questions I Now Ask in Interviews That I Wish I&amp;rsquo;d Asked Five Years Ago&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Louise Deason cho rằng phỏng vấn là cuộc đánh giá hai chiều, dù hầu hết ứng viên vẫn coi nó như một bài thi, đúng với cách có lợi cho công ty. Phần &amp;ldquo;bạn có câu hỏi gì cho chúng tôi không?&amp;rdquo; là phần bị đánh giá thấp nhất, trong khi đó là lúc công ty bớt đề phòng và vô tình để lộ nhiều điều. Bà đưa ra chín câu hỏi buộc phía tuyển dụng phải trả lời cụ thể: lần gần nhất ai đó trong nhóm được thăng chức diễn ra thế nào, người gắn bó lâu nhất với vị trí này hiện làm gì, nhóm thật sự kém ở điểm nào, nhóm xử lý bất đồng với quyết định của lãnh đạo ra sao, kế hoạch nhân sự năm tới, có được gặp người sẽ làm việc cùng không, thành công và thất bại sau sáu tháng trông như thế nào, lần tái cơ cấu gần nhất, và vì sao vị trí này đang mở.&lt;/p&gt;
&lt;p&gt;Giá trị nằm ở tín hiệu đi kèm câu trả lời: có cụ thể không, có né tránh không, có ngập ngừng không. Một câu trả lời rõ ràng về lộ trình thăng tiến hay điểm yếu thật của nhóm cho thấy sự thẳng thắn; ngược lại, những câu như &amp;ldquo;nhóm luôn đồng thuận&amp;rdquo;, sự chậm trễ khi sắp xếp cho gặp đồng nghiệp tương lai hay khoảng lặng trước câu hỏi về lý do mở vị trí đều đáng lưu tâm. Thông điệp cuối: bạn đang chọn giữa những lựa chọn không hoàn hảo theo những cách khác nhau, và công ty khó chịu khi bị hỏi kỹ là công ty chưa xứng đáng với thời gian của bạn.&lt;/p&gt;
&lt;h2 id="agentic-testing-where-agents-fit-in-the-e2e-testing-stack"&gt;&lt;a href="https://slack.engineering/agentic-testing-where-agents-fit-in-the-e2e-testing-stack/" rel="noopener noreferrer"&gt;Agentic Testing: Where Agents Fit in the E2E Testing Stack&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Sergii Gorbachov từ Slack chia sẻ kết quả hơn 200 lần chạy kiểm thử đầu cuối bằng agent, nhằm xác định agent nên nằm ở đâu trong hệ thống kiểm thử. Kiểm thử đầu cuối truyền thống ép một hành trình cố định qua giao diện, còn kiểm thử bằng agent bắt đầu từ mục tiêu: agent quan sát trạng thái, tự chọn bước tiếp theo rồi xác minh kết quả. Nhóm so sánh ba cách: agent dùng Playwright MCP, agent dùng Playwright CLI, và bài kiểm thử Playwright do AI sinh ra, trên không gian làm việc thử nghiệm với dữ liệu không phải thật, qua hai luồng: trả lời thread và khám phá tìm kiếm.&lt;/p&gt;
&lt;p&gt;Bài kiểm thử được sinh ra chạy nhanh nhất, khoảng 3 phút, nhưng tỷ lệ lỗi tăng vọt lên khoảng 48% ở luồng phức tạp. Agent qua MCP đáng tin nhất, tỷ lệ lỗi từ 0 đến 12%, trong khi CLI ở mức 12 đến 20%, chủ yếu do lỗi đăng nhập và điều hướng ở tầng thực thi. MCP cũng tốn ít lượt tương tác hơn vì gộp thao tác và trả trạng thái trong một vòng. Tuy vậy, mỗi lần chạy bằng agent tốn 15 đến 30 đô la và hơn 5 phút, phần lớn chi phí đến từ việc gửi lại ngữ cảnh cũ. Kết luận: kiểm thử tất định vẫn là nền tảng hồi quy nhanh trong CI, còn kiểm thử bằng agent là lớp bổ sung trên đỉnh kim tự tháp kiểm thử, hợp với việc khám phá hành vi phức tạp, gỡ lỗi luồng chập chờn và tái hiện lỗi từ môi trường thật.&lt;/p&gt;
&lt;h2 id="making-agents-easy-13-lessons-from-forter"&gt;&lt;a href="https://blog.forter.dev/making-agents-easy-13-lessons-from-forters-agentic-ai-sprint/" rel="noopener noreferrer"&gt;Making Agents Easy: 13 Lessons from Forter&amp;rsquo;s Agentic AI Sprint&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Ben Maraney kể lại cách Forter tổ chức một đợt chạy nước rút hai tuần để toàn bộ khối R&amp;amp;D tự xây agent. Ý chính không phải agent tự nhiên dễ, mà tổ chức có thể chủ động giảm ma sát ở ba lớp: công cụ, nền tảng chạy agent và các rào cản tổ chức. Với công cụ, Forter dựng một MCP server nội bộ tên Toolchain: người dùng vào giao diện, chọn và cấu hình công cụ rồi nhận ngay API key; muốn thêm công cụ mới chỉ cần một file YAML mô tả và một lớp bọc mỏng. Nhờ vậy, khi đợt chạy kết thúc, Toolchain đã có hơn 90 công cụ nội bộ. Thay vì dựng RAG phức tạp, họ đưa công cụ tìm kiếm doanh nghiệp sẵn có thành công cụ MCP.&lt;/p&gt;
&lt;p&gt;Bài viết còn nhiều bài học vận hành. Người xây agent cần thấy rõ từng yêu cầu và phản hồi của công cụ; một agent xử lý sự cố của Forter từng &amp;ldquo;giải quyết&amp;rdquo; lỗi chỉ bằng cách đọc bản phân tích sau sự cố có sẵn. Với hệ thống nội bộ có người duyệt kết quả, có thể chưa cần evals ngay, hãy ưu tiên truy vết và theo dõi trước. Bộ phận pháp lý và bảo mật nên được kéo vào sớm, quyền truy cập đặt trong mã tất định, chi phí token cần bảng theo dõi. Khi đào tạo, hãy hình dung LLM như một thực tập sinh thông minh mới ra trường: kiến thức rộng, ít kinh nghiệm thực tế, nên trách nhiệm đưa hướng dẫn rõ ràng thuộc về người xây agent.&lt;/p&gt;
&lt;h2 id="agentic-code-review"&gt;&lt;a href="https://addyosmani.com/blog/agentic-code-review/" rel="noopener noreferrer"&gt;Agentic Code Review&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Addy Osmani lập luận rằng khi agent viết mã ngày càng nhanh, nút thắt của kỹ thuật phần mềm chuyển từ viết mã sang xác minh và tin tưởng mã đó. AI có thể tạo ra rất nhiều mã đúng cú pháp, có kiểm thử và trông hợp lý, nhưng tốc độ đọc hiểu của con người không tăng theo. Vì vậy review trở thành kỹ năng có đòn bẩy lớn nhất: không chỉ để bắt lỗi, mà còn để hiểu lại ý định, đánh giá phạm vi ảnh hưởng, lan tỏa hiểu biết trong nhóm và quyết định thay đổi có đáng làm hay không. Dữ liệu năm 2026 mà bài viết tổng hợp cho thấy sản lượng tăng mạnh, nhưng mã phải sửa lại, lỗi, sự cố và thời gian review cũng tăng theo.&lt;/p&gt;
&lt;p&gt;Không có một chính sách review đúng cho mọi nơi. Dự án cá nhân chưa có người dùng có thể dựa nhiều hơn vào kiểm thử, AI review và tự động hóa; hệ thống lâu năm có người dùng, tiền hoặc dữ liệu nhạy cảm thì cần review phân tầng theo rủi ro. Addy khuyên giữ pull request nhỏ, nêu rõ ý định, yêu cầu bằng chứng như kết quả kiểm thử thật trước khi review, đọc phần thay đổi kiểm thử kỹ hơn phần mã, coi CI tất định là rào chắn không thương lượng và coi AI review là cảm biến chứ không phải phán quyết. Con người có thể không đọc từng dòng nữa, nhưng vẫn phải quyết định việc merge, chịu trách nhiệm với môi trường thật và dồn sức vào những thay đổi đắt giá nếu sai như xác thực, thanh toán, bảo mật hay dữ liệu cá nhân.&lt;/p&gt;
&lt;h2 id="i-thought-redis-was-just-a-hashmap"&gt;&lt;a href="https://mukul0x9.pages.dev/blog/memdb/" rel="noopener noreferrer"&gt;I Thought Redis Was Just a HashMap&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Mukul Makwana bắt đầu từ một câu hỏi tưởng đơn giản: Redis hay Memcached có khác gì một bảng băm toàn cục với ba thao tác &lt;code&gt;set&lt;/code&gt;, &lt;code&gt;get&lt;/code&gt;, &lt;code&gt;del&lt;/code&gt;? Để tìm câu trả lời, tác giả tự xây một cơ sở dữ liệu trong bộ nhớ tối giản bằng Go. Bản đầu tiên dùng &lt;code&gt;map&lt;/code&gt; có sẵn, nhận yêu cầu qua TCP và tạo một goroutine cho mỗi kết nối. Vấn đề lộ ra ngay khi nhiều goroutine cùng đọc ghi một map: phải thêm khóa, và một mutex toàn cục khiến mọi thao tác đều phải chờ nhau.&lt;/p&gt;
&lt;p&gt;Tác giả chuyển sang cấu trúc hai mảng: &lt;code&gt;bucket_array&lt;/code&gt; lưu vị trí dữ liệu, còn &lt;code&gt;data_array&lt;/code&gt; chứa metadata, key, value và vị trí phần tử kế tiếp khi xảy ra va chạm. Để giảm tranh chấp khóa, bảng được chia thành nhiều shard, mỗi shard có mảng riêng và &lt;code&gt;RWMutex&lt;/code&gt; riêng; khi shard đầy, một goroutine nền lo việc mở rộng. Vì dữ liệu chỉ ghi nối thêm, thao tác xóa chỉ đánh dấu bia mộ, rồi một bước nén dựng lại shard khi bộ nhớ vượt ngưỡng, đổi lại shard bị khóa trong lúc đó. Đo hiệu năng chỉ với thao tác SET cho thấy map có sẵn vẫn thắng về thông lượng và thời gian dừng toàn cục, nhưng bảng băm tự viết giảm thời gian đánh dấu đồng thời của GC từ khoảng 113 ms xuống 7,8 ms nhờ ít con trỏ hơn. Bản này vẫn thiếu lưu trữ bền vững, chính sách loại bỏ, khôi phục sau sự cố và sao chép, những thứ khiến Redis thật phức tạp hơn nhiều.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Bài viết đã được viết lại bởi Claude Code với Opus 5.5 vào ngày 27/09/2026.&lt;/em&gt;&lt;/p&gt;</description></item><item><title>Newsletter #118</title><link>https://miti99.com/post/2026/07/06/</link><pubDate>Mon, 06 Jul 2026 00:00:00 +0700</pubDate><guid>https://miti99.com/post/2026/07/06/</guid><description>&lt;p&gt;&lt;em&gt;Mời bạn thưởng thức Newsletter #118.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="telling-the-story-right-efficient-logging-in-go"&gt;&lt;a href="https://robinsiep.com/blog/posts/go-logs/" rel="noopener noreferrer"&gt;Telling the story right: efficient logging in Go&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Tiếp nối bài viết về lỗi trong Go, Robin Siep bàn về logging: log tốt phải đủ ngữ cảnh để gỡ lỗi production nhưng vẫn dễ đọc khi phát triển cục bộ. Có ba nhóm cần ghi log: lỗi, gần như luôn ở mức &lt;code&gt;ERROR&lt;/code&gt; kèm stack trace và điều kiện xảy ra; sự kiện nghiệp vụ và chuyển trạng thái quan trọng, thường ở mức &lt;code&gt;INFO&lt;/code&gt;, ghi rõ ai thực hiện; và giao tiếp với cơ sở dữ liệu, dịch vụ bên thứ ba hay ứng dụng khách, phần quá nhiễu có thể hạ xuống &lt;code&gt;DEBUG&lt;/code&gt;. Ba sai lầm hay gặp là ghi secret, thông tin đăng nhập hay dữ liệu cá nhân vào log, chạy production sai log level, và cắt giảm log quá sớm, vì ghi quá ít thường gây hại hơn.&lt;/p&gt;
&lt;p&gt;Structured log là bắt buộc trên production vì dễ lọc và truy vấn, còn log văn bản có màu dễ theo dõi khi làm cục bộ. Tác giả khuyên giữ cả log văn bản trên production: khi đường ống thu thập log hỏng, đó là thứ còn lại để xoay xở và sửa chính đường ống ấy. Phần triển khai dùng &lt;code&gt;slog&lt;/code&gt; với hai handler tự viết: một handler chuyển bản ghi sang định dạng CLEF để gửi tới Seq và tách stack trace vào trường riêng; một handler văn bản tô màu, hiển thị thuộc tính dễ đọc. Nếu gửi sang Seq thất bại, lỗi của đường ống và bản ghi gốc vẫn được ghi qua handler văn bản. Cách làm này dùng được cho cả zap, và cho thấy log chỉ có giá trị khi giữ được ngữ cảnh ngay cả lúc hệ thống quan sát gặp sự cố.&lt;/p&gt;
&lt;h2 id="on-cpu-physics-and-cpu-cycles"&gt;&lt;a href="https://6it.dev/blog/on-cpu-physics-and-cpu-cycles-80730" rel="noopener noreferrer"&gt;On CPU Physics and CPU Cycles&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Bài viết là bản nháp một phần chương sách về lập trình C++ hiệu quả trên CPU 64-bit hiện đại, với luận điểm chính: hiệu năng không chỉ phụ thuộc thuật toán mà còn bị chi phối bởi vật lý phần cứng. Tín hiệu điện phải đi càng xa thì truy cập càng chậm, chủ yếu do điện dung ký sinh tăng theo chiều dài đường dẫn. Đó là lý do phép tính trên thanh ghi và ALU chỉ tốn vài chu kỳ, L1 cache cũng chỉ vài chu kỳ, L2 và L3 đắt dần, còn một lần truy cập RAM chính có thể mất tới hàng trăm chu kỳ CPU. Đi xa hơn nữa, từ bo mạch chủ tới SSD, HDD và mạng, độ trễ tăng thêm nhiều bậc độ lớn, nên tính cục bộ của dữ liệu trong cache có ảnh hưởng thực tế rất lớn.&lt;/p&gt;
&lt;p&gt;Để che bớt độ trễ, CPU hiện đại dùng pipeline, thực thi superscalar và dự đoán nhánh, cho phép chạy tiếp trước khi biết kết quả của một phép rẽ nhánh. Tuy vậy, mỗi lần đoán sai vẫn tốn khoảng vài chục chu kỳ; các gợi ý &lt;code&gt;[[likely]]&lt;/code&gt;/&lt;code&gt;[[unlikely]]&lt;/code&gt; chỉ nên dùng khi nhánh thật sự rõ ràng, chẳng hạn đường xử lý lỗi. Với bộ nhớ trong C/C++, stack thường nằm sẵn trong cache, vùng static/global ở mức trung bình, còn heap nên được coi là &amp;ldquo;lạnh&amp;rdquo;, trừ khi dữ liệu nằm gần nhau hoặc được duyệt tuần tự qua cấu trúc liên tục như &lt;code&gt;vector&lt;/code&gt;. Vì vậy &lt;code&gt;vector&lt;/code&gt; thường thân thiện với cache và TLB hơn hẳn các cấu trúc dựa trên node như danh sách liên kết hay cây. Hãy nhìn hiệu năng như một chuỗi chi phí vật lý.&lt;/p&gt;
&lt;h2 id="the-log-is-the-agent"&gt;&lt;a href="https://x.com/ishaansehgal/status/2065129901427130678" rel="noopener noreferrer"&gt;The Log Is the Agent&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Ishaan Sehgal đưa ra một định nghĩa gọn cho AI agent: agent không phải model, runtime, vòng lặp đang chạy hay bộ công cụ, mà là log bền vững ghi lại mọi thứ nó đã thấy và đã làm. Tác giả mượn hình ảnh nhân vật chơi hàng trăm giờ trong Skyrim hay Elden Ring: thứ làm nên nhân vật không phải game engine, máy chơi game hay tay cầm, mà là file lưu game. Với agent, log là chuỗi sự kiện chỉ ghi thêm gồm đầu vào của người dùng, đầu ra của model, lời gọi công cụ và kết quả, kèm tham chiếu tới system prompt và skill. Chỉ riêng log đã đủ để một tiến trình mới dựng lại trạng thái và chạy tiếp.&lt;/p&gt;
&lt;p&gt;Hệ quả kiến trúc là log phải là thành phần trung tâm chứ không phải sản phẩm phụ để gỡ lỗi. Mọi thao tác đều đọc log, ghi thêm vào log hoặc hiển thị một góc nhìn của log, giống cách cơ sở dữ liệu coi bảng, chỉ mục và cache là các phép chiếu trên log thay đổi. Vòng lặp chạy trên log vì thế có tính idempotent và chịu lỗi tốt: worker chết thì worker khác nhận phiên và làm tiếp; muốn thử model hay chiến lược khác thì rẽ nhánh từ cùng một lịch sử. Compaction hay bản tóm tắt giúp vừa cửa sổ ngữ cảnh nhưng bản chất là mất thông tin, nên không được thay thế log gốc làm nguồn sự thật. Nó cũng lộ ra một kiểu phụ thuộc nhà cung cấp mới: phụ thuộc vào lịch sử vận hành của agent. Khả năng xem, xuất và truy vấn log quyết định ai thật sự sở hữu agent.&lt;/p&gt;
&lt;h2 id="software-is-not-a-single-player-game"&gt;&lt;a href="https://www.davidpoll.com/2026/06/software-is-not-a-single-player-game/" rel="noopener noreferrer"&gt;Software Is Not A Single-Player Game&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;David Poll phản hồi quan điểm cho rằng mọi phán đoán quan trọng nên diễn ra trước code review, ở giai đoạn PRD hay design doc. Quan điểm đó có gốc rễ hợp lý: viết mã từng là bước đắt nhất, nên ngành dựng cả hạ tầng như PRD, design doc và RFC để giảm cái giá của việc làm sai. Nhưng cách nhìn tuyến tính ấy bỏ lỡ bản chất của phát triển phần mềm: đây không phải trò chơi một người, mà là nơi nhiều người cùng rèn gu kỹ thuật, mô hình tư duy chung và phán đoán sản phẩm theo thời gian. Code review không chỉ để bắt lỗi, mà là một trong những nơi chính quá trình đó diễn ra.&lt;/p&gt;
&lt;p&gt;Khi AI làm chi phí viết mã giảm mạnh, những tài liệu vốn chỉ là vật thay thế cho mã đang nhường chỗ cho thay đổi chạy được. Prototype hay pull request giờ đủ rẻ để trở thành nơi tranh luận tốt hơn, và điều đó khiến review càng trở nên trung tâm: đội ngũ nhìn vào thay đổi thật, hỏi nó tác động thế nào tới hệ thống và người dùng, rồi quyết định có đưa vào sản phẩm hay không. Một pull request bị đóng không còn là thất bại mà có thể là cách rẻ nhất để biết một hướng đi không phù hợp. Tuy vậy, tác giả cảnh báo xu hướng &amp;ldquo;cứ phát hành rồi rollback&amp;rdquo;: mã có thể hoàn tác, còn niềm tin và ấn tượng của người dùng về sản phẩm thì không. Một số quyết định vẫn phải chốt trước khi viết mã, như kiến trúc cấp tổ chức, bảo mật, tuân thủ hay cam kết với khách hàng, nhưng phạm vi đó đang thu hẹp dần.&lt;/p&gt;
&lt;h2 id="cleaning-up-after-ai-rockstar-developers"&gt;&lt;a href="https://www.codingwithjesse.com/blog/rockstar-developers/" rel="noopener noreferrer"&gt;Cleaning up after AI rockstar developers&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Jesse Skinner dùng hình ảnh &amp;ldquo;rockstar developer&amp;rdquo; để nói về một rủi ro quen thuộc: một người rất giỏi, rất nhanh, mê công nghệ mới, có thể một mình viết lại phần lõi hệ thống, nhưng để lại mã nguồn mà cả đội khó hiểu và khó bảo trì. Khi người đó rời đi, những người ở lại phải trả giá thật: mất nhiều thời gian chỉ để chạy được dự án, lần theo luồng dữ liệu rối rắm, học thêm ngôn ngữ hay thư viện lạ, và thuyết phục tổ chức rằng hệ thống cần được cứu thay vì tiếp tục tin vào hào quang cũ.&lt;/p&gt;
&lt;p&gt;Điểm chính của bài viết là AI có thể khuếch đại đúng vấn đề này. Mỗi phiên trò chuyện với LLM giống như thêm một &amp;ldquo;rockstar&amp;rdquo; mới vào codebase: sinh mã cực nhanh, không nhớ ngữ cảnh dài hạn, thích áp dụng các best practice chung chung dù bài toán không cần, và không chịu trách nhiệm về việc hệ thống có còn dễ hiểu với cả đội hay không. Khi hàng loạt tính năng và bản sửa lỗi được tạo ra qua nhiều phiên khác nhau, codebase dễ biến thành tập hợp của hàng trăm phong cách thiết kế rời rạc, còn kiến trúc thì phình ra vượt xa độ phức tạp thật của bài toán. Tác giả khuyên dùng LLM như công cụ hỗ trợ dưới sự dẫn dắt kỹ thuật của con người: làm từng thay đổi nhỏ, xem xét kỹ từng thay đổi, giữ quyền kiểm soát kiến trúc, liên tục đơn giản hóa, và sẵn sàng tự viết mã khi đó là cách tốt hơn để giữ chất lượng.&lt;/p&gt;
&lt;h2 id="a-new-era-for-software-testing"&gt;&lt;a href="https://antirez.com/news/168" rel="noopener noreferrer"&gt;A new era for software testing&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Antirez nhận định lập trình tự động bằng AI giúp tăng tốc nhưng phải đánh đổi chất lượng cấu trúc của mã, còn với QA, LLM mở ra cách tự động hóa mạnh hơn mà không hy sinh chất lượng. Bộ kiểm thử truyền thống gồm unit test và integration test vẫn cần thiết, nhưng phủ hết mọi dòng mã không có nghĩa là phủ hết mọi trạng thái. Kiểm thử tích hợp vốn khó vì vấn đề thời gian, thiết lập phức tạp và đầu ra chỉ đánh giá được bằng mắt, nên nhiều bài kiểm thử thủ công bị bỏ qua.&lt;/p&gt;
&lt;p&gt;Đề xuất của tác giả là viết một file Markdown yêu cầu AI agent đóng vai kỹ sư QA, thực hiện loạt kiểm thử thủ công trên bản phát hành mới. Agent bắt đầu bằng việc đọc các commit mới so với bản đã phát hành, xác định vùng có thể bị ảnh hưởng, rồi tập trung săn các lỗi hồi quy cụ thể. Với DwarfStar, một inference engine cho LLM mã nguồn mở, agent kiểm tra suy luận phân tán giữa hai máy MacBook với mọi file GGUF có sẵn, đồng thời phát hiện suy giảm tốc độ mà không cần biết trước tốc độ cũ, vì đó là mục tiêu thay đổi theo từng bản. Với Redis Arrays, agent tự xây một ứng dụng lớn, dựng môi trường có replication và persistence, mô phỏng nhiều ngày sử dụng với nhiều người dùng để tìm hành vi bất thường. Agent còn có thể chỉ ra tính năng gây bất ngờ, thiếu tài liệu hay cẩu thả. Antirez tin rằng QA tự động có thể nâng chuẩn chất lượng mỗi bản phát hành, phần nào bù lại chất lượng mã thấp hơn khi viết nhanh bằng AI.&lt;/p&gt;
&lt;h2 id="nobody-pushed-back-why-engineers-stay-silent-until-it"&gt;&lt;a href="https://howtocenterdiv.com/beyond-the-div/nobody-pushed-back" rel="noopener noreferrer"&gt;Nobody Pushed Back: Why Engineers Stay Silent Until It&amp;rsquo;s Too Late&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Bài viết lập luận rằng nhiều thảm họa kiến trúc xảy ra không phải vì kỹ sư không biết vấn đề, mà vì họ biết nhưng không thấy an toàn để lên tiếng. Trong phòng họp, một quyết định kỹ thuật có thể được thông qua dưới nhãn &amp;ldquo;alignment&amp;rdquo; dù nhiều người không thật sự đồng tình; vài tháng sau hệ thống sụp đổ, ai cũng nói mình đã thấy trước. Đây là một mẫu lặp lại: rủi ro kỹ thuật đã rõ, những người gần hệ thống nhất nhìn thấy nó, nhưng cái giá xã hội và nghề nghiệp của việc phản biện cao hơn cái giá của im lặng.
Các ví dụ Nokia, TSB, Boeing và Microsoft cho thấy thông tin xấu hiếm khi thiếu, chỉ là không đến được nơi ra quyết định. Nokia có người hiểu Symbian không hợp với kỷ nguyên màn hình cảm ứng; TSB có những phản đối kỹ thuật trước đợt chuyển đổi hệ thống lớn; Boeing có kỹ sư biết rủi ro của MCAS; Microsoft từng có tín hiệu rằng hướng đi của Windows Phone đang tự dồn vào ngõ cụt. Vấn đề không nằm ở sự thiếu can đảm, mà ở hệ thống: phản biện bị gắn nhãn tiêu cực, ý kiến của người có chức vụ cao nhất (HiPPO) luôn thắng, còn các chỉ số &amp;ldquo;xanh&amp;rdquo; được dùng để khép lại tranh luận, biến quyết định kỹ thuật thành quyết định xã hội. Phản biện hiệu quả không phải là nói &amp;ldquo;sai rồi&amp;rdquo;, mà là làm cho chi phí và rủi ro trở nên cụ thể bằng những câu hỏi như quyết định này sẽ tốn gì sau 18 tháng, rủi ro được kiểm thử ra sao, và kế hoạch rollback là gì nếu mọi thứ đổ vỡ.&lt;/p&gt;
&lt;h2 id="loop-engineering"&gt;&lt;a href="https://addyo.substack.com/p/loop-engineering" rel="noopener noreferrer"&gt;Loop Engineering&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Addy Osmani mô tả &amp;ldquo;loop engineering&amp;rdquo; là bước tiếp theo của prompt engineering: thay vì tự tay nhắc agent từng lượt, kỹ sư thiết kế một vòng lặp tự tìm việc, giao việc, kiểm tra kết quả và quyết định bước tiếp theo. Vòng lặp giống một mục tiêu đệ quy: đặt ra mục đích, rồi AI lặp lại cho tới khi hoàn thành. Theo tác giả, hình mẫu này đã thành hiện thực vì cả Codex lẫn Claude Code đều có đủ các mảnh ghép cần thiết, nên thay vì tranh cãi chọn công cụ nào, hãy thiết kế một vòng lặp chạy được trên cả hai.&lt;/p&gt;
&lt;p&gt;Một vòng lặp gồm năm khối. Automation chạy theo lịch để phân loại issue, tóm tắt lỗi CI hay săn lỗi mới. Worktree cho phép nhiều agent làm song song mà không ghi đè file của nhau. Skill đóng gói kiến thức dự án để không phải giải thích lại mỗi phiên. Connector, thường xây trên MCP, cho agent chạm tới issue tracker, cơ sở dữ liệu, Slack hay API staging. Sub-agent tách người làm khỏi người kiểm tra, vì agent viết mã thường quá dễ dãi khi tự chấm bài. Ngoài ra cần một vùng nhớ bền vững nằm ngoài ngữ cảnh như file Markdown hay bảng Linear, bởi model có thể quên giữa các lần chạy còn repo thì không. Dù vậy, Addy nhấn mạnh vòng lặp không xóa bỏ vai trò kỹ sư: một vòng lặp chạy không giám sát cũng là vòng lặp mắc lỗi không giám sát. Nếu không xem xét và hiểu mã được tạo ra, vòng lặp chỉ khiến &amp;ldquo;nợ hiểu biết&amp;rdquo; tích tụ nhanh hơn; vòng lặp tốt giúp tăng tốc trong khi kỹ sư vẫn nắm sâu hệ thống.&lt;/p&gt;
&lt;h3 id="bonus"&gt;Bonus&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;Images:&lt;/strong&gt;
&lt;img src="https://substackcdn.com/image/fetch/$s_!Y582!,w_1100,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1b5057ba-3667-446b-9760-b726da1431f4_2484x3002.png" alt="Latency vs Throughput vs Bandwidth"&gt;
&lt;img src="https://substackcdn.com/image/fetch/$s_!bdjh!,w_1100,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9c8ec17-676c-441a-9c97-d08f8eec804f_2484x3002.png" alt="7 Permission Modes Every Claude Code User Should Know"&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Videos:&lt;/strong&gt;
&lt;a href="https://www.youtube.com/watch?v=MUWAbpg1xLo" rel="noopener noreferrer"&gt;CPU, GPU và TPU khác nhau thế nào?&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
 &lt;p&gt;Video giải thích sự khác nhau giữa CPU, GPU và TPU: CPU linh hoạt cho các tác vụ tổng quát, GPU mạnh ở xử lý song song, còn TPU là phần cứng chuyên dụng cho khối lượng công việc học máy. Nội dung ngắn gọn, giúp chọn đúng loại phần cứng theo độ trễ, thông lượng và chi phí.&lt;/p&gt;

&lt;/blockquote&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Bài viết đã được viết lại bởi Claude Code với Opus 5.5 vào ngày 27/09/2026.&lt;/em&gt;&lt;/p&gt;</description></item><item><title>Newsletter #117</title><link>https://miti99.com/post/2026/07/05/</link><pubDate>Sun, 05 Jul 2026 00:00:00 +0700</pubDate><guid>https://miti99.com/post/2026/07/05/</guid><description>&lt;p&gt;&lt;em&gt;Mời bạn thưởng thức Newsletter #117.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="should-you-normalize-rgb-values-by-255-or-256"&gt;&lt;a href="https://30fps.net/pages/255-vs-256-division/" rel="noopener noreferrer"&gt;Should you normalize RGB values by 255 or 256?&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Pekka Väänänen mổ xẻ một câu hỏi tưởng nhỏ nhưng rất hay gây tranh cãi trong xử lý ảnh: khi chuyển giá trị màu RGB 8-bit sang số thực, nên chia cho 255 hay cộng thêm nửa bước rồi chia cho 256. Cách quen thuộc &lt;code&gt;img / 255.0&lt;/code&gt; ánh xạ 0 thành 0.0 và 255 thành 1.0, nên đen tuyệt đối và trắng tuyệt đối vẫn giữ đúng ý nghĩa, rất tiện cho mọi đoạn mã xử lý phía sau. Cách thay thế &lt;code&gt;(img + 0.5) / 256.0&lt;/code&gt; đặt mỗi giá trị vào giữa một khoảng lượng tử, đẹp hơn về mặt mô hình lượng tử hóa và cho sai số tái dựng lý thuyết thấp hơn một chút, nhưng chỉ khi ta kiểm soát cả bước ghi lẫn bước đọc dữ liệu.&lt;/p&gt;
&lt;p&gt;Kết luận thực dụng của tác giả là với ảnh đến từ bên ngoài, cứ chia cho 255. Những lập luận thường được đưa ra để chống lại 255, như khoảng lượng tử ở hai đầu hẹp hơn, số thực không biểu diễn chính xác tuyệt đối hay cảm giác lãng phí một chút độ chính xác, đều không đáng kể trong thực tế. Ngược lại, chia cho 256 với ảnh vốn được lượng tử hóa theo quy ước 255 có thể đưa thêm sai lệch và khiến mã nguồn phụ thuộc vào dải 8-bit. Lỗi thực sự đáng sợ là trộn bước mã hóa của quy ước này với bước giải mã của quy ước kia. Vì vậy chỉ nên cân nhắc 256 trong một hệ thống khép kín, nơi bạn kiểm soát toàn bộ quy trình lưu và đọc ảnh, không cần 0 ánh xạ đúng về 0.0 và mọi thành phần đều thống nhất một quy ước.&lt;/p&gt;
&lt;h2 id="jira-is-turing-complete"&gt;&lt;a href="https://seriot.ch/computation/jira.html" rel="noopener noreferrer"&gt;Jira is Turing-Complete&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Nicolas Seriot biến một câu đùa quen thuộc về Jira thành một chứng minh nghiêm túc: Jira Automation có thể mô phỏng máy thanh ghi Minsky, một mô hình tính toán đã được chứng minh là Turing-complete. Trong cách dựng của tác giả, mỗi thanh ghi là số lượng issue liên kết thuộc một loại nhất định, chẳng hạn Bug và Task; con trỏ lệnh là trạng thái hiện tại của một Epic; bảng lệnh là tập automation rule; còn mỗi nhịp chạy là một lần chuyển trạng thái do automation kích hoạt. Chỉ với các thao tác tạo, xóa và kiểm tra issue liên kết, Jira đã biểu diễn được &lt;code&gt;INC&lt;/code&gt;, &lt;code&gt;DEC&lt;/code&gt; và rẽ nhánh có điều kiện.&lt;/p&gt;
&lt;p&gt;Ví dụ tối giản là phép cộng: chuyển toàn bộ số Bug ở thanh ghi A sang số Task ở thanh ghi B, dùng các trạng thái như &lt;code&gt;TODO&lt;/code&gt;, &lt;code&gt;DEV&lt;/code&gt; và &lt;code&gt;PROD&lt;/code&gt;. Khi A còn Bug, rule xóa một Bug rồi chuyển sang trạng thái tăng B; khi A bằng 0 thì chuyển sang trạng thái dừng. Tác giả còn dùng thao tác đổi loại issue, thực chất là gộp một lần giảm và một lần tăng, để rút gọn chương trình Fibonacci xuống còn ba trạng thái với ba thanh ghi. Chương trình này chạy cho đến khi chạm giới hạn độ sâu chuỗi automation của Jira Cloud và cần người vận hành kích hoạt lại. Các giới hạn quota như vậy không phủ nhận lập luận, vì mọi máy tính vật lý cũng hữu hạn. Thông điệp không phải là nên lập trình bằng Jira, mà là các automation phức tạp thật sự mang hình dạng của một chương trình, với trạng thái, bộ nhớ, rẽ nhánh và vòng lặp.&lt;/p&gt;
&lt;h2 id="usb-for-software-developers-an-introduction-to-writing-userspace-usb-drivers"&gt;&lt;a href="https://werwolv.net/posts/usb_for_sw_devs/" rel="noopener noreferrer"&gt;USB for Software Developers: An introduction to writing userspace USB drivers&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;WerWolv viết một bài nhập môn USB cho lập trình viên phần mềm muốn giao tiếp với thiết bị mà không phải đào sâu vào tín hiệu điện hay viết mã trong nhân hệ điều hành. Ví dụ xuyên suốt là một điện thoại Android ở chế độ bootloader, vì giao thức Fastboot đơn giản, có tài liệu rõ ràng và thường không bị hệ điều hành gắn sẵn driver. Bài viết bắt đầu bằng &lt;code&gt;lsusb&lt;/code&gt; để đọc &lt;code&gt;VID&lt;/code&gt;, &lt;code&gt;PID&lt;/code&gt;, lớp thiết bị và driver đang gắn, sau đó chuyển sang thư viện &lt;code&gt;libusb&lt;/code&gt; để tìm và mở thiết bị ngay trong không gian người dùng.&lt;/p&gt;
&lt;p&gt;Phần giá trị nhất là cách tác giả nối từng khái niệm USB với mã nguồn thật. Trong quá trình liệt kê thiết bị, host dùng control endpoint &lt;code&gt;0x00&lt;/code&gt; để gửi các request chuẩn như &lt;code&gt;GET_STATUS&lt;/code&gt; và &lt;code&gt;GET_DESCRIPTOR&lt;/code&gt;, qua đó đọc descriptor và biết thiết bị có những cấu hình, interface và endpoint nào. Endpoint có thể hình dung như các cổng giao tiếp, nhưng USB luôn do host chủ động hỏi, thiết bị không tự gửi dữ liệu. Tác giả giải thích bốn kiểu truyền Control, Bulk, Interrupt, Isochronous, cùng hướng &lt;code&gt;IN&lt;/code&gt; và &lt;code&gt;OUT&lt;/code&gt; luôn được hiểu từ góc nhìn của host. Cuối cùng, chương trình gửi lệnh Fastboot &lt;code&gt;getvar:version&lt;/code&gt; qua Bulk OUT endpoint và nhận phản hồi &lt;code&gt;OKAY0.4&lt;/code&gt; qua Bulk IN endpoint. Bài học rút ra: với thiết bị có giao thức rõ ràng, một driver chạy trong không gian người dùng bằng &lt;code&gt;libusb&lt;/code&gt; gần với lập trình socket hơn là một dự án kernel đáng sợ.&lt;/p&gt;
&lt;h2 id="everything-you-need-to-know-about-sourcemaps"&gt;&lt;a href="https://neciudan.dev/everything-you-need-to-know-about-sourcemaps" rel="noopener noreferrer"&gt;Everything you need to know about Sourcemaps&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Neciu Dan giải thích sourcemap ở cả hai mặt: công cụ gỡ lỗi không thể thiếu và một rủi ro bảo mật hay bị xem nhẹ. Sau khi đóng gói, trình duyệt chạy mã đã được gộp, nén và đổi tên biến, nên một lỗi kiểu &lt;code&gt;bundle.min.js:1:48211&lt;/code&gt; gần như vô nghĩa. Sourcemap là file JSON ánh xạ từng vị trí trong mã đã nén về file, dòng, cột và tên gốc, giúp stack trace dễ đọc trở lại. Nhưng chính các trường như &lt;code&gt;sources&lt;/code&gt;, &lt;code&gt;names&lt;/code&gt;, &lt;code&gt;mappings&lt;/code&gt; và đặc biệt &lt;code&gt;sourcesContent&lt;/code&gt; có thể để lộ cấu trúc thư mục, tên module, comment, endpoint API, thậm chí toàn bộ mã nguồn gốc nếu file &lt;code&gt;.map&lt;/code&gt; bị công khai.&lt;/p&gt;
&lt;p&gt;Thông điệp cốt lõi là minify không phải cơ chế bảo vệ mã nguồn. Khi bundle production trỏ tới sourcemap qua &lt;code&gt;sourceMappingURL&lt;/code&gt; hoặc HTTP header &lt;code&gt;SourceMap&lt;/code&gt;, bất kỳ ai có DevTools hay &lt;code&gt;curl&lt;/code&gt; đều tải được nếu máy chủ cho phép. Tác giả dẫn các sự cố thực tế, như trang App Store trên web của Apple năm 2025 và gói npm &lt;code&gt;@anthropic-ai/claude-code&lt;/code&gt; phiên bản 2.1.88 kèm file &lt;code&gt;cli.js.map&lt;/code&gt; gần 60 MB, để cho thấy đây thường chỉ là một lỗi cấu hình đóng gói chứ không phải kỹ thuật tấn công phức tạp. Cách phòng tránh gồm tắt sourcemap ở production khi không cần, dùng hidden sourcemap và tải riêng lên công cụ theo dõi lỗi như Sentry, chặn truy cập &lt;code&gt;.map&lt;/code&gt; ở máy chủ, loại file &lt;code&gt;.map&lt;/code&gt; khỏi artifact và gói npm, đồng thời thêm bước kiểm tra trong CI/CD hoặc sau &lt;code&gt;npm pack&lt;/code&gt; để chặn bản phát hành chứa sourcemap.&lt;/p&gt;
&lt;h2 id="extended-statistics-in-postgres"&gt;&lt;a href="https://www.valerieparhamthompson.com/posts/extended-statistics-postgres/" rel="noopener noreferrer"&gt;Extended Statistics in Postgres&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Valerie Parham-Thompson giải thích cách extended statistics giúp query planner của Postgres ước lượng chính xác hơn khi các cột có quan hệ với nhau mà thống kê mặc định không nhận ra. Planner dựa vào các bảng thống kê nội bộ như &lt;code&gt;pg_class&lt;/code&gt; và &lt;code&gt;pg_stats&lt;/code&gt; để ước lượng số dòng, số giá trị phân biệt, histogram và tần suất, nhưng các thống kê này chủ yếu nhìn từng cột riêng lẻ và ngầm giả định các điều kiện lọc độc lập với nhau. Ví dụ &lt;code&gt;zip='91605'&lt;/code&gt; gần như kéo theo &lt;code&gt;state='CA'&lt;/code&gt;, vậy mà planner vẫn nhân xác suất của hai điều kiện như hai biến độc lập, dẫn đến ước lượng số dòng thấp hơn thực tế rất nhiều và chọn một kế hoạch truy vấn kém.&lt;/p&gt;
&lt;p&gt;Bài viết đi qua ba loại extended statistics. Loại &lt;code&gt;dependencies&lt;/code&gt; mô tả quan hệ phụ thuộc giữa các cột, như mã ZIP gần như xác định bang; sau khi chạy &lt;code&gt;CREATE STATISTICS ... (dependencies)&lt;/code&gt; và &lt;code&gt;ANALYZE&lt;/code&gt;, ước lượng số dòng sát thực tế hơn hẳn. Loại &lt;code&gt;mcv&lt;/code&gt; lưu các tổ hợp giá trị phổ biến trên nhiều cột, hữu ích cho điều kiện &lt;code&gt;WHERE&lt;/code&gt; kết hợp như danh mục và danh mục con. Loại &lt;code&gt;ndistinct&lt;/code&gt; giúp planner ước lượng số nhóm khi &lt;code&gt;GROUP BY&lt;/code&gt; trên nhiều cột, thay vì nhân số giá trị phân biệt của từng cột. Bài học thực tế: khi biết dữ liệu của mình có các cột tương quan, hãy dạy cho planner bằng extended statistics thay vì chỉ nghĩ đến việc thêm index.&lt;/p&gt;
&lt;h2 id="finding-a-needle-in-a-4-gb-haystack-from-075-gbs-to-49-gbs-in-go"&gt;&lt;a href="https://segflow.github.io/post/fast-file-search-go/" rel="noopener noreferrer"&gt;Finding a needle in a 4 GB haystack: from 0.75 GB/s to 49 GB/s in Go&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Assel Meher dùng một bài toán cố ý thật đơn giản để mổ xẻ hiệu năng đọc và quét file trong Go: tìm một giá trị &lt;code&gt;int64&lt;/code&gt; khác 0 nằm ở cuối một file 4 GiB gần như toàn số 0. Không có phân tích cú pháp hay thuật toán phức tạp, nên phép đo phơi bày chi phí thật của từng lớp: Go runtime, thư viện chuẩn, syscall, page cache, bộ nhớ, &lt;code&gt;mmap&lt;/code&gt;, &lt;code&gt;pread&lt;/code&gt;, đa luồng và SIMD. Điểm xuất phát là &lt;code&gt;os.ReadFile&lt;/code&gt; rồi duyệt &lt;code&gt;range&lt;/code&gt; từng byte, chỉ đạt khoảng 0.75 GB/s vì vừa cấp phát và sao chép 4 GiB vừa xử lý từng byte; &lt;code&gt;bufio.ReadByte&lt;/code&gt; cũng không khá hơn vì tốn một lời gọi hàm cho mỗi byte.&lt;/p&gt;
&lt;p&gt;Bước nhảy lớn đầu tiên đến từ việc bỏ chi phí theo byte: đọc theo khối 1 MiB và quét theo &lt;code&gt;uint64&lt;/code&gt; đã vượt 13 GB/s. &lt;code&gt;mmap&lt;/code&gt; không tự động thắng, vì tuy tránh được sao chép nhưng lại phát sinh hơn một triệu minor page fault khi chạm vào từng trang. Song song hóa giúp cải thiện, và &lt;code&gt;pread&lt;/code&gt; song song vượt hẳn &lt;code&gt;mmap&lt;/code&gt; song song, vốn chững lại quanh 28 GB/s do tranh chấp page fault, vì sao chép tuần tự theo khối rẻ hơn page fault hàng loạt. SIMD bằng AVX2 hay gói &lt;code&gt;simd/archsimd&lt;/code&gt; của Go 1.26 làm vòng quét gần như miễn phí, nhưng khi đã chạm trần băng thông bộ nhớ thì chiến lược đọc và mức song song mới là yếu tố quyết định. Kết quả tốt nhất đạt khoảng 49 GB/s với &lt;code&gt;pread&lt;/code&gt; song song kết hợp SIMD thuần Go; muốn nhanh hơn nữa chỉ còn cách đọc ít dữ liệu hơn hoặc đổi phần cứng.&lt;/p&gt;
&lt;h2 id="http-caching-a-refresher"&gt;&lt;a href="https://danburzo.ro/http-caching-refresher/" rel="noopener noreferrer"&gt;HTTP caching, a refresher&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Dan Cătălin Burzo đọc lại RFC 9111 để hệ thống hóa những khái niệm quan trọng của HTTP caching hiện đại. Bài viết nhắc rằng cache không chỉ là cache riêng của trình duyệt mà còn gồm các shared cache như proxy, CDN hay Varnish đứng giữa client và máy chủ gốc. Trọng tâm là header &lt;code&gt;Cache-Control&lt;/code&gt;: các directive như &lt;code&gt;max-age&lt;/code&gt;, &lt;code&gt;s-maxage&lt;/code&gt;, &lt;code&gt;no-cache&lt;/code&gt;, &lt;code&gt;no-store&lt;/code&gt;, &lt;code&gt;private&lt;/code&gt;, &lt;code&gt;public&lt;/code&gt;, &lt;code&gt;must-revalidate&lt;/code&gt;, &lt;code&gt;stale-while-revalidate&lt;/code&gt; và &lt;code&gt;stale-if-error&lt;/code&gt; không chỉ quyết định có lưu response hay không, mà còn quyết định khi nào response còn &amp;ldquo;tươi&amp;rdquo; dựa trên tuổi so với &lt;code&gt;max-age&lt;/code&gt;, &lt;code&gt;s-maxage&lt;/code&gt;, &lt;code&gt;Expires&lt;/code&gt; hoặc ước lượng từ &lt;code&gt;Last-Modified&lt;/code&gt;, khi nào phải xác thực lại, và shared cache có được lưu response của request mang &lt;code&gt;Authorization&lt;/code&gt; hay không.&lt;/p&gt;
&lt;p&gt;Tác giả cũng làm rõ những chi tiết dễ nhầm. Response hết hạn không nhất thiết bị bỏ đi, vì cache có thể gửi request có điều kiện bằng &lt;code&gt;If-None-Match&lt;/code&gt; với &lt;code&gt;ETag&lt;/code&gt; hoặc &lt;code&gt;If-Modified-Since&lt;/code&gt; với &lt;code&gt;Last-Modified&lt;/code&gt; để nhận &lt;code&gt;304 Not Modified&lt;/code&gt; mà không phải tải lại nội dung. Header &lt;code&gt;Vary&lt;/code&gt; mở rộng khóa cache theo các header như &lt;code&gt;Accept-Language&lt;/code&gt; nhưng dễ làm cache kém hiệu quả nếu giá trị quá đa dạng, còn &lt;code&gt;No-Vary-Search&lt;/code&gt; giải quyết chiều ngược lại bằng cách bỏ qua các tham số truy vấn không ảnh hưởng nội dung. Phần cuối so sánh tải lại thường và tải lại cưỡng bức trong trình duyệt, vai trò của &lt;code&gt;immutable&lt;/code&gt;, và cảnh báo rằng &lt;code&gt;public&lt;/code&gt;, &lt;code&gt;s-maxage&lt;/code&gt; hay &lt;code&gt;must-revalidate&lt;/code&gt; có thể khiến shared cache lưu response của request đã xác thực nếu dùng sai, nên hãy dùng &lt;code&gt;private&lt;/code&gt; khi cần chặn. Đây là bài ôn tập tốt cho cả người làm máy chủ, CDN lẫn client tự viết.&lt;/p&gt;
&lt;h2 id="how-to-use-ss-as-a-replacement-for-netstat-to-view-ipv4-sockets"&gt;&lt;a href="https://oneuptime.com/blog/post/2026-03-20-ss-replacement-netstat-ipv4-sockets/view" rel="noopener noreferrer"&gt;How to Use ss as a Replacement for netstat to View IPv4 Sockets&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Nawaz Dhandala giới thiệu &lt;code&gt;ss&lt;/code&gt; như công cụ hiện đại thay thế &lt;code&gt;netstat&lt;/code&gt; đã lỗi thời để kiểm tra socket trên Linux. Vì truy vấn thông tin socket trực tiếp từ kernel, &lt;code&gt;ss&lt;/code&gt; nhanh hơn đáng kể, nhất là trên máy chủ bận rộn với hàng nghìn kết nối. Bài viết tập trung vào IPv4 với cờ &lt;code&gt;-4&lt;/code&gt; và đưa ra các mẫu lệnh hay dùng: xem toàn bộ socket TCP, UDP và raw, xem kết nối TCP, xem các cổng đang lắng nghe, hiển thị tên tiến trình và PID khi có quyền, lọc theo trạng thái như &lt;code&gt;state established&lt;/code&gt;, lọc theo cổng bằng biểu thức như &lt;code&gt;dport = :443&lt;/code&gt;, lọc theo địa chỉ như &lt;code&gt;src 192.168.1.100&lt;/code&gt;, và xem thống kê tổng quan bằng &lt;code&gt;ss -s&lt;/code&gt;. Các cờ &lt;code&gt;-t&lt;/code&gt;, &lt;code&gt;-u&lt;/code&gt;, &lt;code&gt;-l&lt;/code&gt;, &lt;code&gt;-n&lt;/code&gt;, &lt;code&gt;-p&lt;/code&gt; lần lượt tương ứng với TCP, UDP, socket đang lắng nghe, hiển thị dạng số và thông tin tiến trình.&lt;/p&gt;
&lt;p&gt;Phần hữu ích nhất là bảng đối chiếu từ &lt;code&gt;netstat&lt;/code&gt; sang &lt;code&gt;ss&lt;/code&gt;: những lệnh quen tay như &lt;code&gt;netstat -an&lt;/code&gt;, &lt;code&gt;netstat -tnp&lt;/code&gt;, &lt;code&gt;netstat -tlnp&lt;/code&gt; gần như chuyển thẳng thành &lt;code&gt;ss -an&lt;/code&gt;, &lt;code&gt;ss -tnp&lt;/code&gt;, &lt;code&gt;ss -tlnp&lt;/code&gt;. Trong chẩn đoán hằng ngày, &lt;code&gt;ss -4tulnp&lt;/code&gt; là lệnh gọn nhất để xem các socket TCP và UDP IPv4 đang lắng nghe kèm tiến trình sở hữu, còn &lt;code&gt;ss -4tn state established&lt;/code&gt; phù hợp khi cần xem các kết nối TCP IPv4 đang hoạt động. Đây là một bảng tra cứu ngắn gọn, thực dụng cho công việc vận hành Linux.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Bài viết đã được viết lại bởi Claude Code với Opus 5.5 vào ngày 27/09/2026.&lt;/em&gt;&lt;/p&gt;</description></item><item><title>Newsletter #116</title><link>https://miti99.com/post/2026/07/04/</link><pubDate>Sat, 04 Jul 2026 00:00:00 +0700</pubDate><guid>https://miti99.com/post/2026/07/04/</guid><description>&lt;p&gt;&lt;em&gt;Mời bạn thưởng thức Newsletter #116.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="8-myths-on-software-engineering-and-ai"&gt;&lt;a href="https://newsletter.getdx.com/p/8-myths-on-software-engineering-and" rel="noopener noreferrer"&gt;8 myths on software engineering and AI&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Brian Houck giới thiệu bài báo &amp;ldquo;8 Myths on Software Engineering and GenAI&amp;rdquo; mà ông đồng tác giả, gom tám hiểu lầm phổ biến về AI trong kỹ thuật phần mềm thành ba nhóm: lập trình viên thực sự dùng thời gian thế nào, đo tác động của AI ra sao, và AI được áp dụng trong tổ chức thật như thế nào. Một nghiên cứu năm 2025 cho thấy lập trình viên chỉ dành khoảng 14% thời gian để viết mã, phần còn lại là thiết kế, họp, rà soát mã và phối hợp. Vì vậy công cụ sinh mã chỉ là một đòn bẩy nhỏ, và tăng tốc một khâu dễ đẩy nút thắt sang khâu rà soát. Đo năng suất bằng số dòng mã, kể cả dòng do AI sinh ra, làm phình thêm gánh nặng rà soát; PR throughput tốt hơn nhưng vẫn cần đặt trong một bộ chỉ số cân bằng.&lt;/p&gt;
&lt;p&gt;Kết quả nghiên cứu về công cụ AI rất khác nhau: có nghiên cứu thấy tăng tốc rõ, có nghiên cứu trung tính, và một nghiên cứu cho thấy lập trình viên giàu kinh nghiệm còn mất thêm 18% thời gian khi dùng AI. Nhiệm vụ, kinh nghiệm và cả cách viết prompt đều ảnh hưởng đến kết quả. Về áp dụng, 80% lập trình viên dùng công cụ AI nhưng chỉ 29% tin vào độ chính xác của chúng. Thông điệp cuối cùng là tác động của AI phụ thuộc vào hệ thống công việc quanh lập trình viên nhiều hơn vào mô hình: tổ chức cần xây dựng niềm tin, quy trình, guardrail và cách đo ở cấp hệ thống, thay vì chỉ mua giấy phép rồi chờ năng suất tự tăng.&lt;/p&gt;
&lt;h2 id="you-don"&gt;&lt;a href="https://blog.tjll.net/you-dont-love-systemd-timers-enough/" rel="noopener noreferrer"&gt;You Don&amp;rsquo;t Love systemd Timers Enough&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Tác giả cho rằng cụm từ &amp;ldquo;cron job&amp;rdquo; vẫn được dùng cho mọi tác vụ chạy định kỳ, nhưng năm 2026 không nhất thiết phải dùng &lt;code&gt;cron&lt;/code&gt; thật nữa. systemd timer là một loại unit dùng để lên lịch cho unit khác, thường là một &lt;code&gt;.service&lt;/code&gt; cùng tên, nhờ đó tách rõ &amp;ldquo;khi nào chạy&amp;rdquo; với &amp;ldquo;chạy cái gì&amp;rdquo;. Cách này giải quyết các vấn đề quen thuộc của &lt;code&gt;cron&lt;/code&gt;: &lt;code&gt;$PATH&lt;/code&gt; khó đoán, stdout/stderr bị thất lạc, lịch sử chạy khó tra cứu và cú pháp lịch khó đọc. Kết quả chạy nằm trong journal, còn &lt;code&gt;systemctl status&lt;/code&gt; và &lt;code&gt;systemctl list-timers&lt;/code&gt; cho biết lần chạy trước và lần chạy kế tiếp. Bài viết cũng lưu ý &lt;code&gt;ExecStart=&lt;/code&gt; mặc định không chạy như lệnh shell và không kế thừa biến môi trường, nên cần viết unit file rõ ràng thay vì giả định nó giống một script shell; ngoài ra nên tận dụng các tùy chọn sẵn có như &lt;code&gt;ExecCondition=&lt;/code&gt;, &lt;code&gt;OnFailure=&lt;/code&gt; hay &lt;code&gt;Restart=&lt;/code&gt; thay vì tự viết logic.&lt;/p&gt;
&lt;p&gt;Phần giá trị nhất là các tính năng vượt qua &lt;code&gt;cron&lt;/code&gt; truyền thống. Lệnh &lt;code&gt;systemd-analyze calendar&lt;/code&gt; giúp kiểm tra và giải thích biểu thức thời gian trước khi dùng. Bên cạnh lịch theo đồng hồ với &lt;code&gt;OnCalendar=&lt;/code&gt;, timer còn hỗ trợ lịch tương đối như &lt;code&gt;OnBootSec=&lt;/code&gt; và &lt;code&gt;OnUnitActiveSec=&lt;/code&gt;, tức chạy sau khi máy khởi động hoặc sau lần chạy trước, thường khớp với ý định vận hành hơn lịch theo phút hay giờ cố định. Các tùy chọn &lt;code&gt;WakeSystem=&lt;/code&gt;, &lt;code&gt;Persistent=&lt;/code&gt;, &lt;code&gt;RandomizedDelaySec=&lt;/code&gt;, &lt;code&gt;FixedRandomDelay=&lt;/code&gt; và &lt;code&gt;RandomizedOffsetSec=&lt;/code&gt; giúp đánh thức máy khi cần, bù lại lần chạy bị bỏ lỡ sau thời gian tắt máy và giảm hiện tượng nhiều máy cùng chạy một lúc (thundering herd). Nói ngắn gọn, systemd timer là công cụ vận hành giàu ngữ nghĩa hơn hẳn &lt;code&gt;cron&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="life-is-too-short-for-a-slow-terminal"&gt;&lt;a href="https://mijndertstuij.nl/posts/life-is-too-short-for-a-slow-terminal/" rel="noopener noreferrer"&gt;Life is too short for a slow terminal&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Mijndert Stuij viết về việc giữ terminal thật nhanh vì đây là công cụ được dùng gần như cả ngày, và mỗi độ trễ nhỏ khi mở tab, gõ phím hay nhấn tab để hoàn thành lệnh sẽ lặp lại hàng trăm lần. Shell zsh của tác giả khởi động trong khoảng 30ms, không nhờ một dự án tối ưu lớn mà nhờ thói quen giữ cấu hình tối giản. Thắng lợi lớn nhất đến từ những thứ không có mặt: không oh-my-zsh, không plugin manager, chỉ source trực tiếp ba plugin thật sự cần. &lt;code&gt;compinit&lt;/code&gt; là điểm nóng phổ biến, nên tác giả dùng cache &lt;code&gt;.zcompdump&lt;/code&gt; để chỉ chạy kiểm tra đầy đủ khoảng một lần mỗi ngày, các lần còn lại dùng &lt;code&gt;compinit -C&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Với những công cụ nặng như &lt;code&gt;nvm&lt;/code&gt; hay completion của &lt;code&gt;kubectl&lt;/code&gt;, tác giả dùng lazy-loading: bọc chúng trong một hàm chỉ nạp thật khi được gọi lần đầu. Bất cứ công cụ nào hướng dẫn thêm &lt;code&gt;eval &amp;quot;$(tool init zsh)&amp;quot;&lt;/code&gt; vào &lt;code&gt;.zshrc&lt;/code&gt; đều đáng cân nhắc nạp trễ. Prompt cũng cần tránh chạy &lt;code&gt;git status&lt;/code&gt; đồng bộ trong repository lớn; prompt bất đồng bộ như &lt;code&gt;pure&lt;/code&gt; hiển thị ngay rồi bổ sung trạng thái Git sau. Cuối cùng, tác giả khuyên đo thay vì đoán, phân biệt ba loại độ trễ là thời gian khởi động, độ trễ prompt và độ trễ nhập liệu: dùng &lt;code&gt;time zsh -i -c exit&lt;/code&gt;, &lt;code&gt;hyperfine&lt;/code&gt;, &lt;code&gt;zprof&lt;/code&gt; hoặc trace quá trình khởi động để tìm điểm chậm nhất rồi sửa lần lượt. Theo tác giả, dưới 100ms là ổn và dưới 50ms là rất tốt.&lt;/p&gt;
&lt;h2 id="opencodereview"&gt;&lt;a href="https://github.com/alibaba/open-code-review" rel="noopener noreferrer"&gt;OpenCodeReview&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;OpenCodeReview là công cụ CLI mã nguồn mở của Alibaba dùng AI để rà soát mã. Dự án xuất phát từ trợ lý rà soát nội bộ đã phục vụ hàng chục nghìn lập trình viên trong hai năm, sau đó được mở cho cộng đồng và chỉ cần cấu hình một model endpoint tương thích OpenAI hoặc Anthropic là dùng được. Thay vì gửi một đoạn diff cho mô hình rồi nhận nhận xét chung chung, công cụ đọc Git diff, để agent đọc toàn bộ file, tìm kiếm trong codebase và xem các file thay đổi liên quan, rồi tạo comment chính xác theo từng dòng. Ngoài rà soát theo diff, lệnh &lt;code&gt;ocr scan&lt;/code&gt; có thể rà soát cả file hoặc thư mục khi cần kiểm tra một codebase chưa quen.&lt;/p&gt;
&lt;p&gt;Điểm thiết kế đáng chú ý là kiến trúc lai giữa logic xác định và agent. Những bước không được phép sai như chọn file, gom nhóm file liên quan, khớp rule với từng file và định vị comment được xử lý bằng pipeline có ràng buộc cứng; agent chỉ tập trung vào phần cần suy luận động như đọc ngữ cảnh và gọi công cụ, với prompt và bộ công cụ được tinh chỉnh riêng cho việc rà soát. Công cụ có sẵn bộ rule cho các lỗi như NPE, thread-safety, XSS và SQL injection. Benchmark dựng từ 200 pull request thật cho thấy so với agent đa năng dùng cùng mô hình, OpenCodeReview có precision và F1 cao hơn, chỉ tốn khoảng 1/9 lượng token, nhưng recall thấp hơn; đây là đánh đổi có chủ đích để giảm cảnh báo sai.&lt;/p&gt;
&lt;h2 id="golang-code-review-notes-ii"&gt;&lt;a href="https://www.elttam.com/blog/golang-code-review-notes-ii" rel="noopener noreferrer"&gt;Golang code review notes II&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Zoltan Madarassy và Alex Brown tiếp tục loạt ghi chú rà soát mã Go dưới góc nhìn bảo mật. Luận điểm chính là công cụ tự động và trợ lý AI đã bắt được phần lớn lỗi hiển nhiên, nhưng phần nguy hiểm còn lại nằm ở những góc khuất của ngôn ngữ và thư viện chuẩn mà người rà soát phải chủ động biết để tìm. Bài viết điểm qua vài cải thiện tích cực: &lt;code&gt;os.Root&lt;/code&gt; từ Go 1.24 giúp chặn lỗi truy cập vượt thư mục (path traversal), còn các thay đổi quanh &lt;code&gt;math/rand&lt;/code&gt; và &lt;code&gt;crypto/rand&lt;/code&gt; khiến các hàm sinh khóa luôn dùng nguồn ngẫu nhiên an toàn, loại bỏ khả năng vô tình dùng nguồn yếu.&lt;/p&gt;
&lt;p&gt;Trọng tâm là các cái bẫy vẫn dễ bị bỏ sót. Tràn số nguyên diễn ra âm thầm, chẳng hạn khi ép độ dài sang kiểu hẹp hơn như &lt;code&gt;uint32(len(s))&lt;/code&gt;, có thể phá vỡ ranh giới dữ liệu trong thư viện tuần tự hóa. &lt;code&gt;httputil.ReverseProxy.Director&lt;/code&gt; có thể bị ảnh hưởng bởi hop-by-hop header, nên chuyển sang &lt;code&gt;Rewrite&lt;/code&gt; vì hàm này chạy sau bước loại bỏ các header đó. Sao chép nhầm con trỏ trong &lt;code&gt;net/url&lt;/code&gt; dẫn tới thay đổi trạng thái dùng chung và điều kiện tranh chấp; byte null đi qua biên CGO hoặc thư viện C có thể giúp vượt qua kiểm tra xác thực; &lt;code&gt;encoding/json&lt;/code&gt; có thể bỏ qua custom marshaller khi method dùng pointer receiver nhưng struct lại được encode theo giá trị. JSON API cũng không tự miễn nhiễm CSRF nếu không kiểm tra &lt;code&gt;Content-Type&lt;/code&gt;, cookie &lt;code&gt;SameSite&lt;/code&gt; và nguồn gốc request. Tác giả phát hành thêm các Semgrep rule để phát hiện một số mẫu rủi ro này.&lt;/p&gt;
&lt;h2 id="thoughts-on-starting-new-projects-with-llm-agents"&gt;&lt;a href="https://eli.thegreenplace.net/2026/thoughts-on-starting-new-projects-with-llm-agents/" rel="noopener noreferrer"&gt;Thoughts on starting new projects with LLM agents&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Eli Bendersky chia sẻ kinh nghiệm dùng agent để xây từ đầu dự án &lt;code&gt;watgo&lt;/code&gt; viết bằng Go. Tác giả bắt đầu bằng việc cùng agent lặp lại thiết kế và API, lưu vào một file Markdown được commit trong kho mã để cả người và agent cùng bám theo. Sau đó ông yêu cầu agent viết các changelist (CL) nhỏ theo thứ tự hợp lý, tự rà soát bằng diff view của VSCode, chỉnh tay khi cần rồi mới tự commit. Khi agent tạo ra một thay đổi quá lớn, cách thực tế là commit ở điểm chấp nhận được rồi tiếp tục yêu cầu tái cấu trúc bằng các CL riêng; nếu đi sai hướng, cả chuỗi vẫn có thể được revert.&lt;/p&gt;
&lt;p&gt;Thông điệp chính là phải giữ con người trong vòng kiểm soát với dự án cần bảo trì lâu dài. Vibe coding có thể phù hợp với nguyên mẫu hoặc mã dùng một lần, nhưng với dự án quan trọng, người viết phải hiểu, rà soát và dẫn hướng mọi quyết định. Tác giả nhấn mạnh CL nhỏ và một chiến lược kiểm thử vững, đồng thời cảnh báo không nên để agent viết cả kiểm thử lẫn phần triển khai rồi tự củng cố lỗi sai của chính nó. Go được xem là ngôn ngữ rất hợp để agent viết vì ít thay đổi, ít cách làm trùng lặp, thư viện chuẩn phong phú, định dạng thống nhất và xử lý lỗi tường minh; khi phần lớn thời gian của con người chuyển từ viết sang đọc mã, tính dễ đọc trở thành lợi thế lớn. Tác giả cũng khuyên lập trình viên junior thận trọng khi dựa vào LLM.&lt;/p&gt;
&lt;h2 id="the-history-of-pets-vs-cattle-and-how-to-use-the-analogy-properly"&gt;&lt;a href="https://cloudscaling.com/blog/cloud-computing/the-history-of-pets-vs-cattle/" rel="noopener noreferrer"&gt;The History of Pets vs Cattle and How to Use the Analogy Properly&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Randy Bias kể lại nguồn gốc và ý nghĩa đúng của ẩn dụ &amp;ldquo;pets vs cattle&amp;rdquo; trong hạ tầng đám mây. Ông mượn ý tưởng từ một bài trình bày về mở rộng SQL Server, rồi đặt nó vào bối cảnh cloud và nhấn mạnh tính độc nhất của &amp;ldquo;pet&amp;rdquo; so với tính có thể thay thế của &amp;ldquo;cattle&amp;rdquo;. &amp;ldquo;Pet&amp;rdquo; là máy chủ hoặc cặp máy chủ được coi là không thể thiếu, thường được dựng và chăm sóc thủ công, khi hỏng thì cả đội phải lao vào xử lý; &amp;ldquo;cattle&amp;rdquo; là nhóm từ ba máy trở lên được dựng bằng công cụ tự động, thiết kế để chịu lỗi, nên mất một vài node thì hệ thống vẫn tự vận hành mà không cần con người can thiệp. Ẩn dụ này thành công vì giải thích nhanh sự chuyển dịch từ cách làm cũ sang kiến trúc cloud-native.&lt;/p&gt;
&lt;p&gt;Bài viết cũng cảnh báo việc dùng sai ẩn dụ làm mất giá trị ban đầu. Ví dụ điển hình là tính năng &amp;ldquo;Pet Sets&amp;rdquo; của Kubernetes: các hệ thống có trạng thái được nêu làm ví dụ như Cassandra, Kafka hay MongoDB thực chất đều được thiết kế theo kiểu &amp;ldquo;cattle&amp;rdquo;, vì chúng chịu lỗi theo mô hình scale-out. Nếu định nghĩa &amp;ldquo;pet&amp;rdquo; là hệ thống cần xử lý đặc biệt thay vì hệ thống không được phép hỏng, người học sẽ khó phân biệt cách làm cũ và mới. Trọng tâm của ẩn dụ là khả năng thay thế từng máy chủ và thiết kế chịu lỗi ở cấp hệ thống, không phải chuyện workload có trạng thái hay không.&lt;/p&gt;
&lt;h2 id="every-byte-matters"&gt;&lt;a href="https://fzakaria.com/2026/06/01/every-byte-matters" rel="noopener noreferrer"&gt;Every byte matters&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Farid Zakaria nhắc rằng hiệu năng không chỉ nằm ở độ phức tạp Big-O. Ngay cả trong một vòng lặp tuyến tính, kích thước dữ liệu và cách bố trí bộ nhớ vẫn tạo khác biệt rất lớn vì CPU nạp dữ liệu theo từng cache line, thường là 64 byte. Nếu một struct lớn nhưng ta chỉ cần một trường nhỏ như &lt;code&gt;is_alive&lt;/code&gt;, cách tổ chức array of structs sẽ kéo theo nhiều byte không dùng tới vào cache. Ngược lại, struct of arrays gom cùng một trường vào vùng nhớ liên tục, giúp mỗi lần nạp cache phục vụ được nhiều phần tử hơn; trong thử nghiệm của tác giả, mức cải thiện lên tới 30 lần khi struct có kích thước 1KiB.&lt;/p&gt;
&lt;p&gt;Bài viết cũng giải thích vì sao kích thước working set quan trọng với truy cập ngẫu nhiên. Khi truy cập tuần tự, bộ prefetcher của CPU đoán được bước kế tiếp và che bớt độ trễ. Nhưng với hash map, cây, đồ thị hay các cấu trúc nhiều con trỏ, địa chỉ kế tiếp khó đoán, nên dữ liệu phải nằm gọn trong cache nếu muốn tránh chờ bộ nhớ. Chỉ cần tăng kích thước struct từ 64B lên 128B, cùng một số lượng phần tử đã bị đẩy xuống tầng cache chậm hơn: với 512 phần tử, struct 64B vừa trong L1d với độ trễ khoảng 3 ns, còn struct 128B đã tràn sang L2 với khoảng 11 ns. Bài học thực tế là mỗi byte trên đường nóng đều có chi phí, và hiểu kích thước struct, cache line cùng working set giúp tối ưu hiệu quả hơn nhiều so với chỉ phân tích thuật toán.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Bài viết đã được viết lại bởi Claude Code với Opus 5.5 vào ngày 27/09/2026.&lt;/em&gt;&lt;/p&gt;</description></item><item><title>Newsletter #115</title><link>https://miti99.com/post/2026/07/02/</link><pubDate>Thu, 02 Jul 2026 00:00:00 +0700</pubDate><guid>https://miti99.com/post/2026/07/02/</guid><description>&lt;p&gt;&lt;em&gt;Mời bạn thưởng thức Newsletter #115.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="go-experiments-explained"&gt;&lt;a href="https://www.alexedwards.net/blog/go-experiments-explained" rel="noopener noreferrer"&gt;Go Experiments Explained&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Alex Edwards giải thích cơ chế thử nghiệm mà Go thường đưa vào mỗi bản phát hành: có khi là gói mới trong thư viện chuẩn như &lt;code&gt;testing/synctest&lt;/code&gt;, có khi là thay đổi trình biên dịch hoặc runtime như bộ gom rác mới, và hiếm hơn là thay đổi hành vi ngôn ngữ như phạm vi biến vòng lặp trước khi thành mặc định ở Go 1.22. Mục đích là lấy phản hồi thực tế trước khi tính năng được chính thức hóa; nếu gây lỗi hoặc bị phản đối, nó có thể được sửa hoặc bỏ hẳn. Vòng đời phổ biến là ban đầu tắt mặc định, sau một hai bản phát hành thì bật mặc định, đôi khi kèm giai đoạn chuyển tiếp cho phép tạm tắt. Nhưng cũng có ngoại lệ: &lt;code&gt;Arenas&lt;/code&gt; bị treo vô thời hạn sau phản hồi tiêu cực, còn Swiss tables đi thẳng lên mặc định.&lt;/p&gt;
&lt;p&gt;Vì chưa có trang chính thức nào theo dõi trạng thái, tác giả tự tổng hợp bảng danh sách cho Go 1.26 từ &lt;code&gt;go doc goexperiment.Flags&lt;/code&gt;, mã nguồn &lt;code&gt;internal/buildcfg&lt;/code&gt; và ghi chú phát hành. Cách dùng: liệt kê tên viết thường, phân tách bằng dấu phẩy trong biến môi trường &lt;code&gt;GOEXPERIMENT&lt;/code&gt; để bật, thêm tiền tố &lt;code&gt;no&lt;/code&gt; để tắt, áp dụng cho &lt;code&gt;go build&lt;/code&gt;, &lt;code&gt;go run&lt;/code&gt; và &lt;code&gt;go test&lt;/code&gt;. Với lập trình viên Go thông thường, các mục đáng quan tâm là &lt;code&gt;GreenTeaGC&lt;/code&gt; và &lt;code&gt;Dwarf5&lt;/code&gt; (đã bật sẵn, nên biết cách tắt khi gặp sự cố), &lt;code&gt;JSONv2&lt;/code&gt;, &lt;code&gt;GoroutineLeakProfile&lt;/code&gt; để dò rò rỉ goroutine, &lt;code&gt;RuntimeSecret&lt;/code&gt; cho mã xử lý dữ liệu nhạy cảm và &lt;code&gt;RuntimeFreegc&lt;/code&gt;. Lưu ý quan trọng: thử nghiệm không nằm trong cam kết tương thích của Go, nên hãy dùng để thử, đo đạc và góp phản hồi thay vì phụ thuộc quá sớm.&lt;/p&gt;
&lt;h2 id="low-level-network-optimizations-socket-options-that-matter"&gt;&lt;a href="https://goperf.dev/02-networking/low-level-optimizations/" rel="noopener noreferrer"&gt;Low-Level Network Optimizations: Socket Options That Matter&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Bài viết thuộc Go Optimization Guide phân tích các tùy chọn socket ảnh hưởng trực tiếp tới độ trễ và thông lượng của ứng dụng mạng viết bằng Go. Giá trị mặc định của hệ điều hành ưu tiên an toàn và tương thích, nên dưới tải lớn chúng thường thành nút thắt trước cả CPU hay bộ nhớ. &lt;code&gt;TCP_NODELAY&lt;/code&gt; tắt thuật toán Nagle để gói tin nhỏ được gửi ngay, đổi lại tốn băng thông hơn. &lt;code&gt;SO_REUSEPORT&lt;/code&gt; cho phép nhiều tiến trình hoặc luồng cùng gắn vào một cổng, nhân hệ điều hành chia kết nối mới cho từng hàng đợi riêng, giảm tranh chấp và tận dụng nhiều nhân CPU. Kích thước bộ đệm &lt;code&gt;SO_RCVBUF&lt;/code&gt; và &lt;code&gt;SO_SNDBUF&lt;/code&gt; nên xấp xỉ tích của băng thông và thời gian khứ hồi: quá nhỏ thì tăng số lời gọi hệ thống và giới hạn thông lượng, quá lớn thì phí bộ nhớ và làm tăng độ trễ.&lt;/p&gt;
&lt;p&gt;Với TCP keepalive, mặc định rất bảo thủ; các giá trị thường gặp trong vận hành là chờ 30–60 giây, cách nhau 10–15 giây, thăm dò 3–5 lần, nhưng chỉnh quá gắt sẽ tăng lưu lượng và dễ báo sai trên đường truyền nghẽn. Trong Go, &lt;code&gt;SetKeepAlivePeriod&lt;/code&gt; chỉ điều khiển thời gian chờ ban đầu; muốn chỉnh &lt;code&gt;TCP_KEEPINTVL&lt;/code&gt; và &lt;code&gt;TCP_KEEPCNT&lt;/code&gt; phải dùng lời gọi hệ thống trực tiếp. &lt;code&gt;SOMAXCONN&lt;/code&gt; quyết định số kết nối được xếp hàng chờ, là cấu hình cấp hệ thống qua &lt;code&gt;sysctl&lt;/code&gt;. Để đặt tùy chọn đúng thời điểm, tức trước khi socket được gắn cổng, nên dùng hàm &lt;code&gt;Control&lt;/code&gt; của &lt;code&gt;net.ListenConfig&lt;/code&gt; cùng &lt;code&gt;syscall.RawConn&lt;/code&gt;. Thông điệp xuyên suốt: hãy thay đổi từng thứ một, kiểm thử dưới tải thực tế và dựa vào số liệu đo được thay vì sao chép cấu hình từ nơi khác.&lt;/p&gt;
&lt;h2 id="when-is-100-vibe-coding-ok-"&gt;&lt;a href="https://dev.to/realvorl/when-is-100-vibe-coding-ok--38p" rel="noopener noreferrer"&gt;When Is 100% Vibe Coding OK ?&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Viorel PETCU mở đầu bằng trải nghiệm mất nhiều thời gian xem xét những bản đặc tả được viết &amp;ldquo;theo cảm hứng&amp;rdquo;: đọc thì có vẻ mạch lạc nhưng gần như không nói gì cụ thể và không áp dụng được vào hệ thống hiện có. Tác giả không phản đối vibe coding, thậm chí dùng thường xuyên cho script dùng một lần, biểu đồ tạm, bộ phân tích cú pháp tạm hay tài liệu không ai phải bảo trì, vì ở đó tốc độ quan trọng hơn sự chặt chẽ. Nhưng khi phần mềm cần được bảo trì lâu dài, bài toán thay đổi hoàn toàn.&lt;/p&gt;
&lt;p&gt;Ví dụ minh họa là một câu đố gỗ gồm 25 mảnh pentacube giống hệt nhau cần lấp đầy khối &lt;code&gt;5×5×5&lt;/code&gt;. Sau khi tự lắp thất bại, tác giả dùng Go và ChatGPT để giải: khó khăn ban đầu không nằm ở thuật toán mà ở cách biểu diễn, từ quy ước tọa độ, ký hiệu lát cắt đến cách xoay và đặt tên mảnh. Khi mô hình đã ổn định, phần còn lại là vét cạn kinh điển với đệ quy và quay lui; phần cứng hiện đại đủ nhanh để không cần tối ưu thêm. Chỉ trong khoảng hai giờ, dự án thành bộ giải tổng quát, có xuất tệp OBJ/MTL để xem trong Blender. Điều cốt lõi là tác giả vượt qua được các giả định sai, ràng buộc bịa đặt và mã lỗi vì đã biết chắc bài toán có nghiệm. Kết luận: vibe coding &amp;ldquo;ổn 100%&amp;rdquo; khi con người nắm các bất biến, ràng buộc là thật và đầu ra kiểm chứng được; còn nếu chính yêu cầu cũng chỉ là cảm hứng thì hệ thống không có gì chắc chắn để hội tụ.&lt;/p&gt;
&lt;h2 id="self-calling-executables"&gt;&lt;a href="https://log.pfad.fr/2026/self-calling-executable/" rel="noopener noreferrer"&gt;Self-calling executables&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Olivier giới thiệu kỹ thuật &amp;ldquo;self-calling executable&amp;rdquo;: một chương trình đang chạy khởi động một bản khác của chính nó, trực tiếp hoặc gián tiếp, để đảm nhận vai trò khác. Ứng dụng đầu tiên là kiểm thử hàm &lt;code&gt;main&lt;/code&gt; như một hộp đen khi mã nguồn phụ thuộc trạng thái toàn cục khiến các bài kiểm thử can thiệp lẫn nhau. Bài kiểm thử gọi lại chính tệp thực thi kiểm thử qua &lt;code&gt;os.Args[0]&lt;/code&gt; kèm biến môi trường như &lt;code&gt;TEST_MAIN&lt;/code&gt;; trong &lt;code&gt;TestMain&lt;/code&gt;, nếu thấy biến này thì chạy lệnh chính thay vì bộ kiểm thử, nhờ đó mỗi lần chạy được cô lập trong một tiến trình con.&lt;/p&gt;
&lt;p&gt;Ứng dụng thứ hai là tương tác với công cụ bên ngoài, ví dụ jjui, giao diện dòng lệnh cho Jujutsu. Lệnh &lt;code&gt;ssh&lt;/code&gt; dùng biến &lt;code&gt;SSH_ASKPASS&lt;/code&gt; để gọi một chương trình khác khi cần hỏi mật khẩu; jjui trỏ biến này về chính nó, nên khi ssh cần mật khẩu, tiến trình cháu kết nối ngược về tiến trình chính qua Unix socket (đường dẫn chứa PID để nhiều phiên chạy song song), chuyển lời nhắc, nhận mật khẩu từ người dùng rồi in ra stdout cho ssh. Vì đây là thao tác nhạy cảm, tác giả thêm hai lớp bảo vệ: mỗi tiến trình con nhận một khóa ngẫu nhiên phải gửi kèm khi kết nối, và tiến trình chính dò chuỗi PID tổ tiên của bên kết nối để xác nhận. Nhược điểm gồm khó gỡ lỗi, nguy cơ đệ quy vô hạn nếu biến môi trường bị truyền sai, và khi bật race detector mỗi lần thoát sẽ ngủ 1 giây, từng khiến kiểm thử tích hợp của Forgejo kéo dài hơn 2 giờ thay vì 45 phút; có thể khắc phục bằng &lt;code&gt;GORACE=&amp;quot;atexit_sleep_ms=0&amp;quot;&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="modern-engineering-values"&gt;&lt;a href="https://cpojer.net/posts/modern-engineering-values" rel="noopener noreferrer"&gt;Modern Engineering Values&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Christoph Nakazawa kể rằng giờ ông hiếm khi tự tay viết mã: các dự án như Vite+, fate hay Athena Crisis phần lớn do coding agent viết, chủ yếu qua Codex CLI. Cách làm của ông là yêu cầu agent đề xuất kế hoạch và đặt câu hỏi trước khi thực thi, buộc tạo bài kiểm thử thất bại trước khi sửa lỗi, chạy vài vòng rà soát tự động rồi mới tự xem lại thay đổi. Khi viết mã không còn là nút thắt, việc thảo luận và xem xét mã mới là giới hạn.&lt;/p&gt;
&lt;p&gt;Từ đó, bài viết đề xuất những giá trị kỹ thuật cho thời agent. Quyền sở hữu mạnh: agent khuếch đại người hiểu sâu sản phẩm nhưng cũng khuếch đại nhiễu từ người thiếu ngữ cảnh, nên đội hiệu quả sẽ nhỏ, khoảng hai đến ba người, ranh giới rõ, và việc xem xét mã tập trung vào sự thống nhất thay vì tranh cãi từng dòng. Gu tốt để biết điều gì thực sự đáng làm. Rào chắn chặt và vòng phản hồi nhanh như lint, kiểm thử tự động, công cụ chỉ xử lý tệp thay đổi, vì làm việc với agent giống liên tục đón nhân viên mới không có ngữ cảnh. Đưa ngữ cảnh vào repo thay vì để rải rác ở tài liệu, tin nhắn hay trí nhớ của từng người. Tự sở hữu stack thay vì phụ thuộc nặng vào thư viện ngoài, vì agent đã làm giảm chi phí tự xây. Về quản lý, ông tin vai trò này sẽ ngày càng kỹ thuật hơn, vì khi thực thi rẻ đi thì khả năng phán đoán trở thành nút thắt.&lt;/p&gt;
&lt;h2 id="building-software-is-learning"&gt;&lt;a href="https://registerspill.thorstenball.com/p/building-software-is-learning" rel="noopener noreferrer"&gt;Building Software Is Learning&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Thorsten Ball công khai một tin nhắn nội bộ gửi đội Amp về việc vì sao xây phần mềm mới luôn là quá trình học. Kịch bản &amp;ldquo;cần tính năng này&amp;rdquo;, &amp;ldquo;làm xong rồi&amp;rdquo;, &amp;ldquo;đúng ý tôi&amp;rdquo; gần như không bao giờ xảy ra khi không có đặc tả; thực tế thường là &amp;ldquo;không phải ý tôi&amp;rdquo;, &amp;ldquo;có ba cách làm, chọn cách nào?&amp;rdquo; hoặc &amp;ldquo;dùng thử rồi, không thích&amp;rdquo;. Lý do là người ta chỉ thực sự hiểu mình đang xây gì trong lúc xây, và muốn tránh hoàn toàn thì phải đặc tả đầy đủ từ đầu, mà đặc tả đầy đủ thì chính là lập trình.&lt;/p&gt;
&lt;p&gt;Tin tốt là có thể rút ngắn khoảng thời gian giữa câu &amp;ldquo;để tôi làm&amp;rdquo; đầy tự tin và lúc thực tế phản hồi. Thay vì biến mất bốn tuần, hãy làm nguyên mẫu trong một giờ, viết đặc tả trong ba mươi phút, chia nhỏ và phát hành mỗi ngày một phần, cắt phạm vi để tập trung vào điều chưa biết, làm video demo giả, viết trước bài thông báo, hoặc viết ví dụ trong README để thử thiết kế API. Phản hồi có thể đến từ CI, đồng nghiệp, người dùng hay chính bạn khi dùng thử. Tác giả cũng lưu ý: bản thử nghiệm quá nhiều lỗi chỉ đem về báo cáo lỗi chứ không phải phản hồi giá trị, còn giữ nhánh ba tuần rồi gộp 27 commit thì khi CI hỏng sẽ có 27 nguyên nhân tiềm năng. Câu hỏi cần tự đặt liên tục là làm sao nhận phản hồi sớm nhất, thường xuyên nhất để học nhanh hơn.&lt;/p&gt;
&lt;h2 id="you-must-fix-your-asserts"&gt;&lt;a href="https://kristoff.it/blog/fix-your-asserts/" rel="noopener noreferrer"&gt;You Must Fix Your Asserts&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Loris Cro phản đối thói quen tắt assert trong môi trường production, lấy Zig làm bối cảnh. Assert là dòng mã khẳng định một sự thật của chương trình, như tiền điều kiện, hậu điều kiện hay bất biến; nếu hệ thống kiểu biểu diễn được ràng buộc thì nên dùng kiểu, còn không thì assert giúp bắt lỗi lập trình, thậm chí hiệu quả hơn nhiều bài kiểm thử đơn vị khi kết hợp fuzzing. Trong Zig, &lt;code&gt;std.debug.assert&lt;/code&gt; dựa trên &lt;code&gt;unreachable&lt;/code&gt;: ở chế độ có kiểm tra như Debug hay ReleaseSafe, vi phạm assert làm chương trình panic; ở ReleaseFast hay ReleaseSmall, trình biên dịch coi đó là dữ kiện để tối ưu, nhưng nếu assert sai thì chương trình sẽ hoạt động sai khó lường. Khác với C/C++, assert của Zig là hàm thường chứ không phải macro, nên biểu thức truyền vào luôn được tính.&lt;/p&gt;
&lt;p&gt;Theo tác giả, có ba lựa chọn: giữ assert như kiểm tra lúc chạy, dùng assert để tối ưu và chấp nhận rủi ro, hoặc tắt hẳn, và lựa chọn thứ ba là tệ nhất. Tắt assert nghĩa là khi điều &amp;ldquo;không thể xảy ra&amp;rdquo; xảy ra, chương trình vẫn chạy tiếp trên giả định sai, vẫn lệch khỏi đặc tả, mà không còn tín hiệu nào để phát hiện. Nguy hiểm hơn, người đến sau sẽ viết thêm mã dựa trên assert sai đó, và lỗ hổng có thể lặng lẽ xuất hiện. Tác giả dẫn ví dụ TigerBeetle luôn bật assert, còn Ghostty phát hành bản ReleaseFast. Kết luận: hãy sửa assert sai, dành assert tốn kém cho chế độ debug, ghi log thay vì dừng chương trình khi phù hợp, và chọn chế độ build theo mô hình rủi ro thay vì xóa tín hiệu lỗi.&lt;/p&gt;
&lt;h2 id="the-software-engineering-books-i-keep-recommending"&gt;&lt;a href="https://newsletter.techworld-with-milan.com/p/best-software-engineering-books-2026" rel="noopener noreferrer"&gt;The Software Engineering Books I keep recommending&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Dr Milan Milanović chia sẻ danh sách sách kỹ thuật phần mềm ông vẫn thường giới thiệu, với lý do đọc sách giúp ông có bề rộng và chiều sâu khi thảo luận, ra quyết định, và trong thời AI còn giúp đánh giá, định hướng công cụ AI tốt hơn. Thay vì một danh sách &amp;ldquo;ai cũng phải đọc&amp;rdquo;, sách được nhóm theo vấn đề người đọc đang gặp, vì câu hỏi đúng không phải &amp;ldquo;nên đọc gì&amp;rdquo; mà là &amp;ldquo;tôi đang vướng ở đâu&amp;rdquo;. Phần lớn các cuốn không gắn với công nghệ cụ thể nên kiến thức bền theo thời gian.&lt;/p&gt;
&lt;p&gt;Nhóm tư duy viết mã gồm &lt;code&gt;The Pragmatic Programmer&lt;/code&gt;, &lt;code&gt;Clean Code&lt;/code&gt; (một số quy tắc đã lỗi thời) và &lt;code&gt;A Philosophy of Software Design&lt;/code&gt;, cuốn tác giả hay đọc lại nhất. Kiến trúc có &lt;code&gt;Fundamentals of Software Architecture&lt;/code&gt;, &lt;code&gt;Software Architecture: The Hard Parts&lt;/code&gt; và &lt;code&gt;Domain-Driven Design Quickly&lt;/code&gt;; hệ thống và dữ liệu có &lt;code&gt;Designing Data-Intensive Applications&lt;/code&gt; bản thứ hai cùng &lt;code&gt;Understanding Distributed Systems&lt;/code&gt; dễ tiếp cận hơn. Mảng AI có &lt;code&gt;The Hundred-Page Language Models Book&lt;/code&gt; về cách mô hình ngôn ngữ hoạt động và &lt;code&gt;AI Engineering&lt;/code&gt; của Chip Huyen về xây ứng dụng AI thực tế. Các nhóm còn lại gồm &lt;code&gt;The Art of Unit Testing&lt;/code&gt; cho kiểm thử, &lt;code&gt;Accelerate&lt;/code&gt; với bốn chỉ số DORA và &lt;code&gt;Refactoring&lt;/code&gt; cho quy trình phát triển, cùng &lt;code&gt;The Software Engineer's Guidebook&lt;/code&gt; và &lt;code&gt;Deep Work&lt;/code&gt; cho sự nghiệp. Tác giả cũng giới thiệu cuốn sách mới của mình về các định luật trong kỹ thuật phần mềm.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Bài viết đã được viết lại bởi Claude Code với Opus 5.5 vào ngày 27/09/2026.&lt;/em&gt;&lt;/p&gt;</description></item><item><title>Newsletter #114</title><link>https://miti99.com/post/2026/07/01/</link><pubDate>Wed, 01 Jul 2026 00:00:00 +0700</pubDate><guid>https://miti99.com/post/2026/07/01/</guid><description>&lt;p&gt;&lt;em&gt;Mời bạn thưởng thức Newsletter #114.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="go-errors-are-a-story-most-teams-lose-the-plot"&gt;&lt;a href="https://robinsiep.com/blog/posts/go-errors/" rel="noopener noreferrer"&gt;Go errors are a story, most teams lose the plot&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Robin Siep phân tích một khác biệt cơ bản trong cách Go xử lý lỗi: khác với Java hay nhiều ngôn ngữ khác, lỗi trong Go không tự mang stack trace. Thay vào đó, lập trình viên bọc lỗi bằng ngữ cảnh ở mỗi tầng mà lỗi đi qua, chẳng hạn &lt;code&gt;fmt.Errorf(&amp;quot;unable to get user: %w&amp;quot;, err)&lt;/code&gt;, để cuối cùng thu được một chuỗi như &lt;code&gt;unable to get user: failed to query users table: connection refused&lt;/code&gt;. Tác giả gọi đây là &amp;ldquo;dấu vết logic&amp;rdquo;: nếu được duy trì tốt, nó dễ đọc hơn một stack trace dài hàng chục dòng và vẫn đầy đủ ngay cả khi lỗi đi qua channel, goroutine hay các điểm bàn giao khác mà stack trace có thể đánh mất.&lt;/p&gt;
&lt;p&gt;Vấn đề là dấu vết logic đòi hỏi kỷ luật liên tục từ cả đội. Chỉ cần một chỗ quên bọc lỗi, log có thể chỉ còn lại dòng &lt;code&gt;connection refused&lt;/code&gt; trơ trọi; và giống như comment, một thông điệp bọc lỗi viết từ hai năm trước có thể đã lỗi thời hoặc gây hiểu nhầm. Theo quan sát của tác giả, phần lớn các đội không theo kịp gánh nặng bảo trì này, nên lỗi phát sinh trên môi trường thật thiếu thông tin để tìm ra nguyên nhân. Giải pháp trung dung được đề xuất là gắn stack trace ngay khi ứng dụng tạo lỗi hoặc lần đầu nhận lỗi từ thư viện bên thứ ba, rồi chỉ thêm ngữ cảnh thủ công khi nó thật sự làm rõ vấn đề. Khi đó stack trace trở thành lớp dự phòng rẻ và luôn có sẵn. Việc tự cài đặt khá đơn giản, hoặc có thể dùng &lt;code&gt;pkg/errors&lt;/code&gt; của Dave Cheney, hay &lt;code&gt;cockroachdb/errors&lt;/code&gt; nếu cần một thư viện còn được bảo trì tích cực.&lt;/p&gt;
&lt;h2 id="the-last-technical-interview"&gt;&lt;a href="https://steve-yegge.medium.com/the-last-technical-interview-bc13ddcf4564" rel="noopener noreferrer"&gt;The Last Technical Interview&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Steve Yegge, người đã phỏng vấn kỹ thuật gần 35 năm, từng là Bar Raiser ở Amazon và thành viên Hiring Committee ở Google, cho rằng quy trình phỏng vấn truyền thống đang đi đến hồi kết. Mô hình vài buổi phỏng vấn một giờ dồn trong một ngày gần như không đổi suốt năm mươi năm, dù kết quả rất tệ: người phỏng vấn thường bất đồng với nhau, điểm phỏng vấn gần như không dự đoán được hiệu quả làm việc thật, còn hồ sơ ứng viên ngày càng vô dụng vì AI hỗ trợ viết. Câu chuyện đáng nhớ nhất là lần bộ phận tuyển dụng ở Google đưa cho hội đồng một loạt hồ sơ phỏng vấn đã ẩn danh dưới danh nghĩa &amp;ldquo;bài tập hiệu chỉnh&amp;rdquo;; hội đồng bỏ phiếu loại khoảng hai phần ba, rồi mới biết đó chính là hồ sơ của các thành viên năm xưa.&lt;/p&gt;
&lt;p&gt;Theo tác giả, tín hiệu tốt nhất luôn đến từ làm việc thật, như thực tập, chương trình co-op sáu tháng ở Geoworks hay hợp đồng thử việc, nên đã đến lúc ngừng &amp;ldquo;mô phỏng công việc&amp;rdquo;. Mô hình &amp;ldquo;campfire&amp;rdquo; đang nổi lên ở San Francisco: mời ứng viên làm vài ngày có trả phí trên mã nguồn thật, yêu cầu thật, cùng đội thật. Để công bằng, mỗi phần việc phải &amp;ldquo;được tính hai lần&amp;rdquo;: công ty nhận được tín hiệu đánh giá và kết quả thực tế, còn ứng viên mang về một bản ghi lâu dài, có xác nhận của công ty, về những gì họ đã làm và chất lượng ra sao, dù có nhận được lời mời hay không. Hệ thống này chỉ có giá trị khi các &amp;ldquo;dấu xác nhận&amp;rdquo; được cấp trung thực.&lt;/p&gt;
&lt;h2 id="guidelines-for-respectful-use-of-ai"&gt;&lt;a href="https://www.elidedbranches.com/2026/05/guidelines-for-respectful-use-of-ai.html" rel="noopener noreferrer"&gt;Guidelines for Respectful Use of AI&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Camille Fournier cho rằng nhiều lãnh đạo chỉ nhìn chính sách AI qua lăng kính bảo mật, tuân thủ hay chi phí, mà quên mất cách cả nhóm làm việc cùng nhau khi có AI. AI giúp một cá nhân tạo ra nhiều đầu ra hơn, nhưng nếu người đó không tự kiểm tra, phần việc kiểm soát chất lượng sẽ bị đẩy sang đồng đội và năng suất chung lại giảm. Nguyên tắc đầu tiên vì thế là: đừng bắt người khác đọc hay đánh giá thứ mà chính bạn chưa đọc. Điều này đúng với mã nguồn và càng đúng với tài liệu, vì ngữ cảnh trong đầu bạn lúc trò chuyện với AI có thể hoàn toàn không xuất hiện trong văn bản cuối cùng; nếu không thể giải thích và thảo luận về tài liệu đó, bạn không nên gửi nó đi.&lt;/p&gt;
&lt;p&gt;Nguyên tắc thứ hai là &amp;ldquo;ngắn hơn thì tốt hơn&amp;rdquo;: mã nguồn do AI viết thường dài dòng như mã hướng dẫn, pull request lên tới hàng nghìn dòng, tài liệu đáng lẽ 3 trang thành 10 đến 20 trang, còn tin nhắn và email biến thành bức tường chữ. Tác giả gợi ý một phép thử: nếu thay đổi này hỏng lúc 3 giờ sáng và không công cụ AI nào hoạt động, bạn có tự gỡ lỗi được không? Với tài liệu dài, hãy tóm tắt điểm quan trọng ngay phần đầu; nếu phải viết email thật dài để giải thích, có lẽ nên gọi điện. Cuối cùng, AI không phải cái cớ để tắt cả bộ não lẫn sự đồng cảm: hãy dùng năng suất tăng thêm để tạo ra sản phẩm đơn giản hơn, được kiểm thử kỹ hơn.&lt;/p&gt;
&lt;h2 id="software-after-ai"&gt;&lt;a href="https://tomtunguz.com/harnessing-ai/" rel="noopener noreferrer"&gt;Software After AI&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Tomasz Tunguz lập luận rằng kỷ nguyên phần mềm quen thuộc, nơi các sản phẩm SaaS quản lý cơ sở dữ liệu bằng quy trình cố định, đang nhường chỗ cho kỷ nguyên &amp;ldquo;harness&amp;rdquo;: lớp bao quanh LLM để biến nó thành agent đáng tin cậy. Ông ví AI như một con ngựa hoang cần được thuần hóa. Khi mọi công ty đều dùng chung những mô hình như nhau, lợi thế cạnh tranh không còn nằm ở bản thân mô hình mà thuộc về người &amp;ldquo;cầm cương&amp;rdquo; giỏi nhất.&lt;/p&gt;
&lt;p&gt;Bài viết mô tả bảy thành phần của harness. Ngữ cảnh và bộ nhớ cần hệ thống truy xuất riêng cho từng lĩnh vực, cùng một &amp;ldquo;cơ sở dữ liệu ngữ cảnh&amp;rdquo; ghi lại quy trình vận hành thực tế của doanh nghiệp. Công cụ và hành động được cung cấp qua registry, có kiểm tra tham số và chặn thao tác nhạy cảm bằng bước phê duyệt, với MCP đóng vai trò mô liên kết. Điều phối là vòng lặp nghĩ, làm, quan sát, lặp lại, kèm lập kế hoạch, agent con và điều kiện dừng. Trạng thái và lưu trữ giúp một tác vụ hỏng ở bước 7 tiếp tục từ bước 8 thay vì làm lại từ đầu. Sandbox cô lập môi trường chạy. Khả năng quan sát và quản trị, gồm truy vết, log, eval và con người phê duyệt quyết định quan trọng, biến bản demo thành hệ thống chạy thật. Cuối cùng là tối ưu chi phí và quy trình: bước nào nên xử lý bằng logic xác định, bước nào dùng mô hình lớn hay nhỏ. Các phòng lab lớn sẽ thắng ở thị trường họ ưu tiên, nhưng vẫn còn hàng nghìn thị trường khác cho startup.&lt;/p&gt;
&lt;h2 id="design-patterns-are-dead-long-live-design-patterns"&gt;&lt;a href="https://medium.com/google-cloud/design-patterns-are-dead-long-live-design-patterns-b2c2602fbdc4" rel="noopener noreferrer"&gt;Design Patterns Are Dead. Long Live Design Patterns.&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Christina Lin, một lập trình viên kỳ cựu, tự hỏi liệu design pattern có còn giá trị khi AI viết phần lớn mã nguồn. Singleton, Factory hay Observer vốn sinh ra cho con người chứ không cho trình biên dịch, nên ban đầu tác giả nghĩ chúng đã thành thừa. Nhưng khi codebase đủ lớn, AI bắt đầu viết lan man, vì nó cũng có giới hạn: nghiên cứu của Chroma trên 18 mô hình cho thấy độ tin cậy đều giảm khi đầu vào dài ra, hiện tượng &amp;ldquo;context rot&amp;rdquo; xuất hiện từ khoảng 300.000 token. Vì vậy pattern không chết mà được &amp;ldquo;thăng chức&amp;rdquo;: từ bản thiết kế triển khai thành cách nén ý định vào prompt và rào chắn kiến trúc cho một mô hình xác suất.&lt;/p&gt;
&lt;p&gt;Tác giả phân loại lại pattern bằng một câu hỏi: dùng pattern này để giúp con người đọc mã hay để kiềm chế AI? Những pattern chỉ bù cho tính năng ngôn ngữ còn thiếu như Iterator hay Prototype đã chết; các lớp trừu tượng dựng sẵn &amp;ldquo;phòng khi cần&amp;rdquo; cũng nên bớt, vì giờ đây mã nguồn thì rẻ, còn ý định mới đắt. Ngược lại, ba pattern trở nên quan trọng hơn bao giờ hết: Adapter ép đầu ra AI về định dạng chặt chẽ trước khi chạm vào cơ sở dữ liệu và trả lỗi lại để AI tự sửa; Decorator bọc cơ chế thử lại, giới hạn thời gian và theo dõi chi phí token quanh logic chính; Facade giấu quy trình agent phức tạp sau một giao diện gọn gàng dễ xem xét. Nguyên lý chung là coi đầu ra AI như dữ liệu không tin cậy: cách ly, kiểm tra ở biên và chỉ tin khi đã chứng minh được là hợp lệ.&lt;/p&gt;
&lt;h2 id="merge-intervals"&gt;&lt;a href="https://dev.to/jaspreet_singh_86ae1740ac/merge-intervals-89l" rel="noopener noreferrer"&gt;Merge Intervals&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Jaspreet Singh phân tích bài Merge Intervals, một câu hỏi phỏng vấn rất phổ biến: cho mảng các khoảng &lt;code&gt;[start, end]&lt;/code&gt;, hãy gộp mọi khoảng chồng lấn và trả về các khoảng rời nhau. Thoạt nhìn bài toán giống mô phỏng, và cách brute force là với mỗi khoảng, tiếp tục kiểm tra các khoảng phía sau để mở rộng điểm kết thúc, tốn &lt;code&gt;O(N²)&lt;/code&gt;. Mấu chốt nằm ở một quan sát: sau khi sắp xếp theo điểm bắt đầu, mọi khoảng chồng lấn sẽ nằm liền kề nhau, nên chỉ cần so sánh khoảng hiện tại với khoảng vừa gộp gần nhất.&lt;/p&gt;
&lt;p&gt;Thuật toán tối ưu vì thế rất gọn: sắp xếp theo &lt;code&gt;start&lt;/code&gt; rồi duyệt từ trái sang phải; nếu điểm kết thúc của khoảng cuối trong danh sách kết quả nhỏ hơn điểm bắt đầu của khoảng hiện tại thì thêm khoảng mới, ngược lại cập nhật điểm kết thúc bằng giá trị lớn hơn trong hai điểm kết thúc. Với đầu vào &lt;code&gt;[[1,3],[2,6],[8,10],[15,18]]&lt;/code&gt;, kết quả là &lt;code&gt;[[1,6],[8,10],[15,18]]&lt;/code&gt;. Độ phức tạp tổng là &lt;code&gt;O(N log N)&lt;/code&gt; do bước sắp xếp, còn phần duyệt chỉ &lt;code&gt;O(N)&lt;/code&gt;. Tác giả cũng gợi ý cách trình bày khi phỏng vấn: giải thích rõ vì sao sắp xếp khiến các khoảng chồng lấn trở nên liền kề, và ghi nhớ quy tắc &amp;ldquo;sắp xếp trước, rồi xử lý từ trái sang phải&amp;rdquo; cho cả nhóm bài như Insert Interval, Meeting Rooms hay Non-overlapping Intervals.&lt;/p&gt;
&lt;h2 id="best-time-to-buy-and-sell-stock"&gt;&lt;a href="https://dev.to/jaspreet_singh_86ae1740ac/best-time-to-buy-and-sell-stock-3792" rel="noopener noreferrer"&gt;Best Time to Buy and Sell Stock&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Jaspreet Singh tiếp tục với bài LeetCode kinh điển Best Time to Buy and Sell Stock: cho mảng giá cổ phiếu theo ngày, chỉ được mua một lần và bán một lần vào một ngày sau đó, hãy tìm lợi nhuận lớn nhất. Ví dụ với &lt;code&gt;[7,1,5,3,6,4]&lt;/code&gt;, mua ở giá 1 và bán ở giá 6 cho lợi nhuận 5. Cách brute force là coi mỗi ngày là ngày mua, thử mọi ngày bán phía sau rồi giữ lợi nhuận lớn nhất; cách này đúng nhưng tốn &lt;code&gt;O(N²)&lt;/code&gt; vì kiểm tra quá nhiều cặp mua-bán không cần thiết.&lt;/p&gt;
&lt;p&gt;Quan sát then chốt là khi đứng ở một ngày, ta không cần nhớ toàn bộ giá trước đó mà chỉ cần giá thấp nhất từng thấy, vì lợi nhuận tốt nhất nếu bán hôm nay bằng giá hiện tại trừ giá thấp nhất đó. Tác giả kể rằng ban đầu đã nghĩ tới mảng quy hoạch động &lt;code&gt;dp[i]&lt;/code&gt;, nhưng rồi nhận ra thông tin quan trọng duy nhất là giá nhỏ nhất, nên mảng có thể nén lại thành hai biến &lt;code&gt;minPrice&lt;/code&gt; và &lt;code&gt;maxProfit&lt;/code&gt;. Chỉ cần duyệt mảng một lần, tính lợi nhuận, cập nhật đáp án rồi cập nhật giá thấp nhất, thuật toán đạt &lt;code&gt;O(N)&lt;/code&gt; thời gian và &lt;code&gt;O(1)&lt;/code&gt; bộ nhớ. Đây là mẫu &amp;ldquo;prefix minimum&amp;rdquo;: mỗi khi gặp bài về lợi nhuận lớn nhất, hiệu lớn nhất hay kết quả tương lai dựa trên giá trị quá khứ, hãy nghĩ tới việc duy trì giá trị nhỏ nhất hoặc lớn nhất trong lúc duyệt.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Bài viết đã được viết lại bởi Claude Code với Opus 5.5 vào ngày 27/09/2026.&lt;/em&gt;&lt;/p&gt;</description></item><item><title>Newsletter #113</title><link>https://miti99.com/post/2026/06/24/</link><pubDate>Wed, 24 Jun 2026 00:00:00 +0700</pubDate><guid>https://miti99.com/post/2026/06/24/</guid><description>&lt;p&gt;&lt;em&gt;Mời bạn thưởng thức Newsletter #113.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="migrating-from-go-to-rust"&gt;&lt;a href="https://corrode.dev/learn/migration-guides/go-to-rust/" rel="noopener noreferrer"&gt;Migrating from Go to Rust&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Matthias Endler viết một hướng dẫn dài cho các nhóm backend đang cân nhắc chuyển dịch vụ từ Go sang Rust. Thông điệp chính không phải &amp;ldquo;Rust luôn nhanh hơn Go&amp;rdquo;, mà là Rust đẩy nhiều loại rủi ro vào hệ thống kiểu để trình biên dịch bắt trước khi lên môi trường production: giá trị &lt;code&gt;nil&lt;/code&gt;, xử lý lỗi, data race, vòng đời tài nguyên và quyền sở hữu (ownership). Go tối ưu cho tốc độ làm việc của cả nhóm: biên dịch nhanh, thư viện chuẩn mạnh, goroutine dễ dùng. Rust đổi lại bằng trình biên dịch khắt khe hơn, không có GC, cùng &lt;code&gt;Result&lt;/code&gt;, &lt;code&gt;Option&lt;/code&gt;, trait và kiểm tra &lt;code&gt;Send&lt;/code&gt;/&lt;code&gt;Sync&lt;/code&gt;. Các thói quen của Go cần được ánh xạ lại: &lt;code&gt;if err != nil&lt;/code&gt; thành &lt;code&gt;Result&lt;/code&gt; với &lt;code&gt;?&lt;/code&gt;, &lt;code&gt;nil&lt;/code&gt; thành &lt;code&gt;Option&lt;/code&gt;, interface thành trait, goroutine thành task khi thật sự cần.&lt;/p&gt;
&lt;p&gt;Phần thực tế nhất là chiến lược chuyển đổi: đừng viết lại toàn bộ. Hãy bắt đầu từ hot path, worker hoặc dịch vụ có ranh giới rõ, giữ nguyên API rồi chuyển lưu lượng dần từng endpoint qua gateway theo kiểu strangler pattern. &lt;code&gt;cgo&lt;/code&gt; dùng được nhưng thường phức tạp hơn so với tách phần Rust thành dịch vụ riêng. Tác giả cũng thẳng thắn về chi phí: borrow checker khiến tháng đầu vất vả, biên dịch chậm hơn, async coloring gây phiền và một số mảng hệ sinh thái còn nhỏ. Vì vậy không cần bỏ Go hoàn toàn: Go vẫn hợp cho công cụ Kubernetes, CLI và các dịch vụ kết nối &amp;ldquo;nhàm chán&amp;rdquo;, còn Rust đáng đầu tư cho dịch vụ nền tảng nơi độ tin cậy, độ trễ P99 và các lỗi như nil hay data race gây chi phí vận hành lớn hơn chi phí học và xây dựng.&lt;/p&gt;
&lt;h2 id="ai-engineering-for-developers"&gt;&lt;a href="https://www.lucavallin.com/blog/ai-engineering-for-developers" rel="noopener noreferrer"&gt;AI Engineering for Developers&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Luca Cavallin viết một hướng dẫn dài về AI engineering cho lập trình viên đã quen backend, HTTP, hàng đợi, Kubernetes và vận hành production. Luận điểm chính: khi đưa LLM vào sản phẩm, mô hình không phải toàn bộ sản phẩm; thứ thực sự tạo ra giá trị là hệ thống bao quanh nó, gồm prompt, RAG, eval, agent, suy luận (inference), khả năng quan sát, bảo mật và chi phí. Vì vậy công việc này gần kỹ thuật backend hơn huấn luyện mô hình. Đầu ra của AI không đúng/sai tuyệt đối và có thể đổi theo mô hình, prompt hay dữ liệu, nên một bản demo chưa đủ để phát hành: cần bộ dữ liệu eval, kiểm tra hồi quy mỗi lần đổi prompt hoặc mô hình, triển khai canary và giám sát ngay từ đầu.&lt;/p&gt;
&lt;p&gt;Bài viết đi từ nền tảng tới các phần thực dụng: chọn mô hình theo tác vụ, chi phí và độ trễ; tận dụng prompt engineering trước khi nghĩ tới RAG; coi RAG chủ yếu là bài toán tìm kiếm, nơi hybrid search, reranker, cách chia nhỏ tài liệu và trích dẫn nguồn quan trọng hơn việc nhồi thêm ngữ cảnh; chỉ finetune khi prompt và RAG đã hết tác dụng; tối ưu suy luận bằng cache, gom lô và định tuyến giữa các mô hình. Agent được mô tả như một vòng lặp gọi công cụ cần schema rõ ràng, quyền hạn hẹp, tracing xuyên suốt và bước phê duyệt cho hành động nguy hiểm. Phần production nhấn mạnh guardrail, quy trình CI/CD riêng cho thay đổi prompt và mô hình, phân bổ chi phí theo từng tính năng, và nguyên tắc xem mỗi agent như một dịch vụ có danh tính, quyền tối thiểu và nhật ký kiểm toán.&lt;/p&gt;
&lt;h2 id="the-invisible-engineering-behind-lambdas-network"&gt;&lt;a href="https://www.allthingsdistributed.com/2026/04/the-invisible-engineering-behind-lambdas-network.html" rel="noopener noreferrer"&gt;The invisible engineering behind Lambda’s network&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Bài viết trên All Things Distributed kể lại gần một thập kỷ tối ưu hạ tầng mạng phía sau AWS Lambda, loại công việc &amp;ldquo;vô hình&amp;rdquo; mà khách hàng chỉ cảm nhận qua việc hàm khởi động nhanh và ổn định hơn. Vấn đề ban đầu là cold start khi Lambda kết nối vào VPC: ngoài tạo microVM Firecracker và khởi động runtime, hệ thống còn phải dựng đường mạng riêng vào VPC của khách hàng, trong đó việc tạo Geneve tunnel từng nằm trên hot path. Thay vì duy trì bản vá kernel riêng, đội Lambda dùng eBPF: tạo sẵn tunnel với VNI giả, rồi ghi đè header khi VNI thật xuất hiện, nhờ đó độ trễ tạo tunnel giảm từ khoảng 150ms xuống 200 micro giây.&lt;/p&gt;
&lt;p&gt;Phần đáng học hơn là cách họ xử lý quy mô 4.000 mạng trên mỗi worker. Thay vì tạo tap, veth, namespace và rule mỗi khi hàm được gọi, họ tạo sẵn toàn bộ lúc worker khởi động, biến chi phí thay đổi theo tải thành chi phí cố định trả một lần. Họ thay NAT có trạng thái bằng thao tác sửa gói tin không trạng thái bằng eBPF, giảm độ trễ thiết lập NAT 100 lần; chuyển hơn 125.000 rule iptables ở root namespace thành 144 rule tĩnh bằng cách đưa rule riêng của từng slot vào namespace tương ứng; rồi giảm tranh chấp khóa RTNL bằng cách đổi thứ tự tạo mạng và gắn eBPF theo lô. Ở quy mô lớn, rule iptables, conntrack hay một khóa kernel đều có thể thành nút thắt. Kết quả là một topology thống nhất cho cả workload truyền thống lẫn SnapStart, tăng năng lực mạng cho snapshot 20 lần, và được đóng gói lại để Aurora DSQL dùng chung.&lt;/p&gt;
&lt;h2 id="selective-test-execution-at-stripe-fast-ci-for-a-50m-line-ruby-monorepo"&gt;&lt;a href="https://stripe.dev/blog/selective-test-execution-at-stripe-fast-ci-for-a-50m-line-ruby-monorepo" rel="noopener noreferrer"&gt;Selective Test Execution at Stripe: Fast CI for a 50M-line Ruby monorepo&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Aditya Anchuri mô tả cách Stripe giữ CI đủ nhanh cho một monorepo Ruby khoảng 50 triệu dòng, với gần 100.000 tệp kiểm thử và 1,2 triệu đơn vị kiểm thử. Chạy tuần tự toàn bộ sẽ mất khoảng bốn tháng, trong khi Stripe chạy khoảng 50.000 lần build mỗi tuần. Hệ thống Selective Test Execution (STE) giải quyết bằng cách trung bình chỉ chạy khoảng 5% bộ kiểm thử cho mỗi lần build (trung vị dưới 0,5%) và tốn chưa tới 10% tài nguyên tính toán so với chiến lược luôn chạy tất cả.&lt;/p&gt;
&lt;p&gt;Điểm hay là Stripe không cố phân tích phụ thuộc tĩnh cho Ruby, vì metaprogramming, dynamic dispatch, cấu hình và tệp sinh tự động khiến phân tích tĩnh hoặc bỏ sót, hoặc quá bảo thủ. Thay vào đó họ quan sát lúc chạy: một thư viện C++ nội bộ nạp qua &lt;code&gt;LD_PRELOAD&lt;/code&gt; chặn mọi lần mở tệp, gắn tệp được đọc với kiểm thử đang chạy và lan sang cả tiến trình con. Đường xử lý &lt;code&gt;open&lt;/code&gt; chỉ ghi log tối thiểu; việc tổng hợp và lập chỉ mục nằm ngoài hot path. Log được gom thành chỉ mục roaring bitmap ánh xạ từ tệp thay đổi sang kiểm thử cần chạy, lưu khoảng ba tỷ điểm dữ liệu mà vẫn truy vấn nhanh. Thay đổi được phát hiện bằng &lt;code&gt;hashdeep&lt;/code&gt; để bao phủ cả tệp sinh tự động, không chỉ tệp trong git. STE còn cần dữ liệu nền tái lập được và cơ chế an toàn cho CI thực tế: luôn chạy lại kiểm thử đang lỗi, luôn chạy kiểm thử dò tệp theo glob, xử lý linter theo danh sách tệp đổi, và lưu metadata trong MongoDB kèm Monotonic Revision ID để chọn nhanh bản nền theo thứ tự commit.&lt;/p&gt;
&lt;h2 id="being-oncall-taught-me-everything"&gt;&lt;a href="https://yaoyue.org/blog/2026-oncall/" rel="noopener noreferrer"&gt;Being oncall taught me everything&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Yao Yue nhìn lại 7,5 năm trực oncall cho hệ thống cache phân tán ở Twitter, dịch vụ có thông lượng cao nhất và cũng dính nhiều sự cố nghiêm trọng nhất. Bài viết không tô hồng oncall, nhưng cho rằng chính việc sống cùng production đã hình thành tư duy kỹ sư hạ tầng của tác giả. Oncall dạy rằng ở quy mô lớn, tính dự đoán được quan trọng hơn việc nhanh trong đa số trường hợp, tail latency quan trọng hơn trung vị, kiến trúc đơn giản rõ ràng là cứu cánh lúc khủng hoảng, và sự xuất sắc trong vận hành (quan sát kỹ, cấu hình nhất quán, sẵn sàng tự động hóa, giá trị mặc định hợp lý) đến từ thiết kế ban đầu chứ không phải vá vội hai tuần trước khi ra mắt.&lt;/p&gt;
&lt;p&gt;Phần đáng nhớ hơn là bài học về con người. Việc định kỳ khởi động lại Memcached để tránh sập toàn trang đôi khi lại tự gây sự cố, nên tác giả học cách chọn thời điểm ít rủi ro, báo trước cho mọi người và quan trọng nhất là nhận trách nhiệm khi mắc lỗi, vì đó là cách nhanh nhất để lấy lại niềm tin của đồng nghiệp. Nhiều sự cố chỉ được giải quyết nhờ sự kiên trì thu hẹp khả năng và sự giúp đỡ của đồng nghiệp ở mọi mảng hạ tầng: kỹ sư vận hành, kernel, chủ dịch vụ upstream, quản lý và đồng đội cùng trực chiến. Kết luận rất thẳng: kỹ sư chưa thật sự hiểu phần mềm cho tới khi thấy nó chạy và hỏng trong production, và người viết phần mềm không nên nghĩ mình đứng ngoài việc triển khai, giám sát hay gỡ lỗi chính thứ mình tạo ra.&lt;/p&gt;
&lt;h2 id="claude-as-your-performance-analysis-partner"&gt;&lt;a href="https://developers.redhat.com/articles/2026/05/29/claude-your-performance-analysis-partner" rel="noopener noreferrer"&gt;Claude as your performance analysis partner&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Archana Ravindar thử dùng Claude để hỗ trợ phân tích hiệu năng Go qua CPU profile và trace, tập trung vào bộ thu gom rác Green Tea trên kiến trúc POWER10 với bộ benchmark &lt;code&gt;sweet&lt;/code&gt;. Giá trị của Claude không nằm ở việc tự tạo ngay bản vá đúng, mà ở khả năng đọc nhanh những tệp rất lớn và khó nhìn như &lt;code&gt;pprof&lt;/code&gt;, bản dump assembly hay dòng thời gian trace. Ví dụ, Claude chỉ ra hot path trong &lt;code&gt;runtime.tryDeferToSpanScan&lt;/code&gt;, phát hiện mẫu atomic &lt;code&gt;Load8&lt;/code&gt; rồi &lt;code&gt;Or8&lt;/code&gt; và đề xuất gộp thành một lệnh &lt;code&gt;Or32&lt;/code&gt;. Thử nghiệm này lại chậm hơn, và Claude cũng giúp giải thích nguyên nhân là false sharing và tranh chấp giữa các worker GC, một minh họa rõ rằng giảm số lệnh atomic chưa chắc đã nhanh hơn.&lt;/p&gt;
&lt;p&gt;Claude còn hữu ích khi phân tích ở mức thấp: đối chiếu assembly để thấy trình biên dịch không cache lại &lt;code&gt;q.class.sizeclass&lt;/code&gt;, xếp hạng các TODO trong GC theo độ nóng của profile, và gợi ý các mẫu cần tìm trong trace như processor nhàn rỗi khi GC, khoảng trống mark assist, GC chạy quá dày hoặc &lt;code&gt;gcMarkDone&lt;/code&gt; kéo dài. Tác giả phân biệt rõ vai trò: profile cho biết chi phí nằm ở đâu, còn trace giúp hiểu vì sao hệ thống bị nghẽn, nhất là với GC và lập lịch. Tuy vậy mọi gợi ý đều phải đo lại, vì tối ưu phụ thuộc kiến trúc, cache line, tập lệnh hay áp lực thanh ghi rất dễ sai khi thiếu ngữ cảnh; cách dùng tốt nhất là để thu hẹp vùng nghi vấn và lập danh sách điều tra, không phải thay thế benchmark.&lt;/p&gt;
&lt;h2 id="things-you-didn"&gt;&lt;a href="https://jon.chrt.dev/2026/04/15/things-you-didnt-know-about-indexes.html" rel="noopener noreferrer"&gt;Things you didn&amp;rsquo;t know about indexes&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Jon Charter giải thích index trong Postgres bằng một ví dụ dễ hiểu: index giống mục lục sách, giúp cơ sở dữ liệu tìm dữ liệu qua một cấu trúc đã sắp xếp thay vì quét toàn bảng. Nhưng đi kèm là một đánh đổi quan trọng: đọc nhanh hơn thì ghi chậm hơn. Mỗi lệnh &lt;code&gt;INSERT&lt;/code&gt;, &lt;code&gt;UPDATE&lt;/code&gt;, &lt;code&gt;DELETE&lt;/code&gt; phải cập nhật thêm index; index còn tốn dung lượng, chiếm cache và khiến bộ lập kế hoạch truy vấn có thêm phương án phải cân nhắc. Vì vậy &amp;ldquo;đánh index cho mọi thứ&amp;rdquo; hiếm khi là chiến lược tốt.&lt;/p&gt;
&lt;p&gt;Phần hữu ích nhất là những lỗi phổ biến khiến index không được dùng. Composite index phụ thuộc thứ tự cột, nên thứ tự phải bám theo mẫu truy vấn thật: &lt;code&gt;(type_1, type_2)&lt;/code&gt; giúp truy vấn theo &lt;code&gt;type_1&lt;/code&gt; hoặc cả hai cột, nhưng hầu như vô ích nếu chỉ lọc theo &lt;code&gt;type_2&lt;/code&gt;. Áp hàm lên cột như &lt;code&gt;lower(name)&lt;/code&gt; khiến index thường trên &lt;code&gt;name&lt;/code&gt; bị bỏ qua, vì cơ sở dữ liệu cần cấu trúc sắp xếp theo đúng biểu thức &lt;code&gt;lower(name)&lt;/code&gt;; phép chuyển kiểu ngầm định cũng gây hiệu ứng tương tự. Cách kiểm tra đúng là dùng &lt;code&gt;EXPLAIN&lt;/code&gt; hoặc &lt;code&gt;EXPLAIN ANALYZE&lt;/code&gt; để xác nhận bộ lập kế hoạch thực sự chọn index nào, thay vì đoán. Bài viết cũng giới thiệu functional index, partial index cho những lát dữ liệu nhỏ như bản ghi xóa mềm hay &lt;code&gt;is_legendary = true&lt;/code&gt;, và covering index với &lt;code&gt;INCLUDE&lt;/code&gt; để một số truy vấn chạy bằng &lt;code&gt;Index Only Scan&lt;/code&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Bài viết đã được viết lại bởi Claude Code với Opus 5.5 vào ngày 27/09/2026.&lt;/em&gt;&lt;/p&gt;</description></item><item><title>Newsletter #112</title><link>https://miti99.com/post/2026/06/23/</link><pubDate>Tue, 23 Jun 2026 00:00:00 +0700</pubDate><guid>https://miti99.com/post/2026/06/23/</guid><description>&lt;p&gt;&lt;em&gt;Mời bạn thưởng thức Newsletter #112.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="a-few-interesting-modern-pixel-fonts"&gt;&lt;a href="https://unsung.aresluna.org/a-few-interesting-modern-pixel-fonts/" rel="noopener noreferrer"&gt;A few interesting modern pixel fonts&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Marcin Wichary điểm qua vài pixel font hiện đại có ý tưởng thú vị. Analog Mono của Andrew Gleeson &amp;ldquo;sửa tội&amp;rdquo; cho VCR OSD Mono, font pixel kinh điển từng xuất hiện khắp đầu VCR, TV và máy quay thập niên 1990, vốn có đường nền quá thấp khiến mọi chữ có phần đuôi bị kéo lên. Coral Pixels của Kumiko Yoshida là một color font cố tình giữ lại viền màu đặc trưng của hiển thị subpixel thời 1990–2000: thứ từng là lỗi kỹ thuật giờ trở thành chất liệu hoài cổ và yếu tố thị giác có chủ đích. Two Slice của Joseph Fatula thì đẩy sự tối giản tới cực hạn với chiều cao chỉ 2 pixel mà vẫn &amp;ldquo;tạm đọc được&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Tác giả lưu ý rằng tất cả đều là font vector giả dạng pixel để cài được trên hệ điều hành hiện đại, và điều đó dẫn tới Geist Pixel của Vercel. Dù lời giới thiệu hơi phô trương, nó chạm đúng một vấn đề quan trọng: pixel font thường vỡ khi đưa vào sản phẩm thật vì không co giãn tốt giữa các kích thước màn hình, metrics xung đột với hệ thống typography sẵn có, hoặc chỉ mang tính trang trí. Theo tác giả, gót chân Achilles của nhiều font không nằm ở hình dạng chữ mà ở phần việc &amp;ldquo;vô hình&amp;rdquo; xung quanh như kerning, metadata, bộ ký tự bổ sung và vertical metrics. Bài học cho người làm giao diện là vẻ hoài cổ có thể là điểm khởi đầu, nhưng độ ổn định khi tích hợp mới quyết định một font có dùng được trong sản phẩm hay không.&lt;/p&gt;
&lt;h2 id="the-best-engineers-write-less-code"&gt;&lt;a href="https://shvetsm.github.io/posts/the-best-engineers-write-less-code/" rel="noopener noreferrer"&gt;The Best Engineers Write Less Code&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Tác giả phản bác quan niệm rằng không cần viết phần mềm tốt nữa vì LLM đã sinh được mã nguồn. Phần mềm tệ vẫn là phần mềm tệ, dù do Claude Code hay một người thiếu kinh nghiệm viết ra: độ trễ, độ phức tạp và chi phí bảo trì vẫn là thật, và sẽ có rất nhiều việc phải làm để dọn dẹp mã kém chất lượng đang được đẩy lên production. Doanh nghiệp đo mọi thứ bằng thời gian, vì thời gian chính là tiền; đó cũng là lý do họ hào hứng với AI, bởi nó nhanh hơn và rẻ hơn.&lt;/p&gt;
&lt;p&gt;Theo tác giả, điều tốt nhất một kỹ sư có thể làm cho stakeholder là tránh xây những thứ không cần thiết, vì mỗi tính năng kéo theo chi phí dài hạn về bảo trì, gỡ lỗi, hạ tầng, hỗ trợ, di chuyển hệ thống và gánh nặng nhận thức. Trên con đường trở thành kỹ sư senior, bạn sẽ chuyển từ &amp;ldquo;Em làm được, mất vài ngày thôi&amp;rdquo; sang &amp;ldquo;Vì sao chúng ta lại xây thứ này?&amp;rdquo;, và câu hỏi thứ hai cực kỳ quan trọng: mã nguồn là một khoản nợ trừ khi nó giải quyết đúng vấn đề. Tệ nhất là xây sai thứ. Cách giảm rủi ro chính là tinh thần Agile thực chất, không phải nghi thức: thống nhất lý do cần làm, điều chỉnh yêu cầu để ra giá trị nhanh hơn, rồi chia khoảng ngắn để trình bày tiến độ và nhận phản hồi. Kỹ sư giỏi không được đo bằng lượng mã tạo ra, mà bằng giá trị mang lại so với độ phức tạp để lại, và thời gian rảnh tốt nhất là dành để xóa bớt mã.&lt;/p&gt;
&lt;h2 id="fast-is-better-than-slow"&gt;&lt;a href="https://dubroy.com/blog/fast-is-better-than-slow/" rel="noopener noreferrer"&gt;Fast is better than slow&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Patrick Dubroy nhận ra rằng những lập trình viên giỏi nhất ông từng làm cùng đều có chung một điểm: họ rất nhanh, vừa bàn xong vấn đề thì một hai giờ sau đã có bản vá hoặc nguyên mẫu. Ông cho rằng họ không nhanh vì giỏi, mà giỏi vì nhanh: làm nhanh giúp có dữ liệu sớm hơn, ra quyết định tốt hơn, học nhanh và học nhiều hơn theo thời gian, đồng thời thử được nhiều hướng giải quyết trước khi chọn cách tốt nhất. Điều này không đồng nghĩa với văn hóa làm quá giờ; có nhiều cách tăng tốc mà không cần làm thêm.&lt;/p&gt;
&lt;p&gt;Các gợi ý của ông gắn với thực tế công việc kỹ sư phần mềm hơn là kỹ năng lập trình thuần túy. Đừng trì hoãn chỉ vì chưa biết bắt đầu từ đâu, bởi chờ đợi hiếm khi làm việc dễ hơn. Tận dụng các khoảng thời gian nhỏ thay vì đợi một khối thời gian dài liền mạch, vì phân mảnh có thể lấy đi 25–35% thời gian mỗi ngày. Đừng ngại trông ngớ ngẩn: chia sẻ công việc sớm theo &amp;ldquo;quy tắc 70%&amp;rdquo;, gửi PR ngay và đón nhận phản hồi thay vì đánh bóng mãi, và chủ động hỏi đồng nghiệp vì phát triển phần mềm là môn thể thao đồng đội. Chọn đúng trận để tranh luận: khi review, phần lớn khác biệt nhỏ tới mức làm theo còn nhanh hơn cãi, và bản thân ông thường duyệt kiểu &amp;ldquo;LGTM kèm góp ý&amp;rdquo;. Cuối cùng, chỉ làm đúng phần được yêu cầu, vì cố làm &amp;ldquo;hơn mong đợi&amp;rdquo; thường là đoán mò về nhu cầu tương lai, mà càng ít kinh nghiệm thì càng dễ đoán sai.&lt;/p&gt;
&lt;h2 id="java-cheat-sheet"&gt;&lt;a href="https://tms-outsource.com/cs/java-cheat-sheet/" rel="noopener noreferrer"&gt;Java Cheat Sheet&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Đây là tài liệu tra cứu nhanh cho Java của Bogdan Sandu, tập trung vào cú pháp, các mẫu thường dùng và idiom có thể áp dụng ngay. Nội dung được chia theo nhóm: kiểu dữ liệu và ép kiểu, toán tử, câu lệnh điều khiển, mảng, chuỗi và &lt;code&gt;StringBuilder&lt;/code&gt;, lớp và đối tượng, các khái niệm OOP, access modifier, Collections Framework, xử lý ngoại lệ, streams và lambda, concurrency, I/O cùng các tính năng Java hiện đại. Điểm hữu ích là tài liệu không chỉ liệt kê cú pháp mà còn nhắc những lỗi quen thuộc của người mới như so sánh &lt;code&gt;String&lt;/code&gt; bằng &lt;code&gt;==&lt;/code&gt;, nhầm &lt;code&gt;.length&lt;/code&gt; của mảng với &lt;code&gt;.length()&lt;/code&gt; của chuỗi, xử lý &lt;code&gt;null&lt;/code&gt;, vượt giới hạn mảng hay nối chuỗi trong vòng lặp thay vì dùng &lt;code&gt;StringBuilder&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Với lập trình viên junior, cheat sheet kiểu này phù hợp để ôn nhanh khi chuyển từ ngôn ngữ khác sang Java, hoặc khi cần nhớ lại một API chuẩn mà không muốn đọc tài liệu dài. Tuy vậy, nên xem nó như bản đồ tra cứu ban đầu: nó không thay thế tài liệu chính thức, việc hiểu sâu về JVM, OOP, collections và concurrency, hay kinh nghiệm thực hành khi viết mã cho production.&lt;/p&gt;
&lt;h2 id="swapping-go-for-rust-10x-cheaper-k8s-ingress"&gt;&lt;a href="https://dev.to/syedahmershah/swapping-go-for-rust-10x-cheaper-k8s-ingress-2b4p" rel="noopener noreferrer"&gt;Swapping Go for Rust: 10x Cheaper K8s Ingress&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Syed Ahmer Shah kể lại lúc nhìn thấy hóa đơn AWS 4.200 USD mỗi tháng chỉ riêng cho phần compute của lớp ingress Kubernetes. Hệ thống dùng Traefik với 3 pod, mỗi pod khoảng 180MB RSS khi rảnh và hơn 400MB khi có tải, chạy trên các node &lt;code&gt;t3.large&lt;/code&gt; để đủ dư địa. Nguyên nhân không phải Traefik viết kém, mà là garbage collector của Go được tối ưu cho độ trễ chứ không cho bộ nhớ. Với một proxy, mỗi request đều cấp phát header, buffer và trạng thái kết nối; dưới tải liên tục, GC gần như không có &amp;ldquo;thời điểm tốt&amp;rdquo; để dọn dẹp. Ngược lại, Rust giải phóng bộ nhớ ngay khi ra khỏi phạm vi, nên mức dùng bộ nhớ phẳng và không có các đợt dừng do GC.&lt;/p&gt;
&lt;p&gt;Nhóm không viết lại Traefik mà chuyển phần proxy chính sang Envoy, kèm một service Rust nhỏ đảm nhận logic định tuyến tùy chỉnh trước đây nằm trong middleware. Tuần đầu gặp ba sự cố, không phải do Rust mà do mô hình cấu hình tường minh của Envoy khác hẳn cách Traefik &amp;ldquo;làm phép&amp;rdquo; qua annotation. Sau khoảng ba tuần, lớp ingress giảm từ 3 pod Traefik khoảng 380MB RSS mỗi pod xuống 2 pod Envoy cùng lớp Rust khoảng 40MB mỗi pod; chi phí node giảm từ khoảng 340 USD xuống 30 USD mỗi tháng, còn hóa đơn 4.200 USD còn 390 USD. Bài học không phải là &amp;ldquo;Rust thay Go&amp;rdquo;: Go vẫn phù hợp cho API, CLI và microservice thông thường, còn Rust đáng cân nhắc cho data plane hiệu năng cao. Việc chuyển đổi tốn hai kỹ sư khoảng ba tuần, nên nếu Traefik đang ổn và hóa đơn không đáng lo thì cứ giữ nguyên.&lt;/p&gt;
&lt;h2 id="persistent-multiplayer-state-without-chaos"&gt;&lt;a href="https://packagemain.tech/p/persistent-multiplayer-state-without" rel="noopener noreferrer"&gt;Persistent multiplayer state without chaos&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Julien Singler chia sẻ kiến trúc giữ trạng thái nhất quán cho một game multiplayer trực tiếp, trong đó PostgreSQL giữ dữ liệu gốc còn Redis đảm nhận các việc cần tốc độ. Ở game một người chơi, trạng thái khá đơn giản; nhưng khi nhiều người có thể cùng tác động lên một mục tiêu, các job định kỳ phải hoàn tất đúng thời điểm bất kể ai đang online, và người chơi quay lại sau vài ngày vẫn kỳ vọng tiến trình còn nguyên, thì trạng thái trở thành bài toán khó nhất. Giữ dữ liệu quan trọng trong bộ nhớ hay chỉ ghi xuống đĩa khi người chơi đăng xuất đều dễ làm mất tiến trình hoặc gây lỗi trong nền kinh tế game.&lt;/p&gt;
&lt;p&gt;Backend viết bằng Go, chia thành các lớp handler, service và repository. Mọi thay đổi quan trọng đều đi qua transaction PostgreSQL, là nguồn sự thật duy nhất. Dữ liệu đọc nhiều được lấy từ Redis trước, cache miss mới truy vấn PostgreSQL, và cache bị vô hiệu hóa khi ghi, trước khi trả kết quả thành công để người chơi không thấy dữ liệu cũ. Redis chỉ giữ những gì có thể xây lại như cache, trạng thái online và lock ngắn hạn: lock phân tán bằng &lt;code&gt;SetNX&lt;/code&gt; đảm bảo job định kỳ chỉ chạy một lần khi backend chạy nhiều instance. Job định kỳ dựa trên cột &lt;code&gt;finishes_at&lt;/code&gt; có index thay vì quét toàn bộ người chơi mỗi phút, còn các thay đổi theo từng người chơi dùng &lt;code&gt;SELECT ... FOR UPDATE&lt;/code&gt; trong transaction thay vì tự viết bộ quản lý lock.&lt;/p&gt;
&lt;h2 id="we"&gt;&lt;a href="https://plc.vc/cdx" rel="noopener noreferrer"&gt;We&amp;rsquo;re in the Over-engineering Game Now&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Peter Clark kể rằng cả sự nghiệp ông tự hào vì biết cách scope dự án sao cho tốn ít công sức kỹ thuật nhất mà vẫn linh hoạt tối đa, bởi thời gian kỹ sư là tài nguyên quý nhất ở một công ty phần mềm. Nhưng ông cũng mê thiết kế sản phẩm và các chi tiết trau chuốt, nên luôn giằng co giữa mong muốn làm kỹ hơn và nỗi lo lãng phí nguồn lực. Giờ đây, khi dùng Claude và Codex cho các dự án cá nhân, ông thoải mái &amp;ldquo;over-engineer&amp;rdquo;: blog của ông có changelog động, một &amp;ldquo;máy đo cảm xúc&amp;rdquo;, một MCP tự tạo bài tổng kết hằng tuần, cùng hàng trăm ý tưởng khác mà ông thử rồi bỏ đi.&lt;/p&gt;
&lt;p&gt;Luận điểm chính là AI coding xóa nhòa ranh giới giữa over-engineering và đơn giản là hoàn thiện sản phẩm. Ứng dụng iOS của ông giờ không khởi đầu như một MVP trống trải mà đã đầy đủ tính năng, có sẵn vòng phản hồi kích hoạt các agentic workflow. Những cải tiến từng nằm mãi trong backlog vì chưa bao giờ đủ ưu tiên, hoặc quá nhỏ để được ghi lại, giờ có thể được lặp và trau chuốt liên tục. Ông cho rằng thay đổi này sẽ nâng kỳ vọng của người dùng: khi làm phần mềm tốt gần như miễn phí, những sản phẩm cồng kềnh sẽ khó được chấp nhận hơn. Thay vì chỉ là thời của vibe coding hay ứng dụng cho một người dùng, ông tin chúng ta đang tiến tới giai đoạn mà tầm nhìn sản phẩm không còn bị giới hạn bởi nguồn lực, và câu hỏi duy nhất còn lại là: bạn thật sự muốn xây gì?&lt;/p&gt;
&lt;h2 id="package-managers-that-package-package-managers"&gt;&lt;a href="https://nesbitt.io/2026/05/28/package-managers-that-package-package-managers.html" rel="noopener noreferrer"&gt;Package managers that package package managers&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Andrew Nesbitt xây một ma trận xem package manager nào có thể cài package manager nào khác. Ý tưởng bắt nguồn từ một vòng lặp kỳ lạ: PyPI có gói chứa Node binary, còn npm có gói chứa CPython portable, nên &lt;code&gt;pip install&lt;/code&gt; và &lt;code&gt;npm install&lt;/code&gt; có thể chuyền quyền điều khiển qua lại mãi. Ma trận bao phủ 42 client, lấy dữ liệu từ ecosyste.ms cho các registry ngôn ngữ và Repology cho các bản phân phối Linux. Các hàng dày đặc nhất là system package manager như AUR (chứa 40/42), nixpkgs, Homebrew, DNF và Debian, vì đóng gói binary tùy ý vốn là nhiệm vụ của chúng; nhưng khi làm cột thì gần như trống, bởi chẳng ai cần phân phối lại &lt;code&gt;apt&lt;/code&gt; hay DNF khi chúng đã đi kèm hệ điều hành. Conda nằm giữa hai nhóm, còn PyPI là registry ngôn ngữ đóng gói nhiều nhất vì khá nhiều công cụ đa ngôn ngữ như Conan hay meson được viết bằng Python.&lt;/p&gt;
&lt;p&gt;Bài viết cũng nêu những hệ quả đáng chú ý cho chuỗi cung ứng phần mềm. 25 trên 42 công cụ tự phát hành chính mình qua registry của mình. Một CVE của pip có thể được ghi dưới &lt;code&gt;pkg:pypi/pip&lt;/code&gt; hay &lt;code&gt;pkg:deb/python3-pip&lt;/code&gt;, nhưng các bản pip trong Homebrew, conda-forge, nixpkgs hay Spack cũng mang cùng lỗi và mỗi bên phải tự ánh xạ advisory, nên truy vết lỗ hổng về đúng upstream khó hơn nhiều. Tìm package theo tên cũng không đáng tin, vì các tên ngắn trên registry không gian tên phẳng thường đã bị dự án không liên quan chiếm, và liên kết ngược tới repository nguồn cho kết quả sạch hơn. Tác giả còn dựng một chuỗi cài đặt dài 14 bước, từ máy Arch tới trình biên dịch Elm.&lt;/p&gt;
&lt;h2 id="beyond-the-prompt-claude-code"&gt;&lt;a href="https://arps18.github.io/posts/claude-code-mastery/" rel="noopener noreferrer"&gt;Beyond the Prompt: Claude Code&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Arpan Patel viết một hướng dẫn dài về cách dùng Claude Code như một agent có quy trình thay vì một chatbot chỉ nhận prompt. Trọng tâm là tạo vòng phản hồi để Claude tự kiểm chứng công việc: đọc mã, lập kế hoạch, sửa, chạy kiểm thử, review, rồi cập nhật lại quy tắc mỗi khi mắc sai lầm. Tác giả khuyến khích tìm hiểu trước khi viết mã, dùng plan mode cho thay đổi trên nhiều file, trỏ thẳng tới file hoặc log thay vì mô tả mơ hồ, và giao việc như giao cho một kỹ sư thay vì điều khiển từng bước nhỏ.&lt;/p&gt;
&lt;p&gt;Phần đáng chú ý là cách tổ chức thư mục &lt;code&gt;.claude&lt;/code&gt;: &lt;code&gt;CLAUDE.md&lt;/code&gt; chứa quy tắc chung của dự án, &lt;code&gt;CLAUDE.local.md&lt;/code&gt; cho ghi chú cá nhân, cùng settings, MCP, skills, commands, rules và subagents. &lt;code&gt;CLAUDE.md&lt;/code&gt; nên ngắn gọn như một lan can bảo vệ, chỉ chứa những quy tắc thật sự giúp tránh lỗi, không phải tài liệu mô tả toàn bộ codebase; các quy trình lặp lại nên được chuyển thành skill, command hoặc subagent để dùng lại trong repo. Với nhóm lớn hơn, plugin, MCP và subagent giúp tách ngữ cảnh, review độc lập và chạy nhiều luồng song song, và chúng phát huy tác dụng nhất khi trở thành một phần của quy trình onboarding chung thay vì chỉ là tiện ích cá nhân.&lt;/p&gt;
&lt;h2 id="how"&gt;&lt;a href="https://performance.dev/how-is-linear-so-fast-a-technical-breakdown" rel="noopener noreferrer"&gt;How&amp;rsquo;s Linear so fast? A technical breakdown&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Dennis Brotzky phân tích vì sao Linear tạo cảm giác rất nhanh dù vẫn là một ứng dụng web chạy phía client. Ý chính là Linear không đặt độ trễ mạng trên đường tương tác chính. Dữ liệu mà giao diện đọc đã có sẵn trong trình duyệt, chủ yếu qua IndexedDB và một object pool trong bộ nhớ; thay đổi được áp dụng cục bộ trước, ghi vào hàng đợi giao dịch bền vững rồi mới đồng bộ lên server. Server đóng vai trò xác nhận và gửi lại các delta, chứ không phải thứ giao diện phải chờ trước khi phản hồi người dùng. Ở runtime, MobX cho phép cập nhật rất hạt mịn: mỗi delta chỉ render lại đúng trường và component liên quan thay vì cả danh sách.&lt;/p&gt;
&lt;p&gt;Bài viết cũng đi sâu vào lần tải đầu tiên, nơi tốc độ đến từ nhiều chi tiết nhỏ: giảm lượng JavaScript và CSS, chỉ hỗ trợ trình duyệt hiện đại, chia nhỏ mã mạnh tay, tải trước module để tránh các lượt tải nối tiếp nhau, cache qua service worker, tải trước font với thuộc tính &lt;code&gt;crossorigin&lt;/code&gt; đúng, nhúng sẵn app shell và hiển thị dữ liệu cục bộ trước khi quá trình xác thực chạy nền hoàn tất. Ngoài kỹ thuật, tốc độ còn đến từ thiết kế sản phẩm: mọi thao tác đều có phím tắt, command palette luôn cách một phím bấm và chạy trên dữ liệu cục bộ, còn animation ngắn gọn và chỉ dùng các thuộc tính rẻ như &lt;code&gt;transform&lt;/code&gt; và &lt;code&gt;opacity&lt;/code&gt;. Hiệu năng vì thế vừa là chuyện kỹ thuật, vừa là cảm nhận của người dùng.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Bài viết đã được viết lại bởi Claude Code với Opus 5.5 vào ngày 27/09/2026.&lt;/em&gt;&lt;/p&gt;</description></item></channel></rss>