Newsletter #121

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

How 2004 RuneScape fit a multiplayer RPG into 56k dial-up

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.

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.

How does struct{} take zero bytes in Go

Bài viết giải thích vì sao struct{} 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 unsafe.Sizeof, ta thấy giá trị rỗng này không chiếm byte nào; khi làm value trong map[string]struct{}, ý 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 map[string]struct{} và map[string]bool gần như không còn chênh lệch bộ nhớ đáng kể; chọn struct{} lúc này chủ yếu để diễn đạt ý định.

Ở tầng runtime, khi một giá trị zero-size phải cấp phát trên heap, mallocgc kiểm tra kích thước bằng 0 và trả về địa chỉ của biến toàn cục zerobase 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ụ, fmt.Println có thể khiến giá trị escape, còn println 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 struct{} 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 struct{} 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.

Is Cloudflare silently killing the web hosting industry?

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à “mua gói hosting nào” mà là “triển khai ở đâu”, 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.

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.

Don’t Stack Weaknesses

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.

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à “phù hợp giá trị” hay “phù hợp văn hóa”, 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 “tôi có thể làm nếu muốn”, 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.

System Design Cheatsheet

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.

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ệ.

SQLite improving performance with pre-sort

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ừ SecureRandom, 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 WITHOUT ROWID có BLOB PRIMARY KEY, 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.

Ý 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 memcmp(), 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.

Why is Meta destroying its engineering organization?

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 “đi nhanh” 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.

Điểm đáng chú ý không chỉ là “Meta dùng AI quá nhiều”, 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.

CS 6120: Advanced Compilers: The Self-Guided Online Course

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.

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.

epoll vs io_uring in Linux

Bài viết so sánh epoll và io_uring 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 epoll và nhanh hơn nhiều, nhưng vẫn phải trả giá cho mô hình sẵn sàng: epoll chỉ báo khi mô tả tệp có thể đọc/ghi, còn ứng dụng vẫn phải tự gọi read() hoặc write(). 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ý epoll_ctl, 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ể.

io_uring đổ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 io_uring_enter() có thể nộp và nhận cả lô thao tác thay vì trả phí cho từng read()/write(). Bật IORING_SETUP_SQPOLL 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 io_uring, 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.


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.

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