5 wzorow Redis dla aplikacji Laravel w czasie rzeczywistym ktore skaluja sie

Fred· AI Engineer & Developer Educator6 min read

Po migracji z Redis do KeyDB i z powrotem podczas przetwarzania miliardow w transakcjach logistycznych, nauczylem sie ktore wzory Redis dzialaja w skali. Oto 5 sprawdzonych w boju wzorow ktore mozesz wdrozyc dzisiaj.

1. Pipeline wszystko (10x wzrost wydajnosci)

Zly wzor:

// To tworzy 1000 polaczen sieciowych!
foreach ($shipments as $shipment) {
    Redis::set("shipment:{$shipment->id}", $shipment->toJson());
    Redis::expire("shipment:{$shipment->id}", 3600);
}

Wzor produkcyjny:

// Jedno polaczenie sieciowe, atomowe wykonanie
Redis::pipeline(function ($pipe) use ($shipments) {
    foreach ($shipments as $shipment) {
        $pipe->setex(
            "shipment:{$shipment->id}",
            3600,
            $shipment->toJson()
        );
    }
});

Rzeczywisty wplyw: Zredukowalismy czas masowego importu z 45 sekund do 4 sekund dla 10k rekordow.

2. Implementuj ograniczanie predkosci z oknem przesuwnym

Zapomnij o stalych oknach ktore powoduja lawinowe obciazenia. Oto czego uzywamy w produkcji:

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) {
            // Usun stare wpisy
            $pipe->zremrangebyscore($key, 0, $window);

            // Dodaj aktualne zadanie
            $pipe->zadd($key, $now, $now);

            // Policz zadania w oknie
            $pipe->zcard($key);

            // Ustaw wygasniecie
            $pipe->expire($key, $decay + 1);
        });

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

        return $count <= $max;
    }
}

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

Dlaczego to dziala: Brak skokow na granicach okien, sprawiedliwa dystrybucja, samoczyszczenie.

3. Uniewaznanie cache z tagami (Wlasciwy sposob)

Tagi cache Laravel sa swietne dopoki nie potrzebujesz szczegolowej kontroli. Oto nasz wzor:

class SmartCache
{
    public static function rememberWithDependencies($key, $tags, $ttl, $callback)
    {
        // Zapisz glowny cache
        $value = Cache::remember($key, $ttl, $callback);

        // Sledz zaleznosci w 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 dzien
            }
        });

        return $value;
    }

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

        if (!empty($keys)) {
            // Usun wszystkie otagowane klucze w jednym pipeline
            Redis::pipeline(function ($pipe) use ($keys, $tag) {
                foreach ($keys as $key) {
                    $pipe->del($key);
                }
                $pipe->del("cache_tag:{$tag}");
            });
        }
    }
}

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

// Uniewaznic wszystkie cache zwiazane z przesylkami
SmartCache::invalidateTag('shipments');

Zysk produkcyjny: Zredukowalismy brakujace trafienia cache o 73% i wyeliminowalismy kaskadowe uniewaznenia.

4. Blokady rozproszone ktore nie deadlockuja

Po stracie 50 tysiecy dolarow przez race condition, wdrozylismy te kuloodporne blokady:

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

        // Atomowe ustawienie jesli nie istnieje z wygasaniem
        $acquired = Redis::set(
            $lockKey,
            $identifier,
            'NX', // Tylko ustaw jesli nie istnieje
            'EX', // Wygasa po X sekundach
            $timeout
        );

        if ($acquired) {
            return $identifier;
        }

        // Sprawdz czy blokada jest przestarzala (mechanizm zapasowy)
        $lockHolder = Redis::get($lockKey);
        if (!$lockHolder) {
            // Blokada wygasla miedzy poleceniami, sprobuj ponownie
            return self::acquire($resource, $timeout);
        }

        return false;
    }

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

        // Skrypt Lua dla atomowego sprawdzenia i usuniecia
        $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);
        }
    }
}

// Uzycie do przetwarzania platnosci
DistributedLock::withLock("payment:{$invoice->id}", function () use ($invoice) {
    // Przetwarzaj platnosc bezpiecznie
    if ($invoice->isPaid()) {
        return; // Idempotentne sprawdzenie
    }

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

Krytyczne: Skrypt Lua zapewnia ze zwalniamy tylko NASZA blokade, zapobiegajac przypadkowemu zwolnieniu blokady kogos innego.

5. Metryki w czasie rzeczywistym z HyperLogLog

Sledz unikalnych odwiedzajacych/zdarzenia bez wybuchu pamieci:

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

        // HyperLogLog dodaje unikalne elementy z przestrzenia O(1)
        Redis::pfadd($key, $identifier);
        Redis::expire($key, $window * 2); // Zachowaj 2 okna

        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);
        }

        // Polacz wiele HyperLogLog
        return Redis::pfcount($keys);
    }

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

        // Sledz czestotliwosc na minute
        $rate = self::trackUnique("event:{$event}:rate", uniqid(), 60);

        // Rozglos jesli przekroczono prog
        if ($rate > 1000) {
            broadcast(new HighTrafficAlert($event, $rate));
        }

        return $count;
    }
}

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

// Sledz uzycie API bez problemow z pamiecia
MetricsTracker::trackAndBroadcast('api.call', $user->id);

Oszczednosci pamieci: Sledzenie 10M unikalnych uzytkownikow zajmuje ~12KB zamiast 40MB z tradycyjnymi setami.

Bonus: Lista kontrolna optymalizacji pamieci Redis

Z naszego podrecznika produkcyjnego:

// 1. Uzyj kompresji dla duzych wartosci
Redis::setex(
    "large:{$id}",
    3600,
    gzcompress(json_encode($data), 9)
);

// 2. Uzyj hashow dla malych obiektow (90% oszczednosci pamieci!)
Redis::hset("user:{$id}", [
    'name' => $user->name,
    'email' => $user->email,
    'status' => $user->status
]);

// 3. Ustaw agresywne wygasanie
Redis::setex($key, 300, $value); // 5 min domyslnie, nie 1 godzina

// 4. Uzyj SCAN zamiast KEYS
$cursor = 0;
do {
    [$cursor, $keys] = Redis::scan($cursor, 'MATCH', 'shipment:*', 'COUNT', 100);
    // Przetwarzaj $keys
} while ($cursor !== 0);

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

Drogie lekcje

  1. Zawsze ustawiaj wygasanie - Jeden brakujacy TTL skonsumowa 8GB RAM
  2. Pipeline albo przepadnij - Opoznienie sieci sumuje sie szybko
  3. Uzyj wlasciwej struktury danych - HyperLogLog oszczedzil nam 2 tys. dolarow miesiecznie na pamieci
  4. Blokuj prawidlowo - Race conditions w systemach finansowych = pozwy
  5. Monitoruj wszystko - Nie mozesz naprawic czegos czego nie mierzysz

Te wzory obsluguja 50k zadan na sekunde w produkcji. Zacznij od pipeline'ow i prawidlowego blokowania - to rozwiaze 80% twoich problemow z wydajnoscia Redis.

Opanuj Laravel z prawdziwymi projektami

Chcesz wdrozyc te wzory Redis w prawdziwej aplikacji? Buduj gotowe do produkcji projekty Laravel od zera:

Kazdy samouczek zawiera prompty wspomagane przez AI ktore przeprowadza cie przez budowanie skalowalnych aplikacji ktore efektywnie uzywaja Redis.

Masz pytania o implementacje tych wzorow? Zostaw komentarz - prawdopodobnie debugowalem ten problem o 3 w nocy.

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 →