현재 진행 중인 프로젝트에서 좋아요 기능을 구현하기 위해 고민했던 것 + 이를 어떻게 해결하는지 과정을 기록한 글 입니다!
- Like Table을 새로 하나 만든다.
- Item과 Like 엔티티 간의 연관관계를 맺어주고, like_count 항목을 추가시켜 좋아요 요청이 올 때마다 likes_count+1 해주어 저장한다. (=업데이트 해준다.)
- Like Table을 통해 아이템의 좋아요 개수를 조회할 수 있다.
❗️문제점
Redis
라는 메모리 캐싱을 사용해야 한다.
- 클라이언트로부터 좋아요를 한 아이템 ID 목록을
List
로 받는다.- Redis 저장소에
key: “user:{userId}:likes” value: List<ItemDto>
형태로 저장한다.- 유저가 좋아요 한 아이템의 정보를 조회할 수 있다.
❗️문제점
likeCount
를 추가한 DTO 정보를 저장해놓고, 이를 역직렬화해서 조회하면 효율적일 것이라고 생각했기 때문이다.좋아요 개수
인데, 거의 동일한 데이터를 DB와 캐시 2곳에 중복 저장하는 꼴이 되어 버리기 때문이다.Write
하거나 Read
할 때 기존 데이터에 좋아요 개수
만 추가하면 효율적인 로직이 완성된다.🔺 동시성 문제 고민
INCR
같은 명령어를 사용하면, 내부적으로 동시성 문제를 해결 ⇒ Redis 선택 이유Redis는 싱글 스레드 모델을 사용하기 때문에, 한 시점에 하나의 명령만 처리한다.
따라서INCR
명령 같은 경우, 여러 클라이언트가 동시에 같은 키에 대해 증가 연산을 요청하더라도, Redis 서버는 이를 차례대로 순차적으로 처리한다.
이것은 데이터의 무결성을 보장하며, 복잡한 락(Lock)이나 트랜잭션 메커니즘 없이도 동시에 같은 데이터에 대한 업데이트가 안전하게 이루어질 수 있음을 보장한다.
- 클라이언트로부터 좋아요를 한 아이템 ID 목록을
List
로 받는다.- Redis 저장소에 아래와 같은 형태로 저장한다.
- 좋아요를 누른 사용자와 아이템의 관계를 저장하는 캐시
key: "user:{userId}:likes_item" value: Set<coordinateLookId>
- 각 아이템의 좋아요 개수를 저장하는 캐시
key: "item:{itemId}:likes" value: likeCount (Integer)
- 이렇게 2가지 구조로 특정 아이템의 좋아요 개수를 빠르게 증가 / 감소 / 조회 할 수 있다.
- 좋아요 개수가 업데이트 될 때마다 DB에 동기화 → 데이터 정합성 확보
🔺 데이터 정합성 문제 고민
Write
하는게 좋을지 고민이 많았다.Update
발생 → 캐시 데이터 Write
(항상 캐시 먼저, Hit 확률 높아짐)Write
(데이터 정합성 보장)Read
발생 → 캐시 데이터 Read
Hit
⇒ 캐시에서 데이터 조회Miss
⇒ DB 조회 (확률 낮음)👀 선택 이유
쓰는 순간
보다, 읽는 순간
이 훨씬 많다. (맞춤형 코디룩 조회, 아이템 조회.. 등)likeCount
)이다.