URL:     https://linuxfr.org/users/florian13/journaux/getset-redis-retire-le-ttl-et-la-cle-du-compteur-ne-meurt-plus
Title:   GETSET Redis retire le TTL, et la clé du compteur ne meurt plus
Authors: Florian13
Date:    2026-09-29T23:15:59+02:00
License: CC By-SA
Tags:    redis, programmation, ok_super et slop
Score:   -3


Salut 'Nal,

On a un compteur Redis, `session:{id}:billableSecondsPending`, qui accumule des secondes à facturer tant qu'une session tourne. Les clés de session vivent 15 minutes. Une fin normale les supprime tout de suite. Le TTL ne couvre que les sessions orphelines, crash ou coupure, quand plus personne n'appelle le ménage.

Le reset du compteur passait par `GETSET`. On lit la valeur, on la remet à zéro, un seul aller-retour, pour que deux appels concurrents ne ramassent pas le même solde. `GETSET` remplace aussi la valeur, et il n'a pas d'option pour garder l'expiration. La clé devient persistante. Redis documente ce comportement. Le ticket interne #601 le note comme cause de la fuite.

Si l'appel suivant qui repose un `EXPIRE` ne vient jamais (sauvegarde du contexte, ou nouvel incrément), la clé reste. Session déjà morte, compteur à zéro, Redis la garde. Je n'ai pas compté les clés en prod.

Le correctif est un script Lua. On lit. Si la clé existe, `SET` à `0` avec `KEEPTTL`, qui conserve le temps restant. Si elle n'existe pas, on ne touche à rien. Un `SET ... KEEPTTL` sur une clé absente la crée quand même, et sans TTL. La fuite reviendrait, juste plus tôt.

```lua
local prev = redis.call('GET', KEYS[1])
if prev then
  redis.call('SET', KEYS[1], '0', 'KEEPTTL')
end
return prev
```

L'incrément avait un trou du même genre, un cran avant. `INCRBY` puis `EXPIRE` dans un second appel : un plantage entre les deux, et la clé n'a plus de durée de vie. Les deux commandes tiennent dans le même script, TTL de 900 secondes :

```lua
redis.call('INCRBY', KEYS[1], tonumber(ARGV[1]))
redis.call('EXPIRE', KEYS[1], tonumber(ARGV[2]))
```

`GETSET` est déprécié depuis Redis 6.2, au profit de `SET` avec l'option `GET`. Ça ne sauve pas le TTL. Sans `KEEPTTL`, ou sans un nouveau délai, `SET` efface l'expiration déjà posée.
