5 Redis Patronen voor Real-Time Laravel Apps Die Schalen

Fred· AI Engineer & Developer Educator6 min read

Na het migreren van Redis naar KeyDB en weer terug terwijl we miljarden aan logistieke transacties verwerkten, heb ik geleerd welke Redis patronen werken op schaal. Hier zijn 5 beproefde patronen die je vandaag kunt implementeren.

1. Pipeline Alles (10x Prestatieverbetering)

Slecht Patroon:

// Dit creëert 1000 netwerk round trips!
foreach ($shipments as $shipment) {
    Redis::set("shipment:{$shipment->id}", $shipment->toJson());
    Redis::expire("shipment:{$shipment->id}", 3600);
}

Productie Patroon:

// Eén netwerkaanroep, atomische uitvoering
Redis::pipeline(function ($pipe) use ($shipments) {
    foreach ($shipments as $shipment) {
        $pipe->setex(
            "shipment:{$shipment->id}",
            3600,
            $shipment->toJson()
        );
    }
});

Echte Impact: Verminderde onze bulk import tijd van 45 seconden naar 4 seconden voor 10k records.

2. Implementeer Sliding Window Rate Limiting

Vergeet fixed windows die thundering herds veroorzaken. Dit is wat we in productie gebruiken:

class RateLimiter
{
    public static function attempt($key, $max = 60, $decay = 60)
    {
        $key = "rate_limit:{$key}";
        $now = microtime(true);
        $window = $now - $decay;

        Redis::pipeline(function ($pipe) use ($key, $now, $window, $max) {
            // Verwijder oude entries
            $pipe->zremrangebyscore($key, 0, $window);

            // Voeg huidige request toe
            $pipe->zadd($key, $now, $now);

            // Tel requests in window
            $pipe->zcard($key);

            // Stel expiry in
            $pipe->expire($key, $decay + 1);
        });

        $results = $pipe->execute();
        $count = $results[2];

        return $count <= $max;
    }
}

// Gebruik in middleware
if (!RateLimiter::attempt("api:{$request->ip()}", 100, 60)) {
    return response('Rate limit exceeded', 429);
}

Waarom Het Werkt: Geen pieken bij window-grenzen, eerlijke verdeling, zelfreinigend.

3. Cache Invalidatie met Tags (De Juiste Manier)

Laravel's cache tags zijn geweldig totdat je granulaire controle nodig hebt. Dit is ons patroon:

class SmartCache
{
    public static function rememberWithDependencies($key, $tags, $ttl, $callback)
    {
        // Sla de hoofdcache op
        $value = Cache::remember($key, $ttl, $callback);

        // Volg dependencies in Redis sets
        Redis::pipeline(function ($pipe) use ($key, $tags) {
            foreach ($tags as $tag) {
                $pipe->sadd("cache_tag:{$tag}", $key);
                $pipe->expire("cache_tag:{$tag}", 86400); // 1 dag
            }
        });

        return $value;
    }

    public static function invalidateTag($tag)
    {
        $keys = Redis::smembers("cache_tag:{$tag}");

        if (!empty($keys)) {
            // Verwijder alle getagde keys in één pipeline
            Redis::pipeline(function ($pipe) use ($keys, $tag) {
                foreach ($keys as $key) {
                    $pipe->del($key);
                }
                $pipe->del("cache_tag:{$tag}");
            });
        }
    }
}

// Gebruik
$metrics = SmartCache::rememberWithDependencies(
    'dashboard.metrics',
    ['shipments', 'deliveries', "customer:{$customerId}"],
    300, // 5 minuten
    fn() => $this->calculateExpensiveMetrics()
);

// Invalideer alle shipment-gerelateerde caches
SmartCache::invalidateTag('shipments');

Productie Winst: Verminderde cache misses met 73% en elimineerde cascaderende invalidaties.

4. Gedistribueerde Locks Die Niet Deadlocken

Na het verliezen van $50k door een race condition, implementeerden we dit kogelvrije locking:

class DistributedLock
{
    public static function acquire($resource, $timeout = 10)
    {
        $lockKey = "lock:{$resource}";
        $identifier = uniqid(gethostname(), true);

        // Atomische set if not exists met expiry
        $acquired = Redis::set(
            $lockKey,
            $identifier,
            'NX', // Alleen zetten als niet bestaat
            'EX', // Verloopt na X seconden
            $timeout
        );

        if ($acquired) {
            return $identifier;
        }

        // Controleer of lock stale is (backup mechanisme)
        $lockHolder = Redis::get($lockKey);
        if (!$lockHolder) {
            // Lock verlopen tussen commando's, probeer opnieuw
            return self::acquire($resource, $timeout);
        }

        return false;
    }

    public static function release($resource, $identifier)
    {
        $lockKey = "lock:{$resource}";

        // Lua script voor atomische check en delete
        $script = "
            if redis.call('get', KEYS[1]) == ARGV[1] then
                return redis.call('del', KEYS[1])
            else
                return 0
            end
        ";

        return Redis::eval($script, 1, $lockKey, $identifier);
    }

    public static function withLock($resource, $callback, $timeout = 10)
    {
        $identifier = self::acquire($resource, $timeout);

        if (!$identifier) {
            throw new LockTimeoutException("Could not acquire lock for {$resource}");
        }

        try {
            return $callback();
        } finally {
            self::release($resource, $identifier);
        }
    }
}

// Gebruik voor betalingsverwerking
DistributedLock::withLock("payment:{$invoice->id}", function () use ($invoice) {
    // Verwerk betaling veilig
    if ($invoice->isPaid()) {
        return; // Idempotente check
    }

    $invoice->processPayment();
    $invoice->markAsPaid();
});

Kritiek: Het Lua script zorgt ervoor dat we alleen ONZE lock vrijgeven, waardoor we voorkomen dat we per ongeluk iemand anders' lock vrijgeven.

5. Real-Time Metrics met HyperLogLog

Volg unieke bezoekers/events zonder geheugenexplosie:

class MetricsTracker
{
    public static function trackUnique($metric, $identifier, $window = 3600)
    {
        $key = "metric:{$metric}:" . floor(time() / $window);

        // HyperLogLog voegt unieke items toe met O(1) ruimte
        Redis::pfadd($key, $identifier);
        Redis::expire($key, $window * 2); // Bewaar 2 windows

        return Redis::pfcount($key);
    }

    public static function getCardinality($metric, $windows = 1, $windowSize = 3600)
    {
        $keys = [];
        $now = time();

        for ($i = 0; $i < $windows; $i++) {
            $keys[] = "metric:{$metric}:" . floor(($now - ($i * $windowSize)) / $windowSize);
        }

        // Merge meerdere HyperLogLogs
        return Redis::pfcount($keys);
    }

    public static function trackAndBroadcast($event, $userId)
    {
        // Volg uniek event
        $count = self::trackUnique("event:{$event}:users", $userId);

        // Volg per-minuut rate
        $rate = self::trackUnique("event:{$event}:rate", uniqid(), 60);

        // Broadcast als drempel bereikt
        if ($rate > 1000) {
            broadcast(new HighTrafficAlert($event, $rate));
        }

        return $count;
    }
}

// Gebruik
$uniqueVisitors = MetricsTracker::trackUnique('page.visits', $request->ip());
$dailyActive = MetricsTracker::getCardinality('user.active', 24, 3600);

// Volg API-gebruik zonder geheugenproblemen
MetricsTracker::trackAndBroadcast('api.call', $user->id);

Geheugenbesparing: Het volgen van 10M unieke gebruikers kost ~12KB in plaats van 40MB met traditionele sets.

Bonus: Redis Geheugenoptimalisatie Checklist

Uit ons productie playbook:

// 1. Gebruik compressie voor grote waarden
Redis::setex(
    "large:{$id}",
    3600,
    gzcompress(json_encode($data), 9)
);

// 2. Gebruik hashes voor kleine objecten (90% geheugenbesparing!)
Redis::hset("user:{$id}", [
    'name' => $user->name,
    'email' => $user->email,
    'status' => $user->status
]);

// 3. Stel agressieve expiries in
Redis::setex($key, 300, $value); // 5 min standaard, niet 1 uur

// 4. Gebruik SCAN in plaats van KEYS
$cursor = 0;
do {
    [$cursor, $keys] = Redis::scan($cursor, 'MATCH', 'shipment:*', 'COUNT', 100);
    // Verwerk $keys
} while ($cursor !== 0);

// 5. Monitor geheugengebruik
$info = Redis::info('memory');
if ($info['used_memory'] > 1073741824) { // 1GB
    alert("Redis memory critical: {$info['used_memory_human']}");
}

De Dure Lessen

  1. Altijd expiries instellen - Eén ontbrekende TTL consumeerde 8GB RAM
  2. Pipeline of perish - Netwerklatentie stapelt snel op
  3. Gebruik de juiste datastructuur - HyperLogLog bespaarde ons $2k/maand aan geheugen
  4. Lock correct - Race conditions in financiële systemen = rechtszaken
  5. Monitor alles - Je kunt niet fixen wat je niet meet

Deze patronen verwerken 50k requests/seconde in productie. Begin met pipelines en correct locken—ze lossen 80% van je Redis prestatieproblemen op.

Beheers Laravel met Echte Projecten

Wil je deze Redis patronen implementeren in een echte applicatie? Bouw productie-klare Laravel projecten vanaf nul:

Elke tutorial bevat AI-geassisteerde prompts om je te begeleiden bij het bouwen van schaalbare applicaties die Redis effectief gebruiken.

Vragen over het implementeren van deze patronen? Laat een reactie achter - ik heb dat probleem waarschijnlijk al om 3 uur 's nachts gedebugd.

Fred

Fred

AUTHOR

Full-stack developer with 10+ years building production applications. I write about cloud deployment, DevOps, and modern web development from real-world experience.

Need a developer who gets it?

POC builds, vibe-coded fixes, and real engineering. Let's talk.

Hire Me →