Additionally, the expire/ttl/get/set in Redis is incredibly easy to use (and abuse, hence the OP article). Some team's criteria is limiting the amount of moving parts - and that's great. Don't use Redis and use a relational database for everything such as what you mentioned. Use it as a queue, a cache, a message broker, etc..
Other teams may care less about an extra moving part if it means their code will look simpler and they leverage relational databases for their more common usecases.
You could also use a library like PG boss to handle the cleanup task.
It was a government project, written by one team (us) to be maintained by another.
The data that needed to be expunged was user signup data, upon completion the record was sent to a CRM and the Redis record destroyed. If the signup wasn't finished it's automatically removed after 12 hours.
Referential integrity wasn't really a problem, emails are unique and if we clash the two records are auto-merged by the CRM.
Setting up scheduled tasks, triggers, partitioning, cron, etc, is just more things that can go wrong. If they go wrong _and_ go unnoticed we end up with piles of data we shouldn't have. That would be many different kinds of bad.
Doing `redis.set(k, v, ex: 12.hours)` or whatever is just easier.