• 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 MongoDB for Laravel Application APIs

High-Throughput Caching Strategies: Scaling MongoDB for Laravel Application APIs

Leveraging Redis for MongoDB Caching in Laravel APIs

When scaling MongoDB-backed Laravel APIs, particularly those experiencing high read volumes, implementing an effective caching layer is paramount. Redis, with its in-memory data structure store capabilities, offers a robust and performant solution. This strategy focuses on caching frequently accessed, relatively static data to offload read pressure from MongoDB and reduce latency.

We’ll explore a practical approach involving caching query results and specific document fetches. This involves creating a dedicated cache service within your Laravel application that intelligently interacts with both MongoDB and Redis.

Implementing a Cache Service with Redis

A common pattern is to abstract cache interactions into a dedicated service class. This promotes code organization and testability. We’ll use Laravel’s built-in Redis facade for seamless integration.

First, let’s define an interface for our cache service to ensure a contract for its operations.

namespace App\Services\Cache;

interface CacheServiceContract
{
    public function get(string $key, ?callable $fallback = null);
    public function set(string $key, $value, int $ttl = 3600);
    public function has(string $key): bool;
    public function forget(string $key): bool;
    public function remember(string $key, int $ttl, callable $callback);
    public function increment(string $key, int $step = 1): int|false;
    public function decrement(string $key, int $step = 1): int|false;
}



Now, the concrete implementation using Redis:

namespace App\Services\Cache;

use Illuminate\Support\Facades\Redis;
use Illuminate\Contracts\Cache\Repository;

class RedisCacheService implements CacheServiceContract
{
    protected Repository $redis;
    protected int $defaultTtl = 3600; // Default TTL in seconds (1 hour)

    public function __construct()
    {
        // Use Laravel's Redis facade, which can be configured for multiple connections
        $this->redis = Redis::connection();
    }

    public function get(string $key, ?callable $fallback = null)
    {
        if ($this->redis->has($key)) {
            return json_decode($this->redis->get($key), true);
        }

        if ($fallback) {
            $value = $fallback();
            if ($value !== null) {
                $this->set($key, $value);
            }
            return $value;
        }

        return null;
    }

    public function set(string $key, $value, int $ttl = null)
    {
        $ttl = $ttl ?? $this->defaultTtl;
        // Ensure value is serializable, JSON is a good choice for complex data
        $serializedValue = json_encode($value);
        if ($serializedValue === false) {
            // Handle serialization errors if necessary
            return false;
        }
        return $this->redis->setex($key, $ttl, $serializedValue);
    }

    public function has(string $key): bool
    {
        return $this->redis->exists($key) > 0;
    }

    public function forget(string $key): bool
    {
        return $this->redis->del($key) > 0;
    }

    public function remember(string $key, int $ttl, callable $callback)
    {
        if ($this->has($key)) {
            return $this->get($key);
        }

        $value = $callback();
        if ($value !== null) {
            $this->set($key, $value, $ttl);
        }
        return $value;
    }

    public function increment(string $key, int $step = 1): int|false
    {
        return $this->redis->incrby($key, $step);
    }

    public function decrement(string $key, int $step = 1): int|false
    {
        return $this->redis->decrby($key, $step);
    }

    // Helper to generate cache keys
    public function generateKey(string $prefix, array $params): string
    {
        return $prefix . ':' . md5(json_encode($params));
    }
}



Register this service in your Laravel application's service provider (e.g., AppServiceProvider):

namespace App\Providers;

use App\Services\Cache\CacheServiceContract;
use App\Services\Cache\RedisCacheService;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function register()
    {
        $this->app->singleton(CacheServiceContract::class, function ($app) {
            return new RedisCacheService();
        });
    }

    // ...
}



Caching MongoDB Query Results

Consider a scenario where you frequently fetch a list of active users with specific filters. Instead of hitting MongoDB for every request, we can cache the result.

Let's assume you have a UserRepository that interacts with MongoDB:

namespace App\Repositories;

use App\Services\Cache\CacheServiceContract;
use MongoDB\Client; // Assuming you're using the official MongoDB PHP driver

class UserRepository
{
    protected Client $mongoClient;
    protected string $collectionName = 'users';
    protected CacheServiceContract $cacheService;

    public function __construct(CacheServiceContract $cacheService)
    {
        // Configure your MongoDB connection here
        $this->mongoClient = new Client(env('MONGODB_URI'));
        $this->cacheService = $cacheService;
    }

    public function getActiveUsers(array $filters = [], int $limit = 100): array
    {
        // Generate a unique cache key based on the method and its parameters
        $cacheKey = $this->cacheService->generateKey('users:active', array_merge($filters, ['limit' => $limit]));

        return $this->cacheService->remember($cacheKey, 3600, function () use ($filters, $limit) {
            $collection = $this->mongoClient->selectCollection(env('MONGODB_DATABASE'), $this->collectionName);

            // Construct MongoDB query
            $query = array_merge(['status' => 'active'], $filters);

            $cursor = $collection->find($query, ['limit' => $limit]);
            return $cursor->toArray();
        });
    }

    // ... other repository methods
}



In a controller or service that uses this repository:

namespace App\Http\Controllers;

use App\Repositories\UserRepository;
use Illuminate\Http\Request;

class UserController extends Controller
{
    protected UserRepository $userRepository;

    public function __construct(UserRepository $userRepository)
    {
        $this->userRepository = $userRepository;
    }

    public function index(Request $request)
    {
        $filters = $request->only(['role', 'department']); // Example filters
        $activeUsers = $this->userRepository->getActiveUsers($filters);

        return response()->json($activeUsers);
    }
}



Caching Specific Document Fetches

For individual document retrievals, especially those that are frequently accessed by ID, caching can significantly reduce database load.

namespace App\Repositories;

use App\Services\Cache\CacheServiceContract;
use MongoDB\Client;
use MongoDB\BSON\ObjectId;

class ProductRepository
{
    protected Client $mongoClient;
    protected string $collectionName = 'products';
    protected CacheServiceContract $cacheService;

    public function __construct(CacheServiceContract $cacheService)
    {
        $this->mongoClient = new Client(env('MONGODB_URI'));
        $this->cacheService = $cacheService;
    }

    public function findById(string $id): ?array
    {
        $cacheKey = $this->cacheService->generateKey('products:id', ['id' => $id]);

        return $this->cacheService->remember($cacheKey, 600, function () use ($id) { // Cache for 10 minutes
            $collection = $this->mongoClient->selectCollection(env('MONGODB_DATABASE'), $this->collectionName);
            try {
                $objectId = new ObjectId($id);
                $document = $collection->findOne(['_id' => $objectId]);
                return $document ? (array) $document : null;
            } catch (\InvalidArgumentException $e) {
                // Handle invalid ObjectId format
                return null;
            }
        });
    }

    // Method to invalidate cache when a product is updated or deleted
    public function update(string $id, array $data): bool
    {
        $collection = $this->mongoClient->selectCollection(env('MONGODB_DATABASE'), $this->collectionName);
        try {
            $objectId = new ObjectId($id);
            $result = $collection->updateOne(['_id' => $objectId], ['$set' => $data]);

            if ($result->getModifiedCount() > 0) {
                // Invalidate the cache for this product
                $cacheKey = $this->cacheService->generateKey('products:id', ['id' => $id]);
                $this->cacheService->forget($cacheKey);
                return true;
            }
            return false;
        } catch (\InvalidArgumentException $e) {
            return false;
        }
    }

    public function delete(string $id): bool
    {
        $collection = $this->mongoClient->selectCollection(env('MONGODB_DATABASE'), $this->collectionName);
        try {
            $objectId = new ObjectId($id);
            $result = $collection->deleteOne(['_id' => $objectId]);

            if ($result->getDeletedCount() > 0) {
                // Invalidate the cache for this product
                $cacheKey = $this->cacheService->generateKey('products:id', ['id' => $id]);
                $this->cacheService->forget($cacheKey);
                return true;
            }
            return false;
        } catch (\InvalidArgumentException $e) {
            return false;
        }
    }
}



Cache Invalidation Strategies

Cache invalidation is often the most challenging aspect of caching. For write operations (updates, deletes), it's crucial to invalidate the corresponding cache entries to prevent serving stale data. The examples above demonstrate a basic "write-through" invalidation where the cache is explicitly cleared after a successful modification.

More advanced strategies include:

  • Time-Based Expiration (TTL): As shown, setting a Time-To-Live (TTL) for cache entries is the simplest approach. Data is considered stale after the TTL expires. This is suitable for data that doesn't change frequently or where eventual consistency is acceptable.
  • Event-Driven Invalidation: For more complex relationships, you might trigger cache invalidation based on events. For instance, when a user's profile is updated, fire an event that listeners can use to clear related cached data (e.g., user's posts, comments).
  • Cache Tagging: While Redis itself doesn't natively support cache tagging in the same way some other caching systems do, you can simulate it. For example, when caching multiple items related to a "product" entity, you could store a list of keys associated with that tag (e.g., in a Redis Set named tag:products). When invalidating the "product" tag, you'd retrieve all keys from this set and delete them.
// Example of simulating cache tagging
public function invalidateTag(string $tag)
{
    $tagKey = "tag:{$tag}";
    $keysToDelete = $this->redis->smembers($tagKey);

    if (!empty($keysToDelete)) {
        // Delete the actual cached items
        $this->redis->del($keysToDelete);
        // Delete the tag key itself
        $this->redis->del($tagKey);
    }
}

public function setWithTag(string $key, $value, int $ttl, string $tag)
{
    $this->set($key, $value, $ttl);
    $tagKey = "tag:{$tag}";
    $this->redis->sadd($tagKey, $key);
}



Redis Configuration and Monitoring

Ensure your Redis instance is properly configured for performance and resilience. For high-throughput scenarios:

  • Memory Allocation: Allocate sufficient RAM to Redis. Monitor memory usage and consider using Redis's eviction policies (e.g., allkeys-lru) if memory becomes a constraint.
  • Persistence: For caching, persistence might be optional or configured for RDB snapshots only, as data loss on restart is often acceptable for cache. If you need durability, consider AOF (Append Only File).
  • Network Latency: Place your Redis server geographically close to your Laravel application servers. Use Redis Sentinel or Cluster for high availability and fault tolerance.
  • Connection Pooling: Laravel's Redis facade handles connection management. Ensure your config/database.php is set up correctly for production, potentially with read/write splitting if using Redis Cluster.

Monitoring Redis is critical. Key metrics to watch include:

  • Memory Usage: used_memory, used_memory_rss.
  • Cache Hit Rate: Monitor keyspace_hits and keyspace_misses. A high hit rate indicates effective caching.
  • Latency: Use Redis commands like redis-cli --latency -h your_redis_host -p your_redis_port to check command execution times.
  • CPU Usage: High CPU can indicate inefficient operations or a need for more powerful hardware.
  • Network Traffic: Monitor bandwidth usage.

Tools like Prometheus with the Redis Exporter, Datadog, or New Relic can provide comprehensive monitoring and alerting.

Conclusion

Implementing a robust caching strategy with Redis for your MongoDB-backed Laravel APIs is a powerful way to scale. By intelligently caching query results and individual documents, and by carefully managing cache invalidation, you can dramatically improve API performance and reduce the load on your database. Remember to monitor your cache performance and adjust TTLs and invalidation strategies as your application's data access patterns evolve.

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

  • Orchestrating Serverless PHP 9 with AWS Lambda and API Gateway: A Deep Dive into Performance and Cost Optimization
  • Leveraging PHP 8.3 JIT and Vectorization for Extreme Performance in Laravel Applications
  • Leveraging PHP 9’s JIT Compiler and Concurrent Execution for High-Performance Laravel Microservices
  • Leveraging PHP 8.3 JIT and Vectorization for High-Throughput Microservices in a Laravel Ecosystem
  • Leveraging Laravel Octane and Docker Swarm for High-Performance, Scalable WordPress Headless Deployments

Categories

  • apache (1)
  • Business & Monetization (390)
  • Centos (4)
  • Comparisons & Decision Making (55)
  • Debian (2)
  • Debugging & Troubleshooting (664)
  • Desktop Applications (14)
  • DevOps (11)
  • DevOps & Cloud Scaling (962)
  • Django (1)
  • Laravel (6)
  • Migration & Architecture (192)
  • Mobile Applications (24)
  • MySQL (1)
  • Performance & Optimization (873)
  • PHP (19)
  • PHP Development (49)
  • Plugins & Themes (244)
  • Programming Languages (10)
  • Python (20)
  • Ruby on Rails (1)
  • Security & Compliance (650)
  • SEO & Growth (492)
  • Server (118)
  • Softwares (1)
  • Ubuntu (9)
  • Uncategorized (24)
  • VB6 & VB.NET (8)
  • Web Applications & Frontend (19)
  • Web Assembly (Wasm) (2)
  • WordPress (26)
  • WordPress Plugin Development (728)
  • WordPress Theme Development (357)

Recent Posts

  • Orchestrating Serverless PHP 9 with AWS Lambda and API Gateway: A Deep Dive into Performance and Cost Optimization
  • Leveraging PHP 8.3 JIT and Vectorization for Extreme Performance in Laravel Applications
  • Leveraging PHP 9's JIT Compiler and Concurrent Execution for High-Performance Laravel Microservices

Top Categories

  • DevOps & Cloud Scaling (962)
  • Performance & Optimization (873)
  • WordPress Plugin Development (728)
  • Debugging & Troubleshooting (664)
  • Security & Compliance (650)
  • SEO & Growth (492)

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