Aller au contenu principal

Patterns

Stratégies de cache Redis (balance, stats, webhooks)

TTL court + invalidation event-driven sur webhooks

Redis est utilisé chez Scell.io pour 4 cas. (1) Cache balance tenant : la balance change après chaque facture/recharge, TTL court (60s) + invalidation immédiate sur webhook `balance.updated`. (2) Cache stats agrégées : nombre de factures par mois, totaux TVA, top clients ; calcul coûteux, TTL 5 min, invalidation sur `invoice.created` / `invoice.cancelled`. (3) Idempotency keys (24h TTL, voir pattern dédié). (4) Rate limiting : sliding window avec `INCR + EXPIRE`. Pattern critique : NE JAMAIS cacher une donnée fiscale sans la chaîne d'invalidation correspondante. Le cache balance qui sert un montant obsolète provoque des doubles débits ou des refus de transaction. La règle `cache → webhook → invalidate` doit être documentée et testée. Anti-pattern : cache write-through vers Redis depuis l'application (incohérence si le service crash entre DB et Redis) ; préférer cache-aside (lecture cache miss → DB → cache write) avec invalidation event-driven sur webhook.

À retenir

  • Balance tenant : TTL 60s + invalidation sur webhook balance.updated
  • Stats agrégées : TTL 5min + invalidation sur invoice.created/cancelled
  • Idempotency keys : TTL 24h
  • Rate limit : sliding window via INCR + EXPIRE
  • Pattern : cache-aside, jamais write-through pour données fiscales

Exemple de code

import { createClient } from 'redis';
const redis = createClient();

// Cache-aside avec invalidation event-driven
async function getBalance(tenantId: string): Promise<number> {
  const key = `balance:${tenantId}`;
  const cached = await redis.get(key);
  if (cached !== null) return Number(cached);
  const fresh = await db.query('SELECT balance FROM tenants WHERE id = $1', [tenantId]);
  await redis.set(key, String(fresh), { EX: 60 }); // 60s TTL safety net
  return fresh;
}

// Webhook handler — invalidation immédiate
async function onBalanceUpdated(event: { tenant_id: string }) {
  await redis.del(`balance:${event.tenant_id}`);
  await redis.del(`stats:${event.tenant_id}:current_month`);
}

// Rate limit sliding window
async function checkRateLimit(apiKey: string, limit = 1000, windowSec = 60): Promise<boolean> {
  const key = `rl:${apiKey}:${Math.floor(Date.now() / 1000 / windowSec)}`;
  const count = await redis.incr(key);
  if (count === 1) await redis.expire(key, windowSec);
  return count <= limit;
}

Voir aussi

Vos préférences cookies

Nous utilisons des cookies pour améliorer votre expérience. Les cookies essentiels sont toujours actifs. Politique cookies.