• Skip to secondary menu
  • Skip to main content
  • Skip to primary sidebar
  • Home
  • Projects
  • Products
  • Themes
  • Tools
  • Request for Quote

Vengala Vinay

Having 12+ Years of Experience in Software Development

  • Home
  • WordPress
  • PHP
    • Codeigniter
  • Django
  • Magento
  • Selenium
  • Server
Home » Tuning Database Queries and Cache hit ratios in Timber and Twig Template Engine Integration in Enterprise Themes Using Custom Action and Filter Hooks

Tuning Database Queries and Cache hit ratios in Timber and Twig Template Engine Integration in Enterprise Themes Using Custom Action and Filter Hooks

Diagnosing Slow Database Queries in Timber/Twig WordPress Themes

Enterprise-grade WordPress themes built with Timber and Twig often face performance bottlenecks stemming from inefficient database queries, particularly when fetching complex data structures for rendering. Identifying these slow queries is the first step towards optimization. We’ll leverage WordPress’s built-in debugging capabilities and custom logging to pinpoint the culprits.

The primary tool for this is the Query Monitor plugin. Once installed and activated, it provides an invaluable dashboard under the “Queries” tab. However, for deeper analysis, especially within custom Timber/Twig integrations, we need to augment this with programmatic query logging.

Implementing Custom Query Logging via WordPress Hooks

WordPress fires the query action hook just before executing a database query. We can hook into this to log the query details, including the execution time. This is particularly useful for queries initiated within your Timber context that might not be immediately obvious in the Query Monitor’s default output.

Consider a scenario where a custom post type ‘product’ has many meta fields, and a Twig template iterates through them, performing a separate query for each. This is a classic N+1 query problem. We can detect this by logging queries that appear to be repetitive or have unusually high execution times within a short span.

Add the following PHP code to your theme’s `functions.php` file or a custom plugin:

/**
 * Log database queries with execution time.
 */
function my_theme_log_database_queries() {
    // Only log if WP_DEBUG is true and we are in the admin area or frontend for development.
    if ( defined( 'WP_DEBUG' ) && WP_DEBUG && ! is_admin() ) {
        global $wpdb;
        add_action( 'query', function( $query ) use ( $wpdb ) {
            $start_time = microtime( true );
            $result = $wpdb->query( $query ); // Execute the query to measure time accurately.
            $end_time = microtime( true );
            $execution_time = ( $end_time - $start_time ) * 1000; // Time in milliseconds.

            // Log queries exceeding a certain threshold (e.g., 50ms) or if they are repetitive.
            // For simplicity, we'll log all for now, but a real-world scenario would filter.
            error_log( sprintf(
                "[%s] Query: %s | Time: %.4f ms | Backtrace: %s\n",
                current_time( 'mysql' ),
                $query,
                $execution_time,
                json_encode( debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS, 5 ) ) // Capture backtrace for context.
            ));
            return $result; // Return the result of the query.
        });
    }
}
add_action( 'init', 'my_theme_log_database_queries' );

This code hooks into the init action to set up our query logging. Inside the callback, we use microtime(true) to capture the start and end times of the query execution. Crucially, we re-execute the query within the hook to get an accurate timing. The backtrace is captured to help identify the source of the query within the Timber/Twig rendering process.

To analyze the logs, you’ll need to have WP_DEBUG and WP_DEBUG_LOG enabled in your `wp-config.php`. The logs will appear in `wp-content/debug.log`. Look for queries with high execution times or repeated queries within the same request.

Optimizing Database Queries with Custom Filters

Once slow queries are identified, we can use WordPress filters to modify them or cache their results. The posts_pre_query filter is a powerful tool for intercepting and altering the main WordPress query before it hits the database. For custom queries, we can use similar filtering mechanisms or, more effectively, implement caching.

Caching Query Results

WordPress’s Transients API is ideal for caching query results. Transients are temporary options that expire after a set time. This is perfect for data that doesn’t change frequently.

Let’s say we have a Twig template that displays a list of “featured products” which are fetched using a custom `WP_Query`. We can cache this result:

/**
 * Fetch and cache featured products.
 *
 * @return array An array of WP_Post objects for featured products.
 */
function my_theme_get_featured_products() {
    $cache_key = 'my_theme_featured_products';
    $featured_products = get_transient( $cache_key );

    if ( false === $featured_products ) {
        // Query arguments for featured products.
        $args = array(
            'post_type'      => 'product',
            'posts_per_page' => 5,
            'meta_key'       => '_is_featured',
            'meta_value'     => '1',
            'orderby'        => 'date',
            'order'          => 'DESC',
        );

        $query = new WP_Query( $args );

        if ( $query->have_posts() ) {
            $featured_products = $query->posts; // Store the actual post objects.
            // Cache for 1 hour.
            set_transient( $cache_key, $featured_products, HOUR_IN_SECONDS );
        } else {
            $featured_products = array(); // Ensure we cache an empty array if no posts found.
            set_transient( $cache_key, $featured_products, HOUR_IN_SECONDS );
        }

        // Important: Reset the global $post object.
        wp_reset_postdata();
    }

    return $featured_products;
}

In your Twig template (e.g., `featured-products.twig`), you would then call this function:

{# featured-products.twig #}
{% set featured_products = Timber\Timber::get_products() %} {# Assuming get_products() is mapped to my_theme_get_featured_products #}

{% if featured_products %}
    <ul>
        {% for product in featured_products %}
            <li>
                <h3><a href="{{ product.link }}">{{ product.title }}</a></h3>
                {# Display other product details #}
            </li>
        {% endfor %}
    </ul>
{% else %}
    <p>No featured products found.</p>
{% endif %}

To make the PHP function accessible in Twig, you’d typically use Timber’s context functionality:

/**
 * Add custom functions to Timber context.
 */
function my_theme_timber_context( $context ) {
    $context['products'] = my_theme_get_featured_products(); // Map to 'products' in Twig.
    return $context;
}
add_filter( 'timber_context', 'my_theme_timber_context' );

This approach significantly reduces database load by serving cached results for repeated requests. The cache is automatically invalidated after one hour, ensuring data freshness.

Improving Cache Hit Ratios

A high cache hit ratio means your application is serving most requests from the cache, leading to faster response times and reduced server load. For Timber/Twig integrations, this involves strategically caching different types of data.

Caching Complex Twig Renderings

While caching individual query results is effective, you can also cache entire rendered Twig blocks or components. This is especially useful for sections of a page that are computationally expensive to render or rely on data that changes infrequently.

WordPress’s built-in object cache (if available via a plugin like Redis Object Cache or Memcached) can be leveraged. However, for more granular control over Twig rendering, you might implement a custom caching mechanism around the Timber::render() call or specific Twig functions.

/**
 * Cache a rendered Twig template section.
 *
 * @param string $template The Twig template file.
 * @param array  $data     The data context for the template.
 * @param string $cache_key A unique key for this cache entry.
 * @param int    $expiration Cache expiration time in seconds.
 * @return string The rendered HTML.
 */
function my_theme_cache_twig_render( $template, $data, $cache_key, $expiration = HOUR_IN_SECONDS ) {
    $cached_html = get_transient( $cache_key );

    if ( false === $cached_html ) {
        $cached_html = Timber\Timber::compile( $template, $data );
        set_transient( $cache_key, $cached_html, $expiration );
    }

    return $cached_html;
}

// Example usage in a Timber context:
function my_theme_render_footer_section( $context ) {
    $footer_data = array(
        'site_name' => get_bloginfo( 'name' ),
        'footer_links' => array(
            array( 'text' => 'About Us', 'url' => '/about' ),
            array( 'text' => 'Contact', 'url' => '/contact' ),
        ),
    );
    $footer_cache_key = 'my_theme_footer_section_' . md5( json_encode( $footer_data ) ); // Cache key based on data.

    $context['footer_html'] = my_theme_cache_twig_render(
        'footer-section.twig',
        $footer_data,
        $footer_cache_key,
        DAY_IN_SECONDS // Cache for a full day.
    );

    return $context;
}
add_filter( 'timber_context', 'my_theme_render_footer_section' );

In your main Twig layout file (e.g., `layout.twig`):

{# layout.twig #}
<!DOCTYPE html>
<html>
<head>
    <title>{{ site.name }}</title>
</head>
<body>
    <header>
        {# Header content #}
    </header>

    <main>
        {{ include(content) }} {# Assuming 'content' is passed for main page content #}
    </main>

    <footer>
        {{ footer_html|raw }} {# Render the cached footer HTML #}
    </footer>
</body>
</html>

This method caches the fully rendered HTML of the footer, ensuring that the complex logic and data fetching for the footer are only executed once per day (or per cache expiration). The `md5(json_encode($footer_data))` in the cache key ensures that if the footer data changes, a new cache entry is generated.

Advanced Cache Invalidation Strategies

A common pitfall with caching is stale data. Implementing robust cache invalidation is as crucial as the caching mechanism itself. For Timber/Twig, this often means invalidating caches when underlying data changes.

WordPress provides hooks for post save, update, and delete actions. We can use these to clear relevant transients.

/**
 * Invalidate relevant caches when a post is saved.
 *
 * @param int $post_id The ID of the post being saved.
 */
function my_theme_invalidate_caches_on_post_save( $post_id ) {
    // Invalidate featured products cache if the saved post is a product.
    if ( 'product' === get_post_type( $post_id ) ) {
        delete_transient( 'my_theme_featured_products' );
        // Potentially invalidate other product-related caches.
    }

    // Invalidate footer cache if footer content is managed by a post type.
    // Example: If a 'footer_settings' post type exists.
    // if ( 'footer_settings' === get_post_type( $post_id ) ) {
    //     // Find all footer cache keys and delete them.
    //     // This requires a more sophisticated cache management system or a known prefix.
    //     // For simplicity, let's assume a known key pattern.
    //     // Example: delete_transient( 'my_theme_footer_section_' . md5( json_encode( $footer_data ) ) );
    //     // A better approach is to have a function that purges all footer caches.
    // }

    // Clear all caches if a critical option is updated.
    if ( isset( $_POST['option_name'] ) && 'my_critical_option' === $_POST['option_name'] ) {
        my_theme_clear_all_cached_sections();
    }
}
add_action( 'save_post', 'my_theme_invalidate_caches_on_post_save', 10, 1 );

/**
 * Helper function to clear all relevant theme caches.
 */
function my_theme_clear_all_cached_sections() {
    delete_transient( 'my_theme_featured_products' );
    // Add other cache keys to delete here.
    // For cached rendered sections, you might need to iterate or use a wildcard if supported by your transient storage.
    // Example: If using Redis, you could flush a specific key pattern.
}

This `save_post` hook ensures that when a product is updated, the `my_theme_featured_products` transient is deleted, forcing a fresh query on the next page load. For more complex scenarios, consider using a dedicated caching plugin that offers more advanced invalidation rules or a custom cache management system.

By systematically diagnosing slow queries, implementing strategic caching with the Transients API, and defining robust invalidation strategies, you can significantly enhance the performance of enterprise WordPress themes built with Timber and Twig, leading to a better user experience and reduced server overhead.

Primary Sidebar

A little about the Author

Having 12+ Years of Experience in Software Development, Vinay is a principal software architect, senior systems engineer, and elite technical consultant. He specializes in bespoke PHP/WordPress development, high-performance Magento 2 & Shopify architectures, custom plugin/theme development from scratch, and legacy code modernization (including VB6, VB.NET, PyQt, and Crystal Reports). Known for solving complex database bottlenecks, speed optimization (Core Web Vitals), and advanced security code auditing, Vinay engineers production-ready systems designed to scale under heavy concurrent load conditions.



Chat on WhatsApp

Recent Posts

  • Optimizing WooCommerce cart response times by lazy loading custom custom product catalogs assets
  • Implementing automated compliance reporting for custom internal server status logs ledgers using custom PHP-Spreadsheet exports
  • How to refactor legacy member profile directories queries using modern WP_Query and custom Transient caching
  • Implementing automated compliance reporting for custom customer support tickets ledgers using FPDF customized scripts
  • Troubleshooting database connection pool timeouts in production when using modern Timber Twig templating engines wrappers

Categories

  • apache (1)
  • Business & Monetization (390)
  • Centos (4)
  • Comparisons & Decision Making (55)
  • Debian (2)
  • Debugging & Troubleshooting (658)
  • Desktop Applications (14)
  • DevOps (7)
  • DevOps & Cloud Scaling (962)
  • Django (1)
  • Laravel (4)
  • Migration & Architecture (192)
  • Mobile Applications (24)
  • MySQL (1)
  • Performance & Optimization (872)
  • PHP (5)
  • PHP Development (38)
  • Plugins & Themes (244)
  • Programming Languages (9)
  • Python (20)
  • Ruby on Rails (1)
  • Security & Compliance (639)
  • SEO & Growth (492)
  • Server (23)
  • Ubuntu (9)
  • VB6 & VB.NET (8)
  • Web Applications & Frontend (19)
  • Web Assembly (Wasm) (2)
  • WordPress (22)
  • WordPress Plugin Development (23)
  • WordPress Plugin Development (17)
  • WordPress Plugin Development (330)
  • WordPress Theme Development (357)

Recent Posts

  • Optimizing WooCommerce cart response times by lazy loading custom custom product catalogs assets
  • Implementing automated compliance reporting for custom internal server status logs ledgers using custom PHP-Spreadsheet exports
  • How to refactor legacy member profile directories queries using modern WP_Query and custom Transient caching

Top Categories

  • DevOps & Cloud Scaling (962)
  • Performance & Optimization (872)
  • Debugging & Troubleshooting (658)
  • Security & Compliance (639)
  • SEO & Growth (492)
  • Business & Monetization (390)

Our Products

  • ERP & LMS Systems (4)
  • Directories & Marketplaces (4)
  • Healthcare Portals (3)
  • Point of Sale (POS) (2)
  • E-Commerce Engines (2)

Our Services

  • E-Commerce Development (10)
  • WordPress Development (8)
  • Python & Desktop GUI (7)
  • General Consulting (7)
  • Legacy Modernization (5)
  • Mobile App Development (4)

Copyright © 2026 · Vinay Vengala