하루종일 열심히 이웃사이의 좋아요 기능에 대해 Redis를 어떤 전략으로 사용할 것인지 고민해봤다. 우선 어떤 캐싱 전략이 있는지 알아보기부터 시작했다. 캐싱 전략을 간략하게 정리해보면 다음과 같다.
이렇게 다양한 전략들이 있는 것을 알게된 후 본 서비스에는 어떤 요구사항이 있기 때문에 어떤 전략을 사용해야 할지, 해당 전략 사용 시 장단점이 뭔지, 구현은 어떻게 되는지에 대해 아래와 같은 아주 길고 긴 고민(뇌절)을 했다.
사실 이 모든 것들은 지금의 작은 규모에서는 크게 상관이 없고, 대규모의 상황을 대비하는 것인데, Redis 를 사용해서 캐싱을 하는 것 자체가 대규모 트래픽에서도 좋은 성능을 유지하고자 하는 것이니 이왕 하는거 열심히 머리를 싸매봤다.
그래서 내린 결론은 다음과 같다.
- 대부분의 sns 서비스에서 빈번하게 사용되는 좋아요 기능을 누락되는 요청없이 신속정확하게 처리하기 위해 Redis를 사용한다.
- 많은 쓰기를 누락없이 처리하고, 정합성 있는 최신 정보를 제공하기 위해 Read Through & Write Back 전략을 사용한다. 즉, 캐시를 통해서만 읽고 쓴다.
- 캐시 데이터 삭제는 expire 설정으로 처리하고, 주기적으로 캐시에서 디비로 동기화한다.
- 캐시에서 디비로 동기화할 대상을 선별하기 위해 캐시에 수정된 데이터 키 목록인 board_key_list를 저장한다.
- 디비에 반영되기 전에 소멸되는 캐시 데이터를 최소화하기 위해 expire는 디비 동기화 주기보다 길게 설정한다. (너무 길면 캐시 메모리 용량 오버헤드 주의)
- 디비 반영 주기는 처리 시간이 길 것을 고려하여 너무 짧지 않게 정한다.
읽기와 쓰기 데이터를 모두 캐시에 올려서 사용하다보니 너무 많은 데이터를 메모리에 두는 게 아닌가 하는 생각이 들었다. 하지만, 기능 특성상, 쓰기 데이터는 모두 읽기 데이터에 포함되는 것들이고,(게시물을 봐야 좋아요를 누를 수 있으니) 본 서비스에서 캐시를 사용자 인증 정보(토큰) 저장 시와 좋아요 기능에서만 사용하기 때문에 비중을 조금 두어도 되지 않을까라고 생각했다.
우선 캐시에 있는 모든 데이터를 매번 디비에 동기화할 것이 아니기 때문에 동기화할 데이터를 선별하는 것이 필요하다고 생각했다. 그래서 디비로부터 상태가 변해서 동기화가 필요한 캐시 데이터의 키 즉, 사용자가 좋아요를 누른 게시물 ID의 목록을 저장하기 위해 앞서 언급한 board_key_list를 만들어봤다.
그러고 나서 지금까지는
- board_key_list에서 동기화할 게시물 ID 리스트 가져오기
- 각 ID별로 member Set 조회하기
- board_key_list 초기화하기
- 디비에서 해당 게시물 ID에 대한 member ID 리스트 가져와서 Set으로 변환하고 캐시 데이터랑 차집합 연산해서 필요에 따라 추가나 삭제하기
이렇게 생각해봤는데 아무래도 2번이랑 4번이 너무 오래 걸릴 것 같아서 트랜잭션 처리나 처리 순서 등을 조금 더 고민해봐야 할 것 같다. 필요에 따라 테이블에 인덱스 생성이나 비동기 처리 등을 고려해볼 수 있을 것이다.