Add query result cache feature
This commit is contained in:
parent
a61fdbbef3
commit
30dcc606d4
@ -298,7 +298,7 @@ Fictioneer customizes WordPress by using as many standard action and filter hook
|
||||
| `restrict_manage_posts` | `fictioneer_add_chapter_story_filter_dropdown`
|
||||
| `save_post` | `fictioneer_refresh_chapters_schema`, `fictioneer_refresh_chapter_schema`, `fictioneer_refresh_collections_schema`, `fictioneer_refresh_post_caches`, `fictioneer_refresh_post_schema`, `fictioneer_refresh_recommendations_schema`, `fictioneer_refresh_recommendation_schema`, `fictioneer_refresh_stories_schema`, `fictioneer_refresh_story_schema`, `fictioneer_save_seo_metabox`, `fictioneer_save_word_count`, `fictioneer_track_chapter_and_story_updates`, `fictioneer_update_modified_date_on_story_for_chapter`, `fictioneer_update_shortcode_relationships`, `fictioneer_purge_transients`, `fictioneer_save_story_metaboxes`, `fictioneer_save_chapter_metaboxes`, `fictioneer_save_advanced_metabox`, `fictioneer_save_support_links_metabox`, `fictioneer_save_collection_metaboxes`, `fictioneer_save_recommendation_metaboxes`, `fictioneer_save_post_metaboxes`, `fictioneer_delete_cached_story_card_after_update`
|
||||
| `show_user_profile` | `fictioneer_custom_profile_fields`
|
||||
| `shutdown` | `fictioneer_save_story_card_cache`
|
||||
| `shutdown` | `fictioneer_save_story_card_cache`, `fictioneer_save_query_result_cache_registry`
|
||||
| `switch_theme` | `fictioneer_theme_deactivation`
|
||||
| `template_redirect` | `fictioneer_disable_date_archives`, `fictioneer_generate_epub`, `fictioneer_handle_oauth`, `fictioneer_logout`, `fictioneer_disable_attachment_pages`, `fictioneer_gate_unpublished_content`, `fictioneer_serve_sitemap`, `fictioneer_redirect_story`
|
||||
| `transition_post_status` | `fictioneer_log_story_chapter_status_changes`, `fictioneer_chapter_future_to_publish`, `fictioneer_post_story_to_discord`, `fictioneer_post_chapter_to_discord`
|
||||
|
@ -1597,9 +1597,11 @@ define( 'CONSTANT_NAME', value );
|
||||
| FICTIONEER_QUERY_ID_ARRAY_LIMIT | integer | Maximum allowed IDs in 'post__{not}_in' query arguments. Default `1000`.
|
||||
| FICTIONEER_PATREON_EXPIRATION_TIME | integer | Time until a user’s Patreon data expires in seconds. Default `WEEK_IN_SECONDS`.
|
||||
| FICTIONEER_PARTIAL_CACHE_EXPIRATION_TIME | integer | Time until a cached partial expires in seconds. Default `4 * HOUR_IN_SECONDS`.
|
||||
| FICTIONEER_CARD_CACHE_LIMIT | integer | Number of cards cached by the story card cache feature if enabled. Default `50`.
|
||||
| FICTIONEER_CARD_CACHE_LIMIT | integer | Number of story cards cached if the feature is enabled. Default `50`.
|
||||
| FICTIONEER_CARD_CACHE_EXPIRATION_TIME | integer | Time until the whole story card cache expires in seconds. Default `HOUR_IN_SECONDS`.
|
||||
| FICTIONEER_STORY_CARD_CHAPTER_LIMIT | integer | Maximum number of chapters shown on story cards. Default 3.
|
||||
| FICTIONEER_QUERY_RESULT_CACHE_THRESHOLD | integer | Count of a query result required to be eligible for caching. Default `75`.
|
||||
| FICTIONEER_QUERY_RESULT_CACHE_LIMIT | integer | Number of query results cached if the feature is enabled. Default `50`.
|
||||
| FICTIONEER_CACHE_PURGE_ASSIST | boolean | Whether to call the cache purge assist function on post updates. Default `true`.
|
||||
| FICTIONEER_RELATIONSHIP_PURGE_ASSIST | boolean | Whether to purge related post caches. Default `true`.
|
||||
| FICTIONEER_CHAPTER_LIST_TRANSIENTS | boolean | Whether to cache chapter lists on story pages as Transients. Default `true`.
|
||||
|
@ -303,6 +303,16 @@ if ( ! defined( 'FICTIONEER_STORY_CARD_CHAPTER_LIMIT' ) ) {
|
||||
define( 'FICTIONEER_STORY_CARD_CHAPTER_LIMIT', 3 );
|
||||
}
|
||||
|
||||
// Integer: Count of the query results required to be eligible for caching
|
||||
if ( ! defined( 'FICTIONEER_QUERY_RESULT_CACHE_THRESHOLD' ) ) {
|
||||
define( 'FICTIONEER_QUERY_RESULT_CACHE_THRESHOLD', 75 );
|
||||
}
|
||||
|
||||
// Integer: Maximum query results cached as Transients
|
||||
if ( ! defined( 'FICTIONEER_QUERY_RESULT_CACHE_LIMIT' ) ) {
|
||||
define( 'FICTIONEER_QUERY_RESULT_CACHE_LIMIT', 50 );
|
||||
}
|
||||
|
||||
/*
|
||||
* Booleans
|
||||
*/
|
||||
|
@ -83,7 +83,7 @@ if ( ! defined( 'FICTIONEER_ENABLE_PARTIAL_CACHING' ) ) {
|
||||
);
|
||||
}
|
||||
|
||||
// Boolean: Partial caching enabled?
|
||||
// Boolean: Story card caching enabled?
|
||||
if ( ! defined( 'FICTIONEER_ENABLE_STORY_CARD_CACHING' ) ) {
|
||||
define(
|
||||
'FICTIONEER_ENABLE_STORY_CARD_CACHING',
|
||||
@ -91,6 +91,14 @@ if ( ! defined( 'FICTIONEER_ENABLE_STORY_CARD_CACHING' ) ) {
|
||||
);
|
||||
}
|
||||
|
||||
// Boolean: Query result caching enabled=
|
||||
if ( ! defined( 'FICTIONEER_ENABLE_QUERY_RESULT_CACHING' ) ) {
|
||||
define(
|
||||
'FICTIONEER_ENABLE_QUERY_RESULT_CACHING',
|
||||
get_option( 'fictioneer_enable_query_result_caching' ) && ! is_customize_preview()
|
||||
);
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ENABLE SHORTCODE TRANSIENTS?
|
||||
// =============================================================================
|
||||
@ -683,6 +691,9 @@ if ( ! function_exists( 'fictioneer_track_chapter_and_story_updates' ) ) {
|
||||
delete_post_meta( $story_id, 'fictioneer_story_data_collection' );
|
||||
delete_post_meta( $story_id, 'fictioneer_story_chapter_index_html' );
|
||||
|
||||
// Delete cached query results
|
||||
fictioneer_delete_transients_like( "fictioneer_query_{$story_id}" );
|
||||
|
||||
// Refresh cached HTML output
|
||||
delete_transient( 'fictioneer_story_chapter_list_html' . $story_id );
|
||||
|
||||
@ -1326,3 +1337,185 @@ function fictioneer_get_cached_story_card( $key ) {
|
||||
// Return cached card
|
||||
return $card;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// TRANSIENT QUERY RESULT CACHE
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Returns the global registry array for cached query results
|
||||
*
|
||||
* Note: The registry is stored as auto-loaded options in the
|
||||
* database and set up as global variable on request. It is
|
||||
* only updated in the 'shutdown' action.
|
||||
*
|
||||
* @since 5.22.3
|
||||
* @global $fictioneer_query_result_registry
|
||||
*
|
||||
* @return array Array with Transient keys for cached query results.
|
||||
*/
|
||||
|
||||
function fictioneer_get_query_result_cache_registry() {
|
||||
// Abort if...
|
||||
if ( ! FICTIONEER_ENABLE_QUERY_RESULT_CACHING ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Initialize global cache variable
|
||||
global $fictioneer_query_result_registry;
|
||||
|
||||
if ( ! $fictioneer_query_result_registry ) {
|
||||
$fictioneer_query_result_registry = get_option( 'fictioneer_query_cache_registry' );
|
||||
|
||||
if ( ! is_array( $fictioneer_query_result_registry ) ) {
|
||||
$fictioneer_query_result_registry = [];
|
||||
update_option( 'fictioneer_query_cache_registry', [] );
|
||||
}
|
||||
}
|
||||
|
||||
// Return array for good measure, but the global will do
|
||||
return $fictioneer_query_result_registry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds query result to the global query result cache
|
||||
*
|
||||
* Note: Only query results that exceed a certain threshold (75)
|
||||
* are stored in the cache and only the latest n items (50).
|
||||
*
|
||||
* @since 5.22.3
|
||||
* @global $fictioneer_query_result_registry
|
||||
* @see FICTIONEER_QUERY_RESULT_CACHE_THRESHOLD
|
||||
* @see FICTIONEER_QUERY_RESULT_CACHE_LIMIT
|
||||
*
|
||||
* @param string $key The cache key of the query.
|
||||
* @param array $result The result of the query.
|
||||
*/
|
||||
|
||||
function fictioneer_cache_query_result( $key, $result ) {
|
||||
// Abort if...
|
||||
if ( ! FICTIONEER_ENABLE_QUERY_RESULT_CACHING ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( count( $result ) < FICTIONEER_QUERY_RESULT_CACHE_THRESHOLD ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize global cache variable
|
||||
global $fictioneer_query_result_registry;
|
||||
|
||||
if ( ! $fictioneer_query_result_registry ) {
|
||||
$fictioneer_query_result_registry = fictioneer_get_query_result_cache_registry();
|
||||
}
|
||||
|
||||
// Build key; must begin with fictioneer_query_{$post_id} for clearing purposes
|
||||
$transient_key = "fictioneer_query_{$key}";
|
||||
|
||||
// Prepend result to stack
|
||||
$fictioneer_query_result_registry = array_merge( array( $key => $transient_key ), $fictioneer_query_result_registry );
|
||||
|
||||
// Randomized expiration to avoid large sets of results to expire simultaneously
|
||||
set_transient( $transient_key, $result, 8 * HOUR_IN_SECONDS + rand( 0, 4 * HOUR_IN_SECONDS ) );
|
||||
|
||||
// Only allow n items in the cache to avoid bloating the database
|
||||
if ( count( $fictioneer_query_result_registry ) > FICTIONEER_QUERY_RESULT_CACHE_LIMIT ) {
|
||||
array_pop( $fictioneer_query_result_registry ); // Drop oldest entry
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds query result to the global query result cache
|
||||
*
|
||||
* Note: Only query results that exceed a certain threshold (75)
|
||||
* are stored in the cache and only the latest n items (50).
|
||||
*
|
||||
* @since 5.22.3
|
||||
* @global $fictioneer_query_result_registry
|
||||
*
|
||||
* @param string $key The cache key of the query.
|
||||
*
|
||||
* @return array|null Array of WP_Post objects or null if not cached or expired.
|
||||
*/
|
||||
|
||||
function fictioneer_get_cached_query_result( $key ) {
|
||||
// Abort if...
|
||||
if ( ! FICTIONEER_ENABLE_QUERY_RESULT_CACHING ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Initialize global cache variable
|
||||
global $fictioneer_query_result_registry;
|
||||
|
||||
if ( ! $fictioneer_query_result_registry ) {
|
||||
$fictioneer_query_result_registry = fictioneer_get_query_result_cache_registry();
|
||||
}
|
||||
|
||||
// Look for cached result...
|
||||
if ( isset( $fictioneer_query_result_registry[ $key ] ) ) {
|
||||
$transient = get_transient( $fictioneer_query_result_registry[ $key ] );
|
||||
|
||||
if ( is_array( $transient ) ) {
|
||||
// Hit
|
||||
return $transient;
|
||||
} else {
|
||||
// Miss
|
||||
fictioneer_delete_cached_query_result( $key ); // Cleanup
|
||||
}
|
||||
}
|
||||
|
||||
// Nothing cached or expired
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes query result from the global query result cache
|
||||
*
|
||||
* @since 5.22.3
|
||||
* @global $fictioneer_query_result_registry
|
||||
*
|
||||
* @param string $key The cache key of the query.
|
||||
*/
|
||||
|
||||
function fictioneer_delete_cached_query_result( $key ) {
|
||||
// Abort if...
|
||||
if ( ! FICTIONEER_ENABLE_QUERY_RESULT_CACHING ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize global cache variable
|
||||
global $fictioneer_query_result_registry;
|
||||
|
||||
if ( ! $fictioneer_query_result_registry ) {
|
||||
$fictioneer_query_result_registry = fictioneer_get_query_result_cache_registry();
|
||||
}
|
||||
|
||||
// Remove entry (if present)
|
||||
unset( $fictioneer_query_result_registry[ $key ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the final global query result cache to the database
|
||||
*
|
||||
* @since 5.22.3
|
||||
* @global $fictioneer_query_result_registry
|
||||
*/
|
||||
|
||||
function fictioneer_save_query_result_cache_registry() {
|
||||
// Abort if...
|
||||
if ( ! FICTIONEER_ENABLE_QUERY_RESULT_CACHING ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize global cache variable
|
||||
global $fictioneer_query_result_registry;
|
||||
|
||||
// Anything to save?
|
||||
if (
|
||||
! is_null( $fictioneer_query_result_registry ) &&
|
||||
$fictioneer_query_result_registry !== get_option( 'fictioneer_query_cache_registry' )
|
||||
) {
|
||||
update_option( 'fictioneer_query_cache_registry', $fictioneer_query_result_registry ?: [] );
|
||||
}
|
||||
}
|
||||
add_action( 'shutdown', 'fictioneer_save_query_result_cache_registry' );
|
||||
|
@ -196,16 +196,20 @@ if ( ! function_exists( 'fictioneer_get_last_fiction_update' ) ) {
|
||||
/**
|
||||
* Returns array of chapter posts for a story
|
||||
*
|
||||
* Note: Returns reduced WP_Post objects with several properties stripped,
|
||||
* such as the content and excerpt.
|
||||
*
|
||||
* @since 5.9.2
|
||||
* @since 5.22.3 - Refactored.
|
||||
*
|
||||
* @param int $story_id ID of the story.
|
||||
* @param array $args Optional. Additional query arguments.
|
||||
* @param bool $full Optional. Whether to not reduce the posts. Default false.
|
||||
*
|
||||
* @return array Array of chapter posts or empty.
|
||||
*/
|
||||
|
||||
function fictioneer_get_story_chapter_posts( $story_id, $args = [] ) {
|
||||
function fictioneer_get_story_chapter_posts( $story_id, $args = [], $full = false ) {
|
||||
// Static variable cache
|
||||
static $cached_results = [];
|
||||
|
||||
@ -240,6 +244,13 @@ function fictioneer_get_story_chapter_posts( $story_id, $args = [] ) {
|
||||
return $cached_results[ $cache_key ];
|
||||
}
|
||||
|
||||
// Query result cache registry hit?
|
||||
$cached_query_result = fictioneer_get_cached_query_result( $cache_key );
|
||||
|
||||
if ( $cached_query_result ) {
|
||||
return $cached_query_result;
|
||||
}
|
||||
|
||||
// Batched or one go?
|
||||
if ( count( $chapter_ids ) <= FICTIONEER_QUERY_ID_ARRAY_LIMIT ) {
|
||||
$query_args['post__in'] = $chapter_ids ?: [0];
|
||||
@ -263,9 +274,34 @@ function fictioneer_get_story_chapter_posts( $story_id, $args = [] ) {
|
||||
return $chapter_positions[ $a->ID ] - $chapter_positions[ $b->ID ];
|
||||
});
|
||||
|
||||
// F-REF-1
|
||||
if ( ! $full ) {
|
||||
foreach ( $chapter_posts as $post ) {
|
||||
// Chapter contents are extremely large and this kind of
|
||||
// query does not need them, but we leave a reference
|
||||
// in case this causes issues in the future.
|
||||
$post->post_content = 'F-REF-1';
|
||||
|
||||
unset(
|
||||
$post->post_type, // We know this if fcn_chapter
|
||||
$post->ping_status, // Unused
|
||||
$post->to_ping, // Unused
|
||||
$post->pinged, // Unused
|
||||
$post->menu_order, // Unused
|
||||
$post->post_mime_type, // Unused
|
||||
$post->post_parent, // Unused
|
||||
$post->post_content_filtered, // Unused here
|
||||
$post->guid, // Unused here
|
||||
$post->post_excerpt // Unused here
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Cache for subsequent calls
|
||||
$cached_results[ $cache_key ] = $chapter_posts;
|
||||
|
||||
fictioneer_cache_query_result( $cache_key, $chapter_posts );
|
||||
|
||||
// Return chapters selected in story
|
||||
return $chapter_posts;
|
||||
}
|
||||
@ -2564,7 +2600,7 @@ if ( ! function_exists( 'fictioneer_multi_save_guard' ) ) {
|
||||
|
||||
function fictioneer_multi_save_guard( $post_id ) {
|
||||
if (
|
||||
( defined('REST_REQUEST') && REST_REQUEST ) ||
|
||||
( defined( 'REST_REQUEST' ) && REST_REQUEST ) ||
|
||||
wp_is_post_autosave( $post_id ) ||
|
||||
wp_is_post_revision( $post_id ) ||
|
||||
get_post_status( $post_id ) === 'auto-draft'
|
||||
|
@ -683,6 +683,12 @@ define( 'FICTIONEER_OPTIONS', array(
|
||||
'group' => 'fictioneer-settings-general-group',
|
||||
'sanitize_callback' => 'fictioneer_sanitize_checkbox',
|
||||
'default' => 0
|
||||
),
|
||||
'fictioneer_enable_query_result_caching' => array(
|
||||
'name' => 'fictioneer_enable_query_result_caching',
|
||||
'group' => 'fictioneer-settings-general-group',
|
||||
'sanitize_callback' => 'fictioneer_sanitize_checkbox',
|
||||
'default' => 0
|
||||
)
|
||||
),
|
||||
'integers' => array(
|
||||
@ -1148,6 +1154,7 @@ function fictioneer_get_option_label( $option ) {
|
||||
'fictioneer_disable_anti_flicker' => __( 'Disable anti-flicker script', 'fictioneer' ),
|
||||
'fictioneer_hide_categories' => __( 'Hide categories on posts', 'fictioneer' ),
|
||||
'fictioneer_enable_story_card_caching' => __( 'Enable caching of story cards', 'fictioneer' ),
|
||||
'fictioneer_enable_query_result_caching' => __( 'Enable caching of large query results', 'fictioneer' ),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -487,6 +487,9 @@ function fictioneer_purge_theme_caches() {
|
||||
// Cache busting string
|
||||
fictioneer_regenerate_cache_bust();
|
||||
|
||||
// Query result cache registry
|
||||
delete_option( 'fictioneer_query_cache_registry' );
|
||||
|
||||
// Log
|
||||
fictioneer_log( __( 'Purged theme caches.', 'fictioneer' ) );
|
||||
}
|
||||
|
@ -748,6 +748,21 @@
|
||||
?>
|
||||
</div>
|
||||
|
||||
<div class="fictioneer-card__row">
|
||||
<?php
|
||||
fictioneer_settings_label_checkbox(
|
||||
'fictioneer_enable_query_result_caching',
|
||||
__( 'Enable caching of large query results', 'fictioneer' ),
|
||||
sprintf(
|
||||
__( 'Caches the latest %d query results with %s or more posts in the database to speed up loading.', 'fictioneer' ),
|
||||
FICTIONEER_QUERY_RESULT_CACHE_LIMIT,
|
||||
FICTIONEER_QUERY_RESULT_CACHE_THRESHOLD
|
||||
),
|
||||
__( '<p>Queries that return many results are resource-intensive and can slow down your site, especially for stories with hundreds of chapters. This feature mitigates the slowdown by caching the results of the largest queries in the database, typically resulting in faster loading times.</p><p>You can use the <code>FICTIONEER_QUERY_RESULT_CACHE_LIMIT</code> constant to change the number of cached results (default is 50) and the <code>FICTIONEER_QUERY_RESULT_CACHE_THRESHOLD</code> constant to change what constitutes as large result (default is 75). Be aware that increasing these numbers will result in higher RAM consumption.</p>', 'fictioneer' )
|
||||
);
|
||||
?>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
Loading…
x
Reference in New Issue
Block a user