[Redis] TTL 시스템 구조
Redis는 낮은 레이턴시의 고속 접근을 위해 사용되는 캐싱용 인메모리 데이터베이스다.
Redis는 캐시의 특성상, 캐시의 만료로 인한 삭제를 지원하기 위해 TTL을 제공한다.
사실 TTL은 그냥 저장공간을 아끼기 위해 약간 GC 개념으로 넣은 부가적인 기능일 뿐인데, 만료 동작 구현하기 편하다고 아무데나 박는 남용 사례가 자주 보이긴 한다. 이걸 보는 사람들은 그러지 않길 바란다.
그러면 TTL은 어떤 방식으로 구현될까?
사실 Redis의 TTL은 시간이 되자마자 실제로 딱 지우는건 아니다. 그런건 현실적으로 불가능하다.
제약
TTL 시스템은 실질적인 제약이 조금 존재한다. 그래서 로직을 TTL에 의존하는 것은 권장하지 않는다.
TTL의 동작 방식은 v2.4, v2.6 2가지를 기점으로 조금 달라졌다.
2.4 버전대에서는 문제가 많았다. 일단 만료 자체에 오차가 꽤 컸고, 최대 1초까지 지연될 수 있었다.
애초에 이 때는 TTL 시간 값을 유닉스 타임스탬프-초 단위로 관리했기 때문에 그랬다.
2.6 버전대부터는 TTL에 사용하는 타임스탬프 단위를 밀리초로 내렸기 때문에 만료 시간의 오차가 최대 1밀리초까지 내려갔다.
하지만 분명하게 오차가 존재하긴 하는 것이고, 마이크로세컨드 수준의 오차도 엄밀하게 발생해서는 안되는 상황이라면 이걸 기반으로 로직을 작성해서는 안된다.
TTL의 동작 방식
Redis는 수동적인 방법(passive way)과 능동적인 방법(active way)을 통해 TTL 데이터를 제거한다.
passive way
Redis는 기본적으로, TTL 시간이 지났다고 해서 데이터를 즉시 지우지는 않는다. 계속해서 모니터링하는건 너무 비효율적이기 때문이다.
그래서 기본적으로는 그냥 뒀다가

TTL이 지난 데이터에 대해 접근을 시도하면
그때 삭제하는 방식을 취한다.
active way
근데 접근된 것만 지워서는 문제가 많을 것이다.
쓰레기들이 쌓였는데, 영영 접근하지 않는다면 어떻게 되겠는가? TTL이 지난 데이터들이 우주쓰레기처럼 메모리를 점유하고 있을 것이다.
그래서 Redis에는 접근을 하지 않더라도 주기적으로 돌면서 TTL 체크를 하는 프로세스가 존재한다.
근데 이게 뭐 풀스캔을 하면서 모든 TTL 만료를 찾는건 아니고, 랜덤으로 일부만 본다.
그리고 이것도 버전마다 동작이 다르다.
before Redis 6.0
예전에는 확률적 방법에 의존해서 TTL 만료를 제거했다. 이런 식이다.
-
TTL 데이터 20개를 무작위 확인
-
만료된걸 전부 삭제
-
만료 비율이 25% 이상이었다면 한번 더 반복
이건 확률적으로 대충 25% 정도는 쓰레기 메모리가 잔존할 수도 있다는 것을 의미하기도 한다.
그래서 트위터는 이거 때문에 성능 문제가 되었던 적도 있다.
since Redis 6.0 (2020)
6.0 버전부터는 조금 더 정교한 방법을 통해 만료된 키를 찾아서 정리한다.
사실 대단한 기술을 적용한 것은 아니다. 값을 TTL 기준으로 정렬해서 TTL 지난걸 빠르게 찾고 제거하는 것이다.
애초에 이렇게 했어야 했던게 아닌가 싶다.
참조
https://redis.io/docs/latest/commands/expire/
https://www.pankajtanwar.in/blog/how-redis-expires-keys-a-deep-dive-into-how-ttl-works-internally-in-redis
https://blog.x.com/engineering/en_us/topics/infrastructure/2019/improving-key-expiration-in-redis
https://www.digitalocean.com/community/cheatsheets/how-to-expire-keys-in-redis
https://redis.io/blog/diving-into-redis-6/