Chuyển tới nội dung chính

7 bài viết được gắn thẻ "Kiến trúc"

Ranh giới tầng, phụ thuộc và những quyết định thiết kế phải trả giá khi hệ thống lớn lên.

Xem tất cả thẻ

Thêm 4 index làm INSERT chậm 6 lần: cái giá không ai nhắc khi bảo bạn đánh index

· 13 phút để đọc
Nguyễn Huỳnh Minh Tiến
Fullstack Developer @ Utop.io
Tóm tắt

Index tăng tốc đọc bằng cách trả giá ở mỗi lần ghi, và cái giá đó lớn hơn hầu hết người ta hình dung. Đo trên PostgreSQL 16 với cùng 200.000 dòng: bảng không index chèn xong trong 287,8 ms và chiếm 10,2 MB; bảng có bốn index mất 1.722,3 ms và chiếm 27 MB. Tức là chậm gấp 6 lần và tốn gấp 2,6 lần dung lượng — chỉ để thêm bốn cấu trúc mà có thể chẳng câu truy vấn nào dùng tới. Câu hỏi đúng không phải "cột này có nên có index không" mà là "phần đọc tiết kiệm được có bù nổi phần ghi phải trả không".

Mọi bài viết về tối ưu database đều kết thúc bằng lời khuyên thêm index. Rất ít bài nói về hoá đơn đi kèm, và càng ít bài đưa con số.

Bài này đào sâu bài Thiết kế database và bài Index trong series học SQL 30 ngày. Mọi số liệu đo thật trên PostgreSQL 16.11.

Một dấu tiếng Việt làm chết lời gọi API: cf-ipcity, HttpClient và giới hạn ASCII

· 12 phút để đọc
Nguyễn Huỳnh Minh Tiến
Fullstack Developer @ Utop.io
Tóm tắt

Cloudflare tự chèn cf-ipcity vào request đi vào, và với khách ở Việt Nam thì giá trị là Hồ Chí Minh — có dấu, tức non-ASCII. Service .NET forward nguyên xi mọi header sang lời gọi đi ra, nên giá trị đó rơi vào HttpClient. Điều bất ngờ là Headers.Add không ném exception, TryAddWithoutValidation cũng trả về true; mọi thứ chỉ nổ ở SendAsync với HttpRequestException: Request headers must contain only ASCII characters, và không byte nào rời khỏi tiến trình. Lỗi không nằm ở Cloudflare cũng không nằm ở .NET, mà ở chỗ ứng dụng forward mọi header vô điều kiện.

Bối cảnh là một nền tảng loyalty thương mại điện tử, quy mô khoảng ba triệu khách hàng. Tôi ẩn tên khách và mọi chi tiết định danh; phần kể được là phần kỹ thuật.

Mọi thứ nhìn đều bình thường. API đang chạy. Pod trên Kubernetes healthy. Database ổn. Request vào tới ứng dụng. Nhưng một lời gọi HTTP sang service nội bộ cứ hỏng trên production — và chỉ trên production.

Thứ làm nó hỏng, hoá ra, là tên thành phố của chính người dùng.

Forward header trong ASP.NET Core: vì sao 'forward hết' là một lỗi kiến trúc

· 13 phút để đọc
Nguyễn Huỳnh Minh Tiến
Fullstack Developer @ Utop.io
Tóm tắt

Vòng lặp copy mọi header từ request đi vào sang request đi ra là một anti-pattern, và nó hỏng theo ba hướng khác nhau: sai về tính đúng đắn (một giá trị non-ASCII làm HttpClient ném exception), sai về bảo mật (bạn tin và chuyển tiếp dữ liệu do client tự đặt), và sai về kiến trúc (header do hạ tầng chèn trở thành một phần hợp đồng của ứng dụng). Cách sửa là allowlist — khai tường minh danh sách header được phép đi qua — đặt trong một DelegatingHandler dùng chung. Khi viết handler đó, cẩn thận một cái bẫy: nó không sống trong scope của request.

Bài này khép lại cụm ba bài bắt đầu từ một sự cố production trên nền tảng loyalty khoảng ba triệu khách hàng, nơi header cf-ipcity: Hồ Chí Minh do Cloudflare chèn làm hỏng một lời gọi nội bộ. Bài đầu kể sự cố, bài hai giải thích vì sao header không mang được tiếng Việt. Bài này trả lời câu hỏi còn lại: viết lại chỗ đó thế nào cho đúng.

Traefik + Cloudflare: vì sao cert hết hạn đồng loạt sau 60 ngày?

· 19 phút để đọc
Nguyễn Huỳnh Minh Tiến
Fullstack Developer @ Utop.io
Tóm tắt

Khi domain bật proxy Cloudflare (mây cam) với SSL mode Full (strict), ACME HTTP-01 không dùng được: Let's Encrypt gọi http://domain/.well-known/acme-challenge/... ở cổng 80, Cloudflare nhận rồi gọi ngược về origin bằng HTTPS:443 — nơi bộ xử lý challenge của Traefik không có mặt. Bạn cần cert để qua được Cloudflare, mà cần qua Cloudflare mới lấy được cert. Lách lần đầu bằng cách tạm tắt mây cam thì được, nhưng lần gia hạn tự động sau ~60 ngày sẽ trượt âm thầm và mọi site hết hạn cùng lúc. Lối ra là DNS-01: xác thực bằng bản ghi TXT qua API Cloudflare, không request nào chạm origin. Đổi lại, cert chỉ cấp được cho những zone mà API token nhìn thấy.

Trên VPS này đang có 14 container chung một reverse proxy: 10 site WordPress, một API .NET, một app Angular, một web app sự kiện, và Traefik. Mười ba hostname, tất cả nằm sau Cloudflare, tất cả cần HTTPS và không ai muốn gia hạn cert bằng tay.

Phần kiến trúc thì đơn giản đến mức nhàm. Phần thú vị nằm ở chỗ Cloudflare và Let's Encrypt, mỗi bên làm đúng việc của mình, nhưng ghép lại thì tạo ra một vòng luẩn quẩn mà bạn chỉ phát hiện vào ngày cert hết hạn — tức là hai tháng sau khi mọi thứ trông có vẻ đã xong.

Unit of Work trong .NET và ABP: SaveChangesAsync không phải commit

· 12 phút để đọc
Nguyễn Huỳnh Minh Tiến
Fullstack Developer @ Utop.io
Tóm tắt

DbContext của EF Core đã là một Unit of Work sẵn: nó gom thay đổi trong change tracker rồi đẩy xuống database trong một transaction khi gọi SaveChanges. Vì vậy tự viết thêm một IUnitOfWork chỉ để gọi SaveChanges thường là thừa. ABP thì khác hẳn: Unit of Work ở đây là ambient, tự mở theo mỗi request, và SaveChangesAsync bên trong một UoW không commit transaction — chỉ CompleteAsync mới commit. Hiểu sai chỗ này là nguồn gốc của phần lớn bug "dữ liệu lúc có lúc không".

Unit of Work là một trong những pattern bị viết lại nhiều nhất trong thế giới .NET, và cũng là pattern bị viết lại một cách thừa thãi nhiều nhất. Bài này tách làm hai phần: EF Core thuần thì bạn cần gì, và ABP đã làm sẵn những gì mà bạn nên hiểu trước khi đụng vào.