- Why “10 comments” can still be slow
- Standardized Wrapper: init_html_get_comments_cached()
- Only purge on DELETE: maintain high cache hit ratio
- Utility function: “recent approved comments” API
- Note: Consider removing post_status for very large databases
- Query tuning tips
- Recommended indexes
- Integration example
- Practical deployment checklist
- Conclusion
Why “10 comments” can still be slow
# 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;
The bottleneck: sorting DESC on comment_ID, joining with wp_posts for post_status filtering, and a massive dataset. You can index aggressively, but the structural fix is reducing query frequency with persistent caching.
Standardized Wrapper: init_html_get_comments_cached()
<?php
/**
* Retrieve comments with persistent caching.
* - Relies on Object Cache backend (Redis/Memcached).
* - Normalizes args to improve cache hit ratio.
*
* @param array $args Same parameters as get_comments().
* @param int $ttl Cache TTL in seconds. Default 1 hour (min 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', // exclude pingback/trackback
'orderby' => 'comment_ID',
'order' => 'DESC',
'update_comment_meta_cache' => false,
'update_comment_post_cache' => false,
];
$args = wp_parse_args( $args, $defaults );
$raw_key = 'gc_' . md5( wp_json_encode( $args ) );
$cache_key = init_html_cache_key_with_version( $raw_key );
$cached = wp_cache_get( $cache_key, $cache_group );
if ( false !== $cached ) {
return $cached;
}
$comments = get_comments( $args );
wp_cache_set( $cache_key, $comments, $cache_group, max( 60, $ttl ) );
return $comments;
}
Only purge on DELETE: maintain high cache hit ratio
On large-scale sites with millions of comments, purging cache on every event (approve/edit/status change) destroys the hit ratio — making caching almost useless. The correct approach is to purge only on deletion (hard delete) or optionally on trashing if your site hides trashed comments.
<?php
/**
* Version-key strategy:
* - If backend supports flush_group → use it.
* - Otherwise bump version key to invalidate all entries cheaply.
*/
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');
}
/** Wrap cache key with version number */
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;
}
/**
* Purge only when deleting:
* - deleted_comment: permanently removed
* - trashed_comment: optional if UI hides trashed comments
*/
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 );
// DO NOT purge on comment_post, edit_comment, or transition_comment_status
// unless you absolutely need real-time updates.
Utility function: “recent approved comments” API
<?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 );
}
Note: Consider removing post_status for very large databases
On large-scale WordPress sites with millions of comments, adding a JOIN wp_posts to filter by post_status = 'publish' is often the main reason queries become extremely slow.
In most real-world cases, comments belonging to unpublished posts (drafts, private posts, etc.) are not displayed on the frontend anyway, so filtering by post status at the SQL level is redundant. If performance is critical and you can safely assume only published content appears publicly, remove the post_status parameter from your query.
This allows MySQL to query only the wp_comments table using its primary key index (comment_ID), avoiding the costly join and full-table scan. The result is a dramatic performance improvement — reducing query time from several seconds to a few milliseconds, especially when combined with persistent object caching.
// Instead of:
$comments = get_comments([
'number' => 10,
'status' => 'approve',
'post_status' => 'publish', // Heavy JOIN on wp_posts
]);
// Use:
$comments = get_comments([
'number' => 10,
'status' => 'approve',
]); // Fast and lightweight
Accepting this small trade-off (skipping the post status filter) has virtually no impact on accuracy in most production environments, but delivers massive gains in query speed and scalability.
Query tuning tips
- Use
fields => 'ids'if you only need comment IDs. - Disable
update_comment_meta_cacheandupdate_comment_post_cachewhen not needed. - Keep
orderby => 'comment_ID'andorder => 'DESC'for recent-first lists.
Recommended indexes
wp_comments (comment_approved, comment_type, comment_ID)or(comment_approved, comment_type, comment_date_gmt)wp_comments (comment_post_ID, comment_approved)for per-post querieswp_posts (ID, post_status)or(post_status)for join filtering
Integration example
<?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, '…' );
printf(
'<li><a href="%s">%s</a>: %s</li>',
esc_url( $link ),
esc_html( $author ),
esc_html( $excerpt )
);
}
echo '</ul>';
}
Practical deployment checklist
- Enable persistent Object Cache (Redis/Memcached) using a plugin like “Redis Object Cache”.
- Use
init_html_get_comments_cached()everywhere repeated comment queries are made. - Purge only on delete (hard delete or optional trash) to maintain cache efficiency.
- Consider
fields => 'ids'+ lazy detail loading for lightweight operations. - Use a minimum TTL of 60 seconds; increase TTL for less dynamic blocks.
Conclusion
Fetching “10 latest comments” can become a silent performance killer on high-scale WordPress sites. Wrapping queries in a persistent Object Cache and purging only on delete keeps cache hit ratios high, reduces TTFB, and drastically lowers MySQL load — a simple, scalable optimization for any site with heavy user activity.
Comments