
August 22, 2026
12 min read
By Kokil Thapa | Last reviewed: September 2026
Your MySQL queries will eventually become the bottleneck on any growing site. For teams running Redis Nepal setups on Kathmandu VPS boxes or regional cloud instances, picking the right caching pattern matters more than installing Redis itself. I've seen legal-tech portals and eCommerce stores add Redis without a plan—and ship stale inventory counts or outdated lawyer listings that hurt trust faster than slow pages ever did. This guide covers the Redis caching patterns for web apps that actually hold up under real traffic.
A lawyer directory can tolerate five minutes of staleness. A gift-card balance cannot. Before you wire Redis into Laravel 12 on PHP 8.4, align cache design with your API response structure and your broader performance plan. The patterns below work on a single Ubuntu VPS in Nepal or on multi-server cloud deployments—the logic stays the same even when the infrastructure scales.
How does the Cache-Aside pattern work in Laravel with Redis?
Cache-Aside (lazy loading) is the safest default for PHP apps. Your code checks Redis first. On a hit, it returns immediately. On a miss, it queries MySQL, stores the result with a TTL, and returns it. If Redis dies, the app still works—it just hits the database more often.
In Laravel 12, wrap the check-set-return cycle in Cache::remember(). The closure runs only on a miss:
<?php
// app/Services/LawyerDirectoryService.php
public function getLawyersByDistrict(string $district): array
{
$cacheKey = "lawyers:district:{$district}";
return Cache::remember($cacheKey, now()->addHour(), function () use ($district) {
return Lawyer::where('district', $district)
->with(['specializations', 'reviews'])
->orderBy('name')
->get()
->toArray(); // Cache arrays, not Eloquent models
});
} Never cache raw Eloquent model instances. Serialized models break when you add columns or change relationships between deploys. On a legal-tech portal I maintain, switching to cached arrays removed an entire class of post-deployment errors. For directory sites built by a Laravel developer in Nepal, this one habit saves hours of debugging.
Stopping the thundering herd
When a hot key expires, hundreds of requests can miss at once and hammer MySQL. Use Cache::lock() so one worker rebuilds while others wait:
$key = 'lawyers:district:kathmandu';
$value = Cache::lock("lock:{$key}", 10)->block(5, function () use ($key) {
return Cache::remember($key, now()->addHour(), function () {
return Lawyer::where('district', 'kathmandu')->get()->toArray();
});
}); Without this guard, one expired listing page during Dashain traffic can cascade into a database outage. Pair Cache-Aside with sensible TTLs—see the database query caching guide for TTL selection rules.
When should you use Write-Through vs Write-Behind caching?
Cache-Aside accepts brief staleness. Write-Through and Write-Behind decide how tightly your Redis layer tracks writes. The trade-off is consistency versus write latency.
| Criteria | Write-Through | Write-Behind |
|---|---|---|
| Consistency | Strong — cache updated in same request | Eventual — queue worker updates cache later |
| Write latency | Higher — DB and Redis both block | Lower — only DB write blocks the user |
| Data loss risk | Low — transaction rolls back on Redis failure | Moderate — failed queue job leaves stale cache |
| Best for | Stock counts, gift-card balances, sessions | View counters, activity feeds, analytics |
| Laravel hook | Service layer inside DB transaction | Queued job after commit |
For eCommerce inventory, Write-Through is non-negotiable. Overselling because Redis showed stale stock destroys customer trust. On a gift-card platform, coupling cache updates inside the transaction prevented double-redemption during festival spikes.
<?php
// app/Services/InventoryService.php
public function decrementStock(int $productId, int $quantity): bool
{
return DB::transaction(function () use ($productId, $quantity) {
$product = Product::lockForUpdate()->find($productId);
if ($product->stock < $quantity) {
throw new InsufficientStockException();
}
$product->decrement('stock', $quantity);
Cache::put(
"product:{$productId}:stock",
$product->fresh()->stock,
now()->addDay()
);
return true;
});
} Write-Behind suits counters and feeds where a few seconds of lag is acceptable. Dispatch a job after the DB commit. Always keep an Artisan command that rebuilds hot keys from MySQL—queue failures happen on every long-running project eventually. Our grocery delivery platform uses Write-Through for stock and Write-Behind for order-count badges.
How do you handle Redis cache invalidation for related data?
Storing data is easy. Knowing when to delete it is the hard part. Updating one lawyer profile may require clearing district lists, specialization filters, and paginated result sets. Individual key deletion does not scale.
Laravel cache tags group related keys under shared namespaces. Tags work only with Redis or Memcached—not file or database drivers. Compare drivers in the Redis vs Memcached cache tags article.
<?php
// Store with tags
Cache::tags(['lawyers', 'district:kathmandu', 'page:1'])
->put('lawyers:list:kathmandu:1', $data, now()->addHour());
// Flush everything tagged 'lawyers'
Cache::tags(['lawyers'])->flush();
// Surgical flush for one district
Cache::tags(['district:kathmandu'])->flush(); Production warning: tag flush runs a SCAN that can block Redis on large key sets. For high-volume catalogs, use versioned keys instead. Store a version counter at lawyers:version. Append it to every list key: lawyers:list:kathmandu:v42. Increment the counter on update—old versions expire via TTL. This gives O(1) invalidation with no blocking scan.
On the Notary Nepal portal, we invalidate service-area pages by bumping a district version counter rather than flushing broad tag groups during business hours.
What Redis configuration prevents production failures on Nepal VPS hosting?
Pattern choice means nothing if Redis itself is misconfigured. Most Redis Nepal deployments I manage run on the same Ubuntu VPS as PHP-FPM—typical for Rs 3,000–8,000/month (~USD 22–60) hosting budgets. These settings have kept shared EC2 instances stable across sister legal-tech sites.
- maxmemory-policy allkeys-lru — evicts cold keys instead of throwing OOM errors when RAM fills.
- maxmemory — cap at 60–70% of available RAM. Leave headroom for PHP-FPM and MySQL on single-server setups.
- tcp-keepalive 60 — detects dead connections before they pile up behind load balancers.
- timeout 300 — closes idle client connections after five minutes.
- save "" — disable RDB snapshots for pure cache workloads. Persistence adds latency you do not need for page fragments.
- lazyfree-lazy-eviction yes — moves eviction to background threads, avoiding latency spikes.
Install Redis 8.10 on Ubuntu following the Ubuntu server setup for PHP apps. Bind to 127.0.0.1 on single-server setups. Never expose port 6379 to the public internet.
# /etc/redis/redis.conf (cache-only workload)
maxmemory 512mb
maxmemory-policy allkeys-lru
save ""
tcp-keepalive 60
timeout 300
lazyfree-lazy-eviction yes
bind 127.0.0.1 ::1 In Laravel's config/database.php, set explicit timeouts so a hung Redis connection does not block PHP-FPM workers indefinitely:
'redis' => [
'client' => env('REDIS_CLIENT', 'phpredis'),
'default' => [
'host' => env('REDIS_HOST', '127.0.0.1'),
'password' => env('REDIS_PASSWORD'),
'port' => env('REDIS_PORT', 6379),
'database' => env('REDIS_DB', 0),
'read_timeout' => 2,
'retry_interval' => 100,
'prefix' => env('CACHE_PREFIX', 'app_') . '_',
],
'cache' => ['database' => env('REDIS_CACHE_DB', 1)],
'session' => ['database' => env('REDIS_SESSION_DB', 2)],
], Separate logical databases—or better, key prefixes—for cache, sessions, and queues. Laravel Horizon expects its own Redis connection; see the Laravel queues with Redis production guide. Tune PHP-FPM pool sizes alongside Redis memory—both compete for RAM on a VPS. The PHP-FPM tuning guide covers worker counts that pair well with a 512 MB Redis instance.
The official Redis memory optimization documentation explains eviction policies in detail. Cross-check your redis.conf against it after every upgrade.
How do you monitor Redis cache effectiveness in production?
You cannot tune what you do not measure. Track these metrics on every Redis Nepal deployment:
- Hit rate — target above 90% for read-heavy pages. Below 80% means TTLs are too short, keys are poorly named, or
maxmemoryis too small. - Evicted keys — run
INFO stats | grep evicted_keys. Non-zero evictions mean your working set exceeds RAM. - p99 latency — local connections should stay under 2 ms. Spikes often trace to blocking commands like
KEYS *or synchronousFLUSHDB. - Memory fragmentation ratio — values above 1.5 suggest restart or active defragmentation during a maintenance window.
- Connected clients — sudden spikes may indicate connection leaks in PHP-FPM or Horizon workers.
Quick hit-rate check from the shell:
redis-cli INFO stats | egrep 'keyspace_hits|keyspace_misses'
# hit_rate = hits / (hits + misses) Laravel Telescope shows application-level cache calls. For infrastructure metrics, export to Prometheus with redis_exporter and alert on hit-rate drops. If you serialize large API payloads, validate JSON structure with our JSON formatter tool before caching—malformed cached blobs are painful to debug.
Compare your before-and-after numbers against the Redis caching speed guide for Laravel. Pair cache metrics with page-speed work from speed optimization services so improvements show up in Core Web Vitals, not just server dashboards.
Which Redis patterns fit Nepal eCommerce and legal-tech workloads?
Not every page needs the same pattern. Match workload type to pattern and TTL:
| Workload | Pattern | TTL guidance | Invalidation |
|---|---|---|---|
| Product catalog listings | Cache-Aside | 15–60 minutes | Versioned keys on product update |
| Live stock / gift-card balance | Write-Through | 24 hours (refreshed on write) | Synchronous on every mutation |
| Lawyer directory filters | Cache-Aside + lock | 1–6 hours | Tag flush by district |
| Page view counters | Write-Behind | 5 minutes | Rebuild command nightly |
| API rate-limit buckets | Redis INCR + EXPIRE | Sliding window | Automatic via TTL |
| Session data | Redis native store | Session lifetime | Logout or expiry |
For multi-server Laravel setups, Redis also backs session storage and queue workers. That triple duty is why memory planning matters on budget VPS hosts common in Nepal. Professional Linux system administration helps right-size the box before you add Horizon and cache on the same instance.
If you are building a new storefront, the e-commerce development service page outlines how caching fits catalog, cart, and checkout architecture from day one—not as a late patch.
Key Takeaways
- Start with Cache-Aside and
Cache::remember()—cache arrays, not Eloquent models, and add locks on hot keys. - Use Write-Through inside DB transactions for inventory, balances, and any data where staleness costs money.
- Prefer versioned keys over broad tag flushes when your Redis key count exceeds a few hundred thousand.
- Set
maxmemory-policy allkeys-lru, bind to localhost, and cap Redis at 60–70% of VPS RAM. - Monitor hit rate and evictions weekly—below 90% hit rate usually means TTL or memory problems, not application bugs.
- Separate Redis logical databases for cache, queues, and sessions to simplify debugging and memory accounting.
People Also Ask
Is Redis free to use for web apps in Nepal?
Yes. Redis is open source and free to install on your own VPS or cloud server. Managed Redis from AWS, DigitalOcean, or similar providers adds a monthly fee—often USD 15–50 for small instances—but saves you patching and failover work. For most Nepali SMB sites on a single VPS, self-hosted Redis 8.10 on Ubuntu costs nothing beyond the RAM you allocate.
What is the difference between Redis and Memcached for Laravel caching?
Both work as Laravel cache drivers. Redis supports richer data structures, persistence options, pub/sub, and cache tags. Memcached is simpler and slightly faster for plain key-value gets, but lacks tags and queues. For Laravel apps using Horizon, sessions, and tagged invalidation, Redis is the practical choice. See the dedicated comparison in our cache tags article.
How much RAM does Redis need for a Laravel website?
A typical SMB Laravel site with page caching and sessions runs well on 256–512 MB of dedicated Redis memory. High-traffic eCommerce catalogs with large fragment caches may need 1–2 GB. Rule of thumb: measure your working set with INFO memory after a week of traffic, then set maxmemory to 120% of that peak. Leave the rest of VPS RAM for PHP-FPM and MySQL.
Can Redis replace MySQL for storing application data?
No. Redis is an in-memory store meant for cache, sessions, queues, and rate limiting—not authoritative data. Always treat MySQL or PostgreSQL as the source of truth. Redis data can be evicted under memory pressure unless you disable eviction, which then risks OOM crashes. Use Write-Through or Write-Behind patterns to keep Redis in sync, not as a standalone database.
Ship Redis caching patterns that survive real traffic
Effective Redis Nepal deployments are about pattern discipline, not just installing another service. Start with Cache-Aside for reads. Add Write-Through only where business rules demand it. Plan invalidation before you cache—not after the first stale-data bug report. Configure eviction, timeouts, and memory caps before launch, and watch hit rates as closely as response times.
If your Laravel app is slow, serving stale data, or running Redis on the same overloaded VPS as everything else, contact us for a caching architecture review. I regularly untangle organic cache layers on production apps serving Nepali and international users—and the fix is usually pattern selection, not more hardware. You can also reach out directly to discuss your stack. For ongoing tuning, explore testing and optimization services or read the Laravel docs on cache configuration for driver-specific options.
Frequently Asked Questions
0 Comments
Leave a comment
Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

