Tại sao bạn không nên dùng wp_cache_flush_group() — và cách purge cache đúng chuẩn trong WordPress

Nếu bạn đang dùng wp_cache_flush_group() để xóa cache sau mỗi sự kiện như thêm chương mới, đăng bình luận, hay cập nhật dữ liệu — hãy đọc bài này trước khi production site của bạn crash lần tiếp theo.

Tại sao bạn không nên dùng wp_cache_flush_group() — và cách purge cache đúng chuẩn trong WordPress

Vấn đề thực tế với wp_cache_flush_group()

Trên bề mặt, wp_cache_flush_group() trông rất tiện: một lệnh duy nhất xóa sạch toàn bộ cache của một group. Nhưng vấn đề nằm ở cách nó hoạt động bên dưới, đặc biệt khi bạn dùng Redis làm object cache backend.

Với Redis, wp_cache_flush_group() thực chất thực hiện lệnh SCAN để tìm tất cả key theo pattern của group đó — đây là thao tác O(n) trên toàn bộ keyspace của Redis. Nếu site bạn có hàng chục nghìn key, lệnh SCAN sẽ block Redis trong nhiều giây, thậm chí gây timeout hoàn toàn.

Kết quả thực tế đã gặp: sau khi user đăng bình luận, site gọi wp_cache_flush_group('init_recent_comments') — Redis crash, trang trắng, xử lý mất gần 6 giây. Sau khi thay bằng wp_cache_delete() đúng key: phản hồi tức thì, dưới 1ms.

Tại sao flush_group lại nguy hiểm?

Ba lý do chính:

1. Redis SCAN không phải atomic. Nó lặp qua keyspace theo từng batch, có thể bỏ sót key mới được thêm trong lúc scan, hoặc xóa nhầm key của group khác nếu naming convention không chặt.

2. Không phải backend nào cũng hỗ trợ. File cache, APCu, và nhiều custom backend không implement flush_group đúng nghĩa. Kết quả là cache không được xóa mà bạn không hay biết — stale data tồn tại mãi.

3. Xóa thừa, tốn tài nguyên. Flush cả group đồng nghĩa với việc xóa luôn những cache key không cần thiết phải xóa, buộc hệ thống phải rebuild toàn bộ — tăng tải DB không đáng có.

Giải pháp 1: Purge đúng key — đơn giản, hiệu quả ngay

Nếu bạn biết chính xác cache key nào cần xóa, hãy xóa thẳng nó. Không cần flush group, không cần scan, không cần lo Redis crash.

Ví dụ thực tế: widget hiển thị bình luận gần đây với number=10:

add_action( 'wp_insert_comment', function( $comment_id, $comment ) {
    if ( ! in_array( $comment->comment_approved, [ '1', 'approve' ], true ) ) {
        return;
    }

    $args = [
        'number' => 10,
        'status' => 'approve',
        'type'   => 'comment',
        'offset' => 0,
    ];
    $args = apply_filters( 'init_html_recent_comments_query_args', $args );
    $key  = 'irc_' . md5( maybe_serialize( $args ) );

    wp_cache_delete( $key, 'init_recent_comments' );
}, 10, 2 );

Cách này hoàn toàn phù hợp khi cache key của bạn có thể dự đoán được — ví dụ chỉ có một vài biến như number, paged, limit.

Giải pháp 2: Registry Pattern — track key tự động, purge chính xác

Khi cache key phức tạp và không thể đoán trước — ví dụ key chứa chapter number, user ID, hay nhiều tham số động — bạn cần một cơ chế tự động track tất cả key đã được set, để khi purge có thể xóa đúng và đủ.

Ý tưởng: mỗi khi wp_cache_set() được gọi, đồng thời lưu cache key đó vào một “registry key” trong cùng group. Khi cần purge, đọc registry ra, xóa từng key, xóa luôn registry.

Hàm track key

/**
 * Track cache key vào registry của manga.
 *
 * @param int    $manga_id
 * @param string $key
 */
function init_html_register_chapter_cache_key( int $manga_id, string $key ): void {
    $group        = "init_html_chapters_{$manga_id}";
    $registry_key = "registry_{$manga_id}";

    $registry = wp_cache_get( $registry_key, $group ) ?: [];

    if ( ! isset( $registry[ $key ] ) ) {
        $registry[ $key ] = true;
        wp_cache_set( $registry_key, $registry, $group, 0 ); // TTL = 0: không expire
    }
}

Hàm purge cache

/**
 * Xóa toàn bộ cache chương của một manga dựa theo registry.
 * Không dùng wp_cache_flush_group() — an toàn với mọi backend.
 *
 * @param int $manga_id
 */
function init_html_clear_chapter_cache( int $manga_id ): void {
    $group        = "init_html_chapters_{$manga_id}";
    $registry_key = "registry_{$manga_id}";

    $registry = wp_cache_get( $registry_key, $group );

    if ( ! empty( $registry ) ) {
        foreach ( array_keys( $registry ) as $key ) {
            wp_cache_delete( $key, $group );
        }
        wp_cache_delete( $registry_key, $group );
    }
}

Cách tích hợp vào hàm cache

Sau mỗi wp_cache_set(), thêm một dòng gọi init_html_register_chapter_cache_key():

// Ví dụ trong hàm lấy danh sách chương
function init_html_get_manga_chapters( int $manga_id, int $limit = 2 ): array {
    $group = "init_html_chapters_{$manga_id}";
    $key   = "manga_chapters_limit_{$limit}";

    $cached = wp_cache_get( $key, $group );
    if ( false !== $cached ) {
        return $cached;
    }

    // ... query DB ...

    wp_cache_set( $key, $data, $group, 10 * MINUTE_IN_SECONDS );
    init_html_register_chapter_cache_key( $manga_id, $key ); // track key

    return $data;
}

// Ví dụ trong hàm kiểm tra chapter hẹn giờ
function init_html_has_future_schedule( int $manga_id ): bool {
    $group = "init_html_chapters_{$manga_id}";
    $key   = 'has_future_schedule';

    // ... query DB ...

    wp_cache_set( $key, $has_future, $group, HOUR_IN_SECONDS );
    init_html_register_chapter_cache_key( $manga_id, $key ); // track key

    return $has_future;
}

// Ví dụ trong hàm lấy chương kề (prev/next)
function init_html_get_adjacent_chapters( int $manga_id, float $current_number ): array {
    $group = "init_html_chapters_{$manga_id}";
    $key   = 'adjacent_' . sprintf( '%.2f', round( $current_number, 2 ) );

    // ... query DB ...

    wp_cache_set( $key, $result, $group, 10 * MINUTE_IN_SECONDS );
    init_html_register_chapter_cache_key( $manga_id, $key ); // track key

    return $result;
}

Gọi purge khi cần

// Sau khi insert hoặc delete chapter
add_action( 'init_html_after_chapter_inserted', function( int $manga_id ) {
    init_html_clear_chapter_cache( $manga_id );
} );

add_action( 'init_html_after_chapter_deleted', function( int $manga_id ) {
    init_html_clear_chapter_cache( $manga_id );
} );

So sánh hai cách tiếp cận

wp_cache_flush_group() Purge đúng key Registry Pattern
Redis SCAN ✅ Có — O(n), nguy hiểm ❌ Không ❌ Không
Thời gian xử lý ~6s (có thể crash) <1ms <1ms
Hỗ trợ mọi backend ❌ Không ✅ Có ✅ Có
Key động/phức tạp ✅ Xử lý được ❌ Phải hardcode ✅ Tự động track
Độ chính xác Xóa thừa (cả group) Chính xác tuyệt đối Chính xác tuyệt đối

Kết luận

wp_cache_flush_group() là một API tiện lợi trên giấy tờ nhưng nguy hiểm trong thực tế — đặc biệt với Redis trên các site có lưu lượng lớn. Thay vào đó, hãy áp dụng nguyên tắc đơn giản: chỉ xóa đúng key cần xóa, không hơn không kém.

Nếu key của bạn đơn giản và có thể đoán trước — purge thẳng. Nếu key phức tạp và động — dùng registry pattern để track tự động. Cả hai cách đều an toàn với mọi cache backend, không gây tải Redis, và đảm bảo cache được invalidate chính xác mà không ảnh hưởng hiệu năng.

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...