CommentAlgorithm
Representing Geeklog's Comments in the Database
Since Geeklog 1.3.10, Geeklog's comments have been stored in the database with columns (lft, rht) representing a (modified) pre-order traversal ordering. This method of storage was chosen to improve comment retrieval performance, especially when viewing a subset of the comments for a given story.
Details
The idea behind pre-order traversal ordering, is that each node is ordered (or visited) before any of its children. In the modified preorder traversal algorithm implemented by Geeklog, the lft value of a comment is always less than the lft and rht values of all of the children of that node. Likewise the rht value of a node is always greater that the lft and rht values of all of the children of that node. (And trivially for any node, lft is always less than rht.) A node without any children has the property rht is exactly one more than lft.
Example: Comment A is a top level comment to a story. Comments A-A and A-B are children of A and comments A-A-A, A-A-B, and A-A-C are children of A-A. The table below shows the values for each comment:
comment | parent | lft | rht |
---|---|---|---|
A | N/A | 1 | 12 |
A-A | A | 2 | 9 |
A-B | A | 10 | 11 |
A-A-A | A-A | 3 | 4 |
A-A-B | A-A | 5 | 6 |
A-A-C | A-A | 7 | 8 |
The resulting database structure has many advantages. Selecting a subtree from the database is as easy as specifying the lft and rht numbers of the root node of the subtree. For instance, to get the subtree with root comment "A-A":
SELECT * FROM comments WHERE lft >= 2 AND rht <= 9
Or selecting all the children of a give node:
SELECT * FROM comments WHERE lft > 2 AND rht < 9
To select comments ordered for display in a "nested" format:
SELECT * FROM comments ORDER BY lft
Storing the preorder traversal information also allows us to easily find the path to a node (i.e. a nodes parents, grandparents, etc).
SELECT * FROM comments WHERE lft < 7 AND rht > 8
Counting the number of children of a comment is equally easy: ('rht' - 'lft' - 1)/2
Previously these operations required a (very slow) recursive algorithm. The way Geeklog had implemented this algorithm required one database query for every comment in the result set. Another way would have been to do one query to fetch all comments, and then use a recursive function to choose the desired comments. Both methods were extremely inefficient and caused load problems on frequently viewed Geeklog sites.
The downside to this method is that inserting and deleting comments takes more work (three queries instead of one) and must be done in a single transaction (or a write lock on the table). Inserts:
START TRANSACTION
UPDATE comments SET rht = rht + 2 WHERE rht >= parent_comment.rht
UPDATE comments SET lft = lft + 2 WHERE lft >= parent_comment.rht
INSERT INTO comments SET lft = parent_comment.rht, rht = parent_comment.rht + 1
COMMIT
Deletions:
START TRANSACTION
UPDATE comments SET rht = rht - 2 WHERE rht > comment.rht
UPDATE comments SET lft = lft - 2 WHERE lft > comment.rht
DELETE FROM comments WHERE id = comment.id
COMMIT
However, since fetching (reading) comments is a much more frequent action then adding or deleting comments and the increase in efficiency for fetching comments is much greater than the loss of efficiency for adding and deleting comments, the trade off is worthwhile.
Please note that in the SQL examples above, parts of the query were left out to make the algorithm more clear. For more detail, see the Geeklog source code (specifically lib-comment.php).