• 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 » High-Throughput Caching Strategies: Scaling MySQL for WooCommerce Application APIs

High-Throughput Caching Strategies: Scaling MySQL for WooCommerce Application APIs

Leveraging Redis for WooCommerce API Response Caching

WooCommerce applications, especially those with high traffic or complex product catalogs, often face performance bottlenecks at the API layer. When serving dynamic data like product listings, cart contents, or order details, repeated database queries to MySQL can quickly saturate the server. A robust caching strategy is paramount. Redis, with its in-memory data structure store capabilities, offers a powerful solution for caching API responses, significantly reducing MySQL load and improving latency.

This strategy focuses on caching the *output* of API endpoints rather than individual database rows. This is particularly effective for read-heavy operations where the same data is requested repeatedly. We’ll explore implementing this using PHP, a common language for WooCommerce development, and the predis/phpredis client.

Redis Setup and Configuration

A basic Redis installation is sufficient for this caching layer. Ensure Redis is accessible from your application servers. For production environments, consider:

  • Running Redis in a dedicated cluster for high availability and scalability.
  • Configuring Redis persistence (RDB snapshots and AOF logging) to prevent data loss on restarts.
  • Setting appropriate memory limits and eviction policies (e.g., allkeys-lru) to manage memory usage effectively.

A minimal redis.conf might look like this:

# redis.conf
port 6379
bind 127.0.0.1
daemonize yes
pidfile /var/run/redis_6379.pid
logfile /var/log/redis/redis-server.log
databases 16
save 900 1
save 300 10
save 60 10000
stop-writes-on-bgsave-error yes
rdbcompression yes
rdbchecksum yes
dbfilename dump.rdb
dir /var/lib/redis

# Memory management
maxmemory 2gb
maxmemory-policy allkeys-lru

PHP Implementation: Caching API Responses

We’ll create a simple caching wrapper class that intercepts API requests. If a cached response exists for a given request key, it’s returned directly. Otherwise, the request proceeds to fetch data from MySQL, caches the result, and then returns it.

First, ensure you have a Redis client installed. predis/predis is a popular choice:

composer require predis/predis

Here’s the caching class:

namespace App\Cache;

use Predis\Client;
use Exception;

class ApiResponseCache
{
    private Client $redis;
    private int $defaultTtl; // Default Time To Live in seconds

    public function __construct(array $redisConfig, int $defaultTtl = 300)
    {
        try {
            $this->redis = new Client($redisConfig);
            $this->defaultTtl = $defaultTtl;
            // Ping to ensure connection
            $this->redis->ping();
        } catch (Exception $e) {
            // Log error and potentially fallback or throw
            error_log("Redis connection failed: " . $e->getMessage());
            throw $e; // Or handle gracefully
        }
    }

    /**
     * Generates a cache key based on request parameters.
     * This needs to be deterministic and unique for each distinct request.
     *
     * @param string $endpoint The API endpoint path.
     * @param array $params Associative array of request parameters.
     * @return string
     */
    private function generateCacheKey(string $endpoint, array $params): string
    {
        // Sort parameters to ensure consistent key generation
        ksort($params);
        return 'api_response:' . md5($endpoint . json_encode($params));
    }

    /**
     * Retrieves a cached response.
     *
     * @param string $endpoint
     * @param array $params
     * @return mixed|null The cached data or null if not found.
     */
    public function get(string $endpoint, array $params)
    {
        $key = $this->generateCacheKey($endpoint, $params);
        $cachedData = $this->redis->get($key);

        if ($cachedData) {
            // Consider logging cache hits for monitoring
            // error_log("Cache HIT for key: " . $key);
            return json_decode($cachedData, true);
        }

        // error_log("Cache MISS for key: " . $key);
        return null;
    }

    /**
     * Stores a response in the cache.
     *
     * @param string $endpoint
     * @param array $params
     * @param mixed $data The data to cache.
     * @param int|null $ttl Time To Live in seconds. Defaults to class default.
     * @return bool True on success, false on failure.
     */
    public function set(string $endpoint, array $params, $data, ?int $ttl = null): bool
    {
        if (empty($data)) {
            return false; // Don't cache empty responses
        }

        $key = $this->generateCacheKey($endpoint, $params);
        $ttl = $ttl ?? $this->defaultTtl;

        try {
            $encodedData = json_encode($data);
            if ($encodedData === false) {
                error_log("Failed to JSON encode data for caching: " . json_last_error_msg());
                return false;
            }
            // Use SETEX for atomic set-with-expiration
            $this->redis->setex($key, $ttl, $encodedData);
            return true;
        } catch (Exception $e) {
            error_log("Redis SETEX failed for key " . $key . ": " . $e->getMessage());
            return false;
        }
    }

    /**
     * Deletes a cached response. Useful for cache invalidation.
     *
     * @param string $endpoint
     * @param array $params
     * @return int Number of keys deleted.
     */
    public function delete(string $endpoint, array $params): int
    {
        $key = $this->generateCacheKey($endpoint, $params);
        return $this->redis->del([$key]);
    }

    /**
     * Clears all API response cache entries. Use with caution.
     *
     * @return int Number of keys deleted.
     */
    public function clearAll(): int
    {
        // This is a potentially dangerous operation.
        // Consider using a specific pattern for API responses if clearing all.
        // For example, using SCAN to find keys matching 'api_response:*'
        // and then DEL. For simplicity here, we assume a dedicated DB or pattern.
        // A safer approach might be:
        // $keys = $this->redis->keys('api_response:*');
        // return $this->redis->del($keys);
        // However, KEYS is blocking and slow on large datasets.
        // SCAN is preferred in production.
        // For this example, we'll assume a dedicated DB or a controlled environment.
        // If using multiple DBs, you might flush a specific one:
        // $this->redis->select(YOUR_API_CACHE_DB_INDEX);
        // return $this->redis->flushdb();

        // Example using SCAN for safer deletion of a pattern
        $pattern = 'api_response:*';
        $deletedCount = 0;
        $iterator = null;
        do {
            $keys = $this->redis->scan($iterator, $pattern, 100); // Scan in batches of 100
            if (!empty($keys)) {
                $deletedCount += $this->redis->del($keys);
            }
        } while ($iterator !== 0);

        return $deletedCount;
    }

    /**
     * Checks if Redis is available.
     * @return bool
     */
    public function isAvailable(): bool
    {
        try {
            return $this->redis->ping() === 'PONG';
        } catch (Exception $e) {
            return false;
        }
    }
}

Integrating with WooCommerce API Endpoints

The integration point will depend on your API framework (e.g., WordPress REST API, custom framework). The core idea is to wrap your data fetching logic within the cache’s get and set methods.

Consider a hypothetical endpoint for fetching product details:

// Assuming $redisCache is an instance of ApiResponseCache
// Assuming $requestParams contains GET parameters like ['id' => 123, 'lang' => 'en']
// Assuming $endpoint is '/wp-json/wc/v3/products'

$cacheKeyParams = $requestParams; // Use relevant parameters for cache key
$cachedResponse = $redisCache->get($endpoint, $cacheKeyParams);

if ($cachedResponse !== null) {
    // Return cached data
    header('Content-Type: application/json');
    echo json_encode($cachedResponse);
    exit; // Stop further processing
}

// --- Cache MISS: Fetch from MySQL (or WooCommerce data source) ---
// This is a placeholder for your actual data fetching logic.
// In a real WooCommerce scenario, this would involve WP_Query, WC_Product, etc.
$productData = fetchProductFromDatabase($requestParams['id']); // Your function to get product data

if (empty($productData)) {
    // Handle not found scenario
    http_response_code(404);
    echo json_encode(['error' => 'Product not found']);
    exit;
}

// --- Cache the result before returning ---
// Determine TTL - e.g., longer for static product data, shorter for dynamic cart data
$ttl = 3600; // Cache product data for 1 hour
$redisCache->set($endpoint, $cacheKeyParams, $productData, $ttl);

// Return the fetched data
header('Content-Type: application/json');
echo json_encode($productData);

Cache Invalidation Strategies

Cache invalidation is often the hardest part of caching. For WooCommerce, common invalidation triggers include:

  • Product Updates: When a product is updated (price, description, stock), its cached API response must be invalidated. This can be hooked into WordPress actions like save_post_product.
  • Order Placement: For endpoints related to orders or inventory, cache invalidation is crucial after an order is placed or stock levels change. Hook into actions like woocommerce_order_status_changed.
  • Category/Tag Changes: If product listings are cached based on category or tag filters, changes to product assignments require invalidation.
  • Time-Based Expiration (TTL): The simplest form, relying on the defaultTtl or specific TTLs set during caching. Suitable for data that can tolerate some staleness.

Example of invalidating product cache on update:

// Assuming $redisCache is an instance of ApiResponseCache
// Assuming you have access to the product ID and potentially its parameters

add_action('save_post_product', function($post_id, $post, $update) {
    if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
        return;
    }
    if (wp_is_post_revision($post_id)) {
        return;
    }

    // Invalidate cache for the specific product endpoint
    // This requires knowing the parameters used to fetch it.
    // A common approach is to invalidate all variations or a wildcard.
    // For simplicity, let's assume we invalidate a specific product ID cache.
    // A more robust solution might involve tagging or pattern-based invalidation.

    // Example: Invalidate cache for GET /wp-json/wc/v3/products?id={post_id}
    // This assumes your cache key generation includes the product ID.
    // If your API endpoint uses slugs or other identifiers, adjust accordingly.

    // To invalidate *all* product caches, you'd need a more sophisticated mechanism
    // like Redis SCAN or a dedicated invalidation key.
    // For this example, we'll simulate invalidating a specific product's cache.

    // This is a simplified example. In reality, you might need to reconstruct
    // the exact parameters used when the cache was SET.
    // A common pattern is to have a separate mechanism to track cache keys per entity.

    // Let's assume a function `get_product_cache_keys($post_id)` exists
    // that returns an array of cache keys associated with this product ID.
    // For demonstration, we'll just delete a hypothetical key.

    // Hypothetical:
    // $cacheKeysToDelete = get_product_cache_keys($post_id);
    // foreach ($cacheKeysToDelete as $key) {
    //     $redisCache->deleteFromKey($key); // A method to delete by raw key
    // }

    // A more practical approach for product updates:
    // Invalidate cache for product lists that might include this product.
    // This is complex. A simpler, though less efficient, approach is to
    // invalidate a broader set of related caches or rely more on TTL.

    // For a direct product GET endpoint:
    // If your cache key is `api_response:md5(endpoint . json_encode(['id' => $post_id, ...]))`
    // You'd need to know all possible parameters that could have been used.
    // A common strategy is to invalidate based on entity ID.
    // Let's assume a method `invalidateEntityCache('product', $post_id)` exists.

    // Example: If you have a Redis set storing keys associated with product ID 123:
    // SMEMBERS product:123:cache_keys -> ['api_response:...', 'api_response:...']
    // Then iterate and delete.

    // For this example, we'll simulate a direct deletion if we know the key structure.
    // This is often done by having a separate cache entry for entity invalidation.
    // E.g., SET product:123:invalidate_token timestamp
    // And in your API handler, check for this token.

    // A simpler, less precise method:
    // Invalidate all product-related caches if you can identify them by pattern.
    // This requires careful key naming.
    // Example: If all product GET requests use keys like 'api_response:products:id:{id}'
    // $redisCache->delete('products', ['id' => $post_id]); // Assuming delete can handle patterns or specific IDs

    // For a robust solution, consider a cache invalidation service or library.
    // For now, we'll assume a direct call to delete if the key is known or can be derived.
    // This often involves a separate lookup table or set in Redis.

    // Let's assume a simplified scenario where we can directly delete a known key pattern.
    // This is highly dependent on your `generateCacheKey` implementation.
    // If `generateCacheKey` uses `md5(endpoint . json_encode(['id' => $post_id]))`,
    // you can't easily reverse it.
    // A better approach is to use Redis Sets to group keys by entity.

    // Example using Redis Sets for invalidation:
    // When setting cache:
    // $key = $this->generateCacheKey(...);
    // $this->redis->setex($key, $ttl, $encodedData);
    // $this->redis->sadd('product:' . $post_id . ':cache_keys', $key);

    // When invalidating:
    $product_id = $post_id; // Assuming $post_id is the product ID
    $cacheKeys = $redisCache->redis->smembers('product:' . $product_id . ':cache_keys');
    if ($cacheKeys) {
        $deletedCount = $redisCache->redis->del($cacheKeys);
        // Optionally remove the set itself if no more keys
        $redisCache->redis->del('product:' . $product_id . ':cache_keys');
        error_log("Invalidated " . $deletedCount . " cache keys for product ID: " . $product_id);
    }

}, 10, 1); // Priority 10, 1 argument ($update)

Monitoring and Performance Tuning

Effective monitoring is crucial for understanding cache performance and identifying areas for optimization.

  • Redis INFO command: Regularly check redis-cli INFO memory and redis-cli INFO stats. Pay attention to used_memory, maxmemory, evicted_keys, keyspace_hits, and keyspace_misses.
  • Application Logs: Log cache hits, misses, and failures. This helps in debugging and understanding access patterns.
  • Latency Monitoring: Track the response times of API endpoints with and without caching enabled.
  • Cache Hit Ratio: Calculate the ratio of keyspace_hits to (keyspace_hits + keyspace_misses). Aim for a high hit ratio (e.g., > 80%).
  • Eviction Policy: If evicted_keys is high, it indicates Redis is running out of memory. Consider increasing maxmemory, optimizing data structures, or using a more aggressive eviction policy if appropriate.
  • TTL Optimization: Analyze the effectiveness of TTLs. Data that changes infrequently can have longer TTLs, while frequently changing data might need shorter TTLs or more aggressive invalidation.

Advanced Considerations

  • Cache Tagging: Implement a more sophisticated invalidation system using cache tags. Each cached item is associated with one or more tags (e.g., ‘product’, ‘category_123’). When an entity is updated, all cache entries with its associated tags are invalidated. This requires a more complex Redis structure (e.g., using Sets to map tags to keys).
  • Serialization Format: While JSON is common, consider alternatives like MessagePack for potentially smaller payloads and faster serialization/deserialization, especially for large datasets.
  • Client Libraries: For higher performance, consider the native phpredis extension over predis if available and compatible.
  • Distributed Caching: For very large-scale applications, explore distributed caching solutions or Redis Cluster for fault tolerance and horizontal scaling.
  • Cache Warming: For critical endpoints, consider pre-populating the cache after deployments or restarts to avoid initial cache misses.
  • Rate Limiting: Implement rate limiting on API endpoints to prevent abuse and protect both your application and Redis instance.

By strategically implementing response caching with Redis, you can dramatically improve the performance and scalability of your WooCommerce application APIs, ensuring a smoother experience for your users and reducing the load on your primary database.

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

  • Top 100 Automated PDF & Document Generation Tool Ideas for Developers that Will Dominate the Software Industry in 2026
  • Top 5 Automated PDF & Document Generation Tool Ideas for Developers in Highly Competitive Technical Niches
  • Top 50 Automated PDF & Document Generation Tool Ideas for Developers without Relying on Paid Advertising Budgets
  • Top 50 Automated PDF & Document Generation Tool Ideas for Developers to Double User Engagement and Session Duration
  • Building a Reactive Frontend Framework inside Theme Security Auditing: Mitigating XSS, CSRF, and SQLi Vulnerabilities under Heavy Concurrent Load Conditions

Categories

  • apache (1)
  • Business & Monetization (390)
  • Centos (4)
  • Comparisons & Decision Making (55)
  • Debian (2)
  • Debugging & Troubleshooting (580)
  • DevOps (7)
  • DevOps & Cloud Scaling (955)
  • Django (1)
  • Migration & Architecture (184)
  • MySQL (1)
  • Performance & Optimization (775)
  • PHP (5)
  • Plugins & Themes (238)
  • Security & Compliance (543)
  • SEO & Growth (488)
  • Server (23)
  • Ubuntu (9)
  • WordPress (22)
  • WordPress Plugin Development (7)
  • WordPress Theme Development (340)

Recent Posts

  • Top 100 Automated PDF & Document Generation Tool Ideas for Developers that Will Dominate the Software Industry in 2026
  • Top 5 Automated PDF & Document Generation Tool Ideas for Developers in Highly Competitive Technical Niches
  • Top 50 Automated PDF & Document Generation Tool Ideas for Developers without Relying on Paid Advertising Budgets
  • Top 50 Automated PDF & Document Generation Tool Ideas for Developers to Double User Engagement and Session Duration
  • Building a Reactive Frontend Framework inside Theme Security Auditing: Mitigating XSS, CSRF, and SQLi Vulnerabilities under Heavy Concurrent Load Conditions
  • Deep Dive: Memory Leak Prevention in Virtual CSS Variables and Dynamic Style Interpolation Using Custom Action and Filter Hooks

Top Categories

  • DevOps & Cloud Scaling (955)
  • Performance & Optimization (775)
  • Debugging & Troubleshooting (580)
  • Security & Compliance (543)
  • SEO & Growth (488)
  • Business & Monetization (390)

Our Products

  • School Management & Student Administration System
  • Integrated Hospital & Clinic Management System
  • Real Estate Directory & Agent Portal
  • Restaurant POS & Table Booking System
  • Retail Inventory POS & Billing System
  • Pharmacy Inventory & Clinic Billing System

Our Services

  • Vibe Engineering & AI Code Auditing Services
  • Prompt Engineering & "Vibe Coding" Workflow Consulting
  • AI-Augmented "Vibe Coding" & Rapid MVP Development
  • Figma to Shopify Liquid Theme Customization
  • Figma to WooCommerce Frontend Development
  • Figma to Magento 2 Theme Development

Copyright © 2026 · Vinay Vengala