Skip to main content

9 posts tagged with "Backend"

The foundations of a backend that runs for real: data, transactions, performance and operations.

View All Tags

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 min read
Nguyễn Huỳnh Minh Tiến
Middle Fullstack Developer @ Utop.vn
Summary

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.

Luỹ kế của bạn sai ngay dòng đầu: window function, RANGE và cái mặc định ít ai đọc

· 12 min read
Nguyễn Huỳnh Minh Tiến
Middle Fullstack Developer @ Utop.vn
Summary

GROUP BY gom dòng lại, window function giữ nguyên dòng mà vẫn tính được số tổng của nhóm — đo thật: cùng một bảng 200.000 dòng, GROUP BY trả về 200 dòng, window trả về 200.000 dòng. Nó cũng nhanh hơn cách cũ: thay self-join bằng window đưa thời gian từ 79,2 ms xuống 22,7 ms. Nhưng có một mặc định gây sai số liệu mà rất ít người đọc tới: OVER (ORDER BY ...) dùng khung RANGE, nên mọi dòng đồng hạng đều nhận cùng một giá trị luỹ kế. Muốn cộng dồn từng dòng một thì phải ghi rõ ROWS.

Window function là thứ biến những câu truy vấn báo cáo dài dòng thành vài dòng đọc được. Nhưng nó cũng có đúng một cái bẫy đủ tinh vi để lọt qua review: bảng luỹ kế trông hợp lý ở giữa và sai ở chỗ có giá trị trùng nhau.

Bài này đào sâu bài Recursive Queries và Window Functions trong series học SQL 30 ngày. Mọi con số là kết quả chạy thật trên PostgreSQL 16.11.

MailKit trong .NET: gửi email SMTP và dựng HTML email template chạy đúng trên Outlook

· 14 min read
Nguyễn Huỳnh Minh Tiến
Middle Fullstack Developer @ Utop.vn
Summary

MailKit là thư viện mail mã nguồn mở cho .NET (giấy phép MIT, tác giả Jeffrey Stedfast), nói được cả SMTP, IMAP và POP3. Tài liệu chính thức của Microsoft về System.Net.Mail.SmtpClient khuyến nghị thẳng việc dùng MailKit thay thế. Phần khó của việc gửi email không nằm ở chỗ gọi SendAsync — nó nằm ở cái HTML bên trong: Outlook classic render bằng engine của Word nên không có flex hay grid, Gmail cắt thư dài quá 102KB, và một thư chỉ có HTML mà thiếu bản plain-text thì điểm spam tăng ngay.

Gửi được email và gửi được email hiển thị đúng là hai bài toán khác nhau. Bài này giải quyết cả hai: phần đầu là cơ chế MailKit, phần sau là những ràng buộc rất cũ kỹ của HTML email mà không đọc trước thì sẽ mất buổi chiều ngồi hỏi vì sao cái template đẹp trên Chrome lại vỡ tan trên Outlook.

Hai giao dịch cùng cộng 100, số dư chỉ tăng 100: isolation level qua thí nghiệm thật

· 13 min read
Nguyễn Huỳnh Minh Tiến
Middle Fullstack Developer @ Utop.vn
Summary

Isolation level quyết định một giao dịch nhìn thấy gì khi có giao dịch khác chạy song song. Ở mức mặc định của hầu hết database là READ COMMITTED, hai phiên cùng đọc số dư 1000 rồi cùng ghi 1100 sẽ cho kết quả cuối là 1100 — một lần cộng biến mất, không có lỗi nào được ném. Nâng lên REPEATABLE READ thì phiên thứ hai nhận ERROR: could not serialize access due to concurrent update: câu trả lời sai âm thầm biến thành một lỗi rõ ràng mà ứng dụng phải thử lại. Toàn bộ số liệu dưới đây đo bằng hai session psql song song trên PostgreSQL 16.11.

Đây là loại lỗi gần như không thể tái hiện trên máy dev, vì ở đó bạn chỉ có một người dùng. Nó chỉ xuất hiện khi hai request thật chạm vào cùng một dòng trong cùng một khoảnh khắc — và lúc đó nó không sập, không log, chỉ làm sai số liệu.

Bài này đào sâu bài Transactions và ACID trong series học SQL 30 ngày.

Index Exists but the Query Still Scans the Whole Table? Sargability and One Function Call

· 12 min read
Nguyễn Huỳnh Minh Tiến
Middle Fullstack Developer @ Utop.vn
Summary

An index is a structure sorted by the column's values. Wrap a function around the column — date_part('year', created_at), LOWER(email), CAST(...) — and the thing you are comparing is no longer the sorted value, so the database cannot use the index and falls back to scanning the whole table. A condition that can use an index is called sargable. Measured on PostgreSQL 16 with 200,000 rows: the function-wrapped version runs in 29.6 ms with a Seq Scan, the range-rewritten version runs in 5.4 ms with a Bitmap Index Scan — about 5.4× faster, returning the same 37,518 rows.

A familiar situation: the query is slow, you add an index on exactly the column being filtered, you run it again — still just as slow. The index is right there, \d shows it, but the execution plan never mentions it.

This post drills into the Index lesson and the query optimization lesson from my 30-day SQL series. Every number is real output from PostgreSQL 16.11.