Khi nào WP_Query “ngốn” RAM – và làm sao phát hiện bằng debug_print_backtrace()

WP_Query đôi khi không làm site chậm đi ngay lập tức — nó âm thầm “ăn RAM” cho đến khi bạn đụng trần Allowed memory size. Bài này đi thẳng vào thực chiến: vì sao truy vấn phình bộ nhớ, cách “bắt quả tang” thủ phạm bằng debug_print_backtrace(), và các bước tối ưu giúp giảm tải ngay lập tức.

Khi nào WP_Query “ngốn” RAM – và làm sao phát hiện bằng debug_print_backtrace()

Nếu bạn từng thấy trang vẫn cache nhanh nhưng mỗi request PHP lại tiêu tốn vài MB, hoặc template/shortcode chạy ổn ở dev nhưng vỡ RAM ở production, thì đây là checklist chẩn đoán dành cho bạn.

  • Hiểu những tình huống khiến WP_Query đội RAM.
  • Log backtrace gọn để chỉ ra đoạn code gây phình bộ nhớ.
  • Áp dụng bộ tối ưu “cắm là đỡ”: fields ="ids", tắt meta/term cache, giới hạn bản ghi, và chiến lược sort hợp lý.

TL;DR

  • WP_Query ngốn RAM khi bạn kéo về quá nhiều post objects + meta/term cache, hoặc meta query/sort phức tạp buộc WP phải tải & xử lý nhiều dữ liệu trong bộ nhớ.
  • Cách bắt quả tang: đo RAM trước/sau mỗi query, và in backtrace rút gọn để biết đoạn code nào tạo ra truy vấn phình to.

Dấu hiệu nhận biết “ăn RAM”

  • Trang load vẫn nhanh (cache HTML), nhưng mỗi request PHP ăn nhiều MB → dễ vỡ trên hosting yếu.
  • Bật debug thấy Fatal error: Allowed memory size ... ở trang nhiều nội dung.
  • Một vòng lặp dùng WP_Query trong template/shortcode chạy ổn ở dev, nhưng vỡ RAM ở production khi dữ liệu lớn.

Nguyên nhân phổ biến

  1. Tải cả post object (title, content, meta, terms) cho rất nhiều bài (vd: posts_per_page = -1).
  2. Meta Query nặng:
    • Dùng LIKE / NOT LIKE / nhiều OR.
    • ORDER BY meta_value không có index, phải lôi toàn bộ rows ra để sort.
  3. Không tắt prefetch meta/term: update_post_meta_cache, update_post_term_cache vẫn true dù không cần.
  4. Không dùng fields => 'ids' khi chỉ cần ID.
  5. no_found_rows = false dù không phân trang → thêm 1 COUNT(*) vô ích.
  6. ACF/meta lớn: một meta chứa JSON to; load 100 bài = load 100 JSON vào RAM.
  7. Template lồng nhiều query (N+1 queries), mỗi query lại không tối ưu.

Chiến thuật bắt “thủ phạm” với backtrace

1) Đo RAM ± backtrace tại chỗ (bao vây điểm nghi ngờ)

Dùng khi bạn nghi một query cụ thể phình RAM (vd loop trong template).

<?php
function init_dbg_bytes($bytes) {
    $units = ['B','KB','MB','GB'];
    $i = 0;
    while ($bytes >= 1024 && $i < count($units)-1) { $bytes /= 1024; $i++; }
    return round($bytes, 2) . ' ' . $units[$i];
}

function init_probe_wp_query($args, $label = 'probe') {
    $m0 = memory_get_usage();
    $q  = new WP_Query($args);
    $m1 = memory_get_usage();
    $diff = $m1 - $m0;

    error_log("INIT_PROBE {$label}: +" . init_dbg_bytes($diff) . " (after: " . init_dbg_bytes($m1) . ")");
    if ($diff > 1 * 1024 * 1024) { // >1MB thì in backtrace rút gọn
        ob_start();
        debug_print_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 8);
        $trace = ob_get_clean();
        error_log("INIT_PROBE {$label} TRACE:\n" . $trace);
    }
    return $q;
}

// Ví dụ:
// $q = init_probe_wp_query([ 'post_type' => 'post', 'posts_per_page' => 50 ], 'home-loop');
  • Khi nào dùng: muốn “bắt quả tang” 1 query cụ thể.
  • Lợi ích: biết RAM tăng bao nhiêu do query đó và gọi từ file/hàm nào.

2) Gài hook toàn cục vào the_posts (tự log mọi WP_Query)

Dùng khi bạn không biết query nào nặng – để hệ thống tự log tất cả.

<?php
if (!function_exists('init_dbg_bytes')) {
    function init_dbg_bytes($bytes) {
        $units = ['B','KB','MB','GB'];
        $i = 0;
        while ($bytes >= 1024 && $i < count($units)-1) { $bytes /= 1024; $i++; }
        return round($bytes, 2) . ' ' . $units[$i];
    }
}

add_filter('the_posts', function($posts, $query) {
    static $last_mem = null;
    $before = memory_get_usage();
    $result = $posts; // giữ nguyên

    // Sau khi WP đã tạo $posts (post objects) xong
    $after = memory_get_usage();
    $diff  = $after - $before;

    // Bỏ các query nhỏ
    if ($diff < 512 * 1024) return $result;

    $tag = [];
    $tag[] = $query->is_main_query() ? 'main' : 'secondary';
    if (!empty($query->query['post_type'])) $tag[] = 'pt:' . (is_array($query->query['post_type']) ? implode(',', $query->query['post_type']) : $query->query['post_type']);
    if (!empty($query->query['meta_query'])) $tag[] = 'meta';
    if (!empty($query->query['orderby']))    $tag[] = 'orderby:' . $query->query['orderby'];
    if (isset($query->query_vars['posts_per_page'])) $tag[] = 'ppp:' . $query->query_vars['posts_per_page'];

    error_log('INIT_TRACE the_posts [' . implode('|', $tag) . ']: +' . init_dbg_bytes($diff) . ' after:' . init_dbg_bytes($after) . ' count:' . count($posts));

    // Backtrace rút gọn khi bùng >2MB
    if ($diff > 2 * 1024 * 1024) {
        ob_start();
        debug_print_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 8);
        $trace = ob_get_clean();
        error_log("INIT_TRACE backtrace:\n" . $trace);
    }

    $last_mem = $after;
    return $result;
}, 999, 2);

Xem log ở đâu: bật WP_DEBUG_LOG để ghi vào wp-content/debug.log.

define('WP_DEBUG', true);
define('WP_DEBUG_LOG', true);
define('WP_DEBUG_DISPLAY', false);
@ini_set('display_errors', 0);

3) “Đo từ gốc” – gài ngay khi WP sắp thực thi SQL

Muốn soi truy vấn SQL nào dẫn tới RAM tăng, log thêm posts_request:

<?php
add_filter('posts_request', function($sql, $query){
    // Đánh dấu query đáng ngờ (nhiều JOIN meta, ORDER BY meta_value)
    if (false !== stripos($sql, 'postmeta') || false !== stripos($sql, 'ORDER BY meta_value')) {
        error_log("INIT_SQL suspicious:\n" . $sql);
        ob_start();
        debug_print_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 6);
        $trace = ob_get_clean();
        error_log("INIT_SQL trace:\n" . $trace);
    }
    return $sql;
}, 10, 2);

Sau khi bắt được thủ phạm – fix kiểu gì?

Checklist tối ưu WP_Query (áp dụng theo nhu cầu):

  1. Chỉ lấy ID khi có thể
    'fields' => 'ids',
  2. Tắt những thứ không cần
    'no_found_rows'          => true,   // nếu không phân trang
    'update_post_meta_cache' => false,  // nếu không cần meta ngay
    'update_post_term_cache' => false,  // nếu không render taxonomy ngay
  3. Giới hạn số lượng
    'posts_per_page' => 20,
  4. Giảm độ phức tạp meta_query
    • Tránh LIKE/NOT LIKE nếu có thể chuyển sang =, IN, BETWEEN + type phù hợp (NUMERIC, DECIMAL…).
    • Nếu cần sort theo meta lớn, cân nhắc đưa dữ liệu ra custom table để index.
  5. Sort rẻ hơn
    • Tránh ORDER BY meta_value trên dataset lớn; dùng date, menu_order, hoặc precompute field có index.
  6. Chia nhỏ và lazy-load (lấy ID trước, rồi load meta theo lô).
  7. Cache thông minh (transient/object cache cho danh sách ID hoặc kết quả đã chuẩn hóa).

Ví dụ tối ưu nhanh (trước → sau)

Trước (ngốn RAM):

$q = new WP_Query([
  'post_type'              => 'manga',
  'posts_per_page'         => -1,
  'meta_query'             => [
      ['key' => 'score', 'value' => 7, 'compare' => '>=', 'type' => 'NUMERIC'],
  ],
  'orderby'                => 'meta_value',
  'meta_key'               => 'score',
  'order'                  => 'DESC',
]);

Sau (nhẹ RAM hơn nhiều):

// 1) Chỉ lấy ID (nhẹ hơn rất nhiều)
$q = new WP_Query([
  'post_type'              => 'manga',
  'fields'                 => 'ids',
  'posts_per_page'         => 50,
  'no_found_rows'          => true,
  'update_post_meta_cache' => false,
  'update_post_term_cache' => false,
  'meta_query'             => [
      ['key' => 'score', 'value' => 7, 'compare' => '>=', 'type' => 'NUMERIC'],
  ],
  'orderby'                => 'meta_value_num',
  'meta_key'               => 'score',
  'order'                  => 'DESC',
]);

// 2) Tùy nhu cầu, load meta theo lô sau (get_post_meta cho từng ID)

Mẹo khi đọc backtrace

  • Tập trung vào template file hoặc plugin file trong 5–8 frame đầu tiên.
  • Nếu thấy call từ shortcode, kiểm tra function render của shortcode đó.
  • Nếu thấy call qua pre_get_posts, kiểm tra hook có áp dụng “đại trà” không (vô tình tác động mọi query).

Kết

  • Đo RAM trước/sau + backtrace rút gọn là cách nhanh & chuẩn để biết đoạn code nào gây phình bộ nhớ khi chạy WP_Query.
  • Fix theo checklist (đặc biệt fields => 'ids', tắt meta/term cache, no_found_rows) thường giảm RAM tức thì.
  • Case cực lớn hoặc cần sort meta phức tạp → custom table + index là con đường bền vữ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...