Bạn đã bao giờ tự hỏi làm thế nào Reddit, Hacker News hay Facebook xử lý hàng nghìn bình luận trong một bài viết, với các tầng reply (trả lời) lồng nhau chằng chịt?
Ví dụ: User A comment, User B trả lời A, User C trả lời B... tạo thành một nhánh cây (thread) sâu hun hút.
Khi thiết kế Backend cho tính năng này, thách thức không chỉ là lưu trữ, mà là **hiệu năng**: Làm sao để lấy ra toàn bộ hội thoại (thread) chỉ với 1 câu query? Làm sao để đếm tổng số reply của một comment cha?
Dưới đây là 4 mô hình thiết kế Database phổ biến nhất để giải quyết bài toán dữ liệu phân cấp (Ancestry) cho hệ thống Comment.
Mình từng xây dựng một tính năng bình luận cho mạng xã hội hoặc diễn đàn bằng Ruby on Rails, bài toán khó nhằn nhất không phải là "lưu comment thế nào", mà là "làm sao để hiển thị chúng dưới dạng cây (nested/threaded) một cách hiệu quả?".
Bạn muốn một cấu trúc giống Reddit hay Hacker News:
A: Bài viết hay quá!
B: Hay chỗ nào thế? (Trả lời A)
C: Logic database chuẩn chỉ. (Trả lời B)
D: Đồng ý. (Trả lời A)
1. Adjacency List (Danh sách kề) - Giải pháp "Mì ăn liền"

Đây là cách đơn giản nhất: Mỗi comment sẽ lưu ID của comment mà nó đang trả lời (`parent_id`).
CREATE TABLE comments (
id BIGINT PRIMARY KEY,
post_id BIGINT,
content TEXT,
parent_id BIGINT NULL -- NULL nếu là comment gốc, có giá trị nếu là reply
);
Ứng dụng thực tế
- Phù hợp: Các hệ thống comment phẳng (flat) hoặc ít cấp (như YouTube hoặc Facebook cũ - chỉ có 1 cấp reply).
- Vấn đề: Để hiển thị dạng cây như Reddit (indentation), bạn phải dùng đệ quy (Recursive CTE) trong SQL hoặc load hết về Code rồi tự xây cây. Nếu thread quá sâu, query sẽ rất chậm.
2. Path Enumeration (Lưu đường dẫn) - Giải pháp của "Dân chuyên"

Hãy tưởng tượng mỗi comment có một địa chỉ nhà kiểu: `1/5/12`.
- `1`: Comment gốc.
- `5`: Reply của 1.
- `12`: Reply của 5.
Chúng ta lưu chuỗi này vào cột `path`.
Ưu điểm
- Sort cực dễ: Bạn có thể sort theo cột `path`, dữ liệu lấy ra sẽ tự động sắp xếp theo đúng thứ tự hội thoại (thread).
- Query nhanh: Muốn lấy toàn bộ hội thoại của comment 1? `SELECT * FROM comments WHERE path LIKE '1/%'`.
Nhược điểm
- Giới hạn độ sâu: Trường `VARCHAR` có giới hạn. Nếu hai người cãi nhau quá hăng say (reply 1000 cấp), chuỗi path sẽ bị tràn.
3. Nested Sets (Tập hợp lồng nhau) - "Cái bẫy" cho hệ thống Comment

Cảnh báo: Đây là mô hình cực hay cho danh mục sản phẩm, nhưng là **ác mộng** cho hệ thống Comment thời gian thực.
Mô hình này dùng 2 chỉ số `Left` và `Right` để xác định phạm vi. Comment con nằm trong khoảng giá trị của Comment cha.
Tại sao KHÔNG nên dùng cho Comment?
- Write cực chậm: Mỗi khi có ai đó post 1 reply mới, bạn phải tính toán và update lại chỉ số `Left/Right` của hàng nghìn comment khác để "lấy chỗ" chèn vào.
- Với mạng xã hội nơi tốc độ "Ghi" (Insert) là ưu tiên hàng đầu, Nested Sets sẽ làm khóa bảng (table locking) và gây nghẽn mạng.
> Lời khuyên: Chỉ dùng Nested Sets nếu comment của bạn là dạng "Lưu trữ/Archived" và không cho phép thêm mới liên tục.
4. Closure Table (Bảng quan hệ riêng) - "Trùm cuối" linh hoạt

Thay vì cố nhét quan hệ cha-con vào bảng `comments`, ta tách hẳn ra một bảng riêng tên là `comment_paths`. Bảng này lưu **mọi** mối quan hệ giữa các comment (không chỉ cha-con trực tiếp mà cả ông-cháu, cụ-chắt).
Cấu trúc
Bảng comments(categories): Chỉ lưu nội dung.
Bảng **_paths:
Ưu điểm
- Query siêu mạnh: "Lấy tất cả reply của comment 1 bất kể bao nhiêu cấp": SELECT * FROM comment_paths WHERE ancestor = 1.
- Dễ dàng xóa/di chuyển: Xóa một nhánh hội thoại rất sạch sẽ.
Nhược điểm
- Tốn dung lượng: Bảng comment_paths sẽ phình to rất nhanh (cấp số mũ). Với hàng triệu comment, bảng này có thể lên tới hàng tỷ dòng.
Mẹo nhỏ cho người dùng PostgreSQL:
Nếu bạn dùng Postgres, hãy tìm hiểu kiểu dữ liệu LTREE. Đây là phiên bản "độ" của Path Enumeration, hỗ trợ đánh index cực mạnh cho các truy vấn dạng cây, là lựa chọn số 1 cho các hệ thống comment hiện đại.
---
Thay vì tự viết SQL đệ quy phức tạp, cộng đồng Rails có một vũ khí kha mạnh: Gem `ancestry`