Vì sao lấy “10 Bình Luận Mới Nhất” cũng có thể giết MySQL nếu không có Object Cache?

Khi site đạt tới hàng triệu bình luận, truy vấn “10 bình luận mới nhất” vẫn có thể quét hàng triệu dòng để sắp xếp và lọc theo trạng thái. Hệ quả: CPU/MySQL quá tải, TTFB đội lên, toàn site chậm. Bài này chỉ ra vì sao nó chậm và cách khắc phục thực dụng: bọc get_comments() bằng persistent Object Cache (Redis/Memcached), chuẩn hoá tham số, và — quan trọng — chỉ purge cache khi xóa bình luận (không purge ở mỗi sự kiện), nhằm duy trì tỉ lệ cache hit cao trên hệ lớn.

Vì sao lấy “10 Bình Luận Mới Nhất” cũng có thể giết MySQL nếu không có Object Cache?

Vì sao “10 bình luận” vẫn chậm?

# Query_time: 8.55s  Rows_sent: 10  Rows_examined: 4,655,080
SELECT wp_comments.comment_ID
FROM wp_comments
JOIN wp_posts ON wp_posts.ID = wp_comments.comment_post_ID
WHERE comment_approved = '1'
  AND comment_type IN ('', 'comment')
  AND wp_posts.post_status IN ('publish')
ORDER BY wp_comments.comment_ID DESC
LIMIT 0,10;

Nút thắt: sắp xếp DESC theo comment_ID, join với wp_posts để lọc post_status, khối dữ liệu cực lớn. Tối ưu index chỉ giúp phần nào; giải pháp “rẻ” và ổn định hơn ở quy mô lớn là giảm tần suất đụng DB bằng cache đối tượng bền vững.

Wrapper chuẩn hoá: init_html_get_comments_cached()

<?php
/**
 * Lấy danh sách bình luận có persistent cache.
 * - Phụ thuộc backend Object Cache (Redis/Memcached).
 * - Chuẩn hoá tham số để tăng tỉ lệ cache hit.
 *
 * @param array $args  Tham số tương tự get_comments().
 * @param int   $ttl   TTL (giây). Mặc định 1 giờ (tối thiểu 60s).
 * @return array<WP_Comment>|array<int>
 */
function init_html_get_comments_cached( array $args = [], int $ttl = HOUR_IN_SECONDS ) {
    $cache_group = 'init_html_comments';
    $defaults = [
        'number'                     => 10,
        'status'                     => 'approve',
        'type'                       => 'comment',   // bỏ pingback/trackback
        'orderby'                    => 'comment_ID',
        'order'                      => 'DESC',
        'update_comment_meta_cache'  => false,
        'update_comment_post_cache'  => false,
        // Nếu chỉ cần ID:
        // 'fields'                  => 'ids',
    ];
    $args = wp_parse_args( $args, $defaults );

    // Khóa cache ổn định theo tham số
    $raw_key   = 'gc_' . md5( wp_json_encode( $args ) );
    $cache_key = init_html_cache_key_with_version( $raw_key ); // bọc version-key để purge chọn lọc

    // Đọc cache
    $cached = wp_cache_get( $cache_key, $cache_group );
    if ( false !== $cached ) {
        return $cached;
    }

    // Gọi query gốc
    $comments = get_comments( $args );

    // Ghi cache (tối thiểu 60s)
    wp_cache_set( $cache_key, $comments, $cache_group, max( 60, $ttl ) );

    return $comments;
}

Chỉ purge khi XÓA: giữ cache hit cao trên site lớn

Ở quy mô hàng triệu bình luận, purge cache “mỗi sự kiện” (duyệt/sửa/chuyển trạng thái) gần như biến cache = 0% hit. Chiến lược an toàn là chỉ purge khi xóa bình luận (hard delete) hoặc khi đưa vào thùng rác (soft delete) nếu UI của bạn ẩn bình luận đã bị trash.

<?php
/**
 * Cơ chế version-key để purge rẻ:
 * - Nếu backend hỗ trợ flush_group: dùng thẳng.
 * - Nếu không: tăng "ver" để vô hiệu hoá toàn bộ key cũ mà không cần enumerate.
 */
function init_html_flush_comment_group_cache_on_delete() {
    if ( function_exists('wp_cache_supports') && wp_cache_supports('flush_group') ) {
        wp_cache_flush_group('init_html_comments');
        return;
    }
    $v = (int) wp_cache_get('ver', 'init_html_comments');
    wp_cache_set('ver', $v + 1, 'init_html_comments');
}

/** Bọc cache key với version hiện tại */
function init_html_cache_key_with_version( string $raw_key ) : string {
    $v = (int) wp_cache_get('ver', 'init_html_comments');
    return $raw_key . ':v' . $v;
}

/**
 * CHỈ purge khi xóa:
 * - deleted_comment: xóa vĩnh viễn.
 * - trashed_comment: nếu business coi bình luận trong thùng rác là "không còn hiển thị", thì purge luôn.
 *   (Nếu UI vẫn hiển thị, có thể bỏ hook này để cache hit cao hơn.)
 */
add_action( 'deleted_comment', 'init_html_flush_comment_group_cache_on_delete', 10 );
add_action( 'trashed_comment', 'init_html_flush_comment_group_cache_on_delete', 10 );

// LƯU Ý: Không purge ở comment_post, edit_comment, transition_comment_status, untrashed_comment...
// để tránh "thổi bay" cache liên tục trên site có traffic lớn.

API tiện dụng: “bình luận mới nhất đã duyệt & bài viết publish”

<?php
function init_html_get_recent_approved_comments( int $limit = 10, int $ttl = HOUR_IN_SECONDS ) {
    $args = [
        'number'        => max(1, $limit),
        'status'        => 'approve',
        'type'          => 'comment',
        'orderby'       => 'comment_ID',
        'order'         => 'DESC',
        'post_status'   => 'publish',
        'update_comment_meta_cache' => false,
        'update_comment_post_cache' => false,
    ];
    return init_html_get_comments_cached( $args, $ttl );
}

Lưu ý thực tế: Bỏ post_status nếu CSDL đã quá lớn

Ở site có hàng triệu bình luận, việc JOIN wp_posts để lọc post_status = 'publish' là nguyên nhân chính khiến MySQL phải quét hàng triệu dòng như ví dụ trên. Trong đa số trường hợp thực tế, bình luận trong bài chưa xuất bản (draft/private) gần như không hiển thị ra frontend — vì vậy việc lọc trạng thái bài viết ở tầng SQL là không cần thiết.

Nếu bạn không có nhu cầu đặc biệt, hoặc cơ sở dữ liệu đã phình quá lớn, hãy bỏ tham số post_status ra khỏi truy vấn. Điều này giúp MySQL chỉ cần đọc thẳng bảng wp_comments (sử dụng chỉ mục PRIMARY(comment_ID)), giảm thời gian truy vấn từ vài giây xuống vài mili-giây, đặc biệt khi kết hợp với cache đối tượng bền vững.

// Thay vì:
$comments = get_comments([
    'number'      => 10,
    'status'      => 'approve',
    'post_status' => 'publish', // Gây JOIN nặng

// Hãy dùng:
$comments = get_comments([
    'number' => 10,
    'status' => 'approve',
]); // Không JOIN, cực nhanh

Chấp nhận đánh đổi nhỏ này (bỏ lọc post_status) gần như không ảnh hưởng đến trải nghiệm người dùng, nhưng đem lại lợi ích hiệu năng cực lớn khi database vượt vài triệu bản ghi.

Tối ưu tham số để giảm tải

  • fields => 'ids' nếu bạn chỉ cần ID cho lớp render phía sau.
  • update_comment_meta_cache => false, update_comment_post_cache => false để tránh preloading thừa.
  • type => 'comment' bỏ pingback/trackback.
  • Giữ orderby => 'comment_ID', order => 'DESC' cho luồng “mới nhất trước”.

Gợi ý index (cân nhắc trước khi áp dụng)

  • wp_comments (comment_approved, comment_type, comment_ID) hoặc (comment_approved, comment_type, comment_date_gmt)
  • wp_comments (comment_post_ID, comment_approved) nếu thường lọc theo từng bài viết
  • wp_posts (ID, post_status) hoặc đơn giản là (post_status) tuỳ pattern

Ví dụ tích hợp render

<?php
$comments = init_html_get_recent_approved_comments( 10, 10 * MINUTE_IN_SECONDS );

if ( $comments ) {
    echo '<ul class="recent-comments">';
    foreach ( $comments as $c ) {
        $link    = get_comment_link( $c );
        $author  = get_comment_author( $c );
        $excerpt = wp_html_excerpt( apply_filters('get_comment_text', $c->comment_content, $c), 80, '&hellip;' );
        printf(
            '<li><a href="%s">%s</a>: %s</li>',
            esc_url( $link ),
            esc_html( $author ),
            esc_html( $excerpt )
        );
    }
    echo '</ul>';
}

Checklist triển khai thực dụng

  • Bật persistent Object Cache (Redis/Memcached) qua plugin như “Redis Object Cache”.
  • Dùng init_html_get_comments_cached() ở mọi block truy vấn bình luận lặp đi lặp lại.
  • Chỉ purge khi xóa (hard delete; optional: trash) để giữ tỉ lệ cache hit cao.
  • Cân nhắc fields => 'ids' + nạp chi tiết theo nhu cầu để giảm payload.
  • TTL tối thiểu 60s; tăng TTL cho block ít quan trọng.

Kết luận

Truy vấn “10 bình luận mới nhất” có thể gây áp lực cực lớn lên MySQL ở site nhiều dữ liệu. Bọc bằng persistent Object Cache và không purge bừa bãi (chỉ purge khi xóa) sẽ giữ cache hit ổn định, giảm TTFB rõ rệt, và giúp toàn site mượt hơn mà không phải “đập” lại kiến trúc.

Bình luận


  • Không có bình luận.

Init Toolbox

Nhấn Ctrl + \ trên máy tính, hoặc vuốt sang trái ở bất kỳ đâu trên mobile.

Đăng nhập





Đang tải...