2025. 4. 6. 23:19ㆍ개발
이 API는 좋아요 수를 기준으로 10개의 인기 여행 계획 게시글을 선정하며, 서비스 내에서 가장 많이 요청되는 API 중 하나다.
기존에는 데이터베이스에 DESC 인덱스를 생성하여 조회 속도를 높였으나, 부하 테스트 결과 여전히 DB 부하가 존재한다.
1초에 2천명 동시 요청 테스트시, 평균 좋아요 기능의 api 콜 대기 시간이 write는 7556ms, read 속도는 500ms 이다.
따라서 새로운 해결 방식 필요함을 느꼈다. 새로운 해결 방식을 찾기 위해 다루는 데이터의 특성부터 다시 파악하였다.
위의 API에서 다루는 데이터는 크게 두가지로, 좋아요 정보와 게시글 내용이다. 이 데이터에 대해, 다음과 같이 가정하였다.
- 데이터 : 게시글 좋아요 수
- Workload : 자주 조회, 업데이트, write시 즉시 반영 필요 없음
- 데이터 : 게시글 내용
- Workload : 자주 조회, write시 즉시 반영 필요
- 데이터 : 유저 - 게시글 (좋아요 관계)
- Workload : 자주 조회, 업데이트, write시 반영 필요
여기까지 보았을 때, 게시글 좋아요와 게시글의 내용은 서로 다른 요구사항을 갖는다. 그 다음 이 서비스에서, 사용자들의 행동을 가정하였다.
읽기 vs 쓰기 비율
게시글 유형 | 읽기 쓰기 비율 |
인기 게시글 | 읽기가 압도적으로 많음 |
일반 게시글 | 읽기와 쓰기 비율이 유사 |
- 인기 게시글의 기준은 좋아요 수
- 인기 게시글 목록은 순위 변동 반영 필요
- 게시글의 내용은 수정 시 즉시 반영
위의 특성을 통해 다음을 파악하였다.
인기 게시글은 읽기/쓰기 비율을 보았을 때, 캐싱이 유리하다. 하지만 순위 변동의 반영과 게시글 내용의 즉시 수정이란 조건 반영이 필요하다
좋아요 정보는 쓰기 비율이 많으며, 동시에 읽기 비율도 많다. 사용자가 좋아요/좋아요 취소를 반복적으로 누를 수 있는 상황이 있는데 이때 db에 쿼리를 계속 날리고 커넥션도 부족해질 거이다.
해결책 - 인기 게시글 & 좋아요를 캐시에 저장
1. 일반 게시글은 캐시에 저장하지 않았다. 유저 수에 비례해 데이터 수도 늘어나고 그에 비해, 읽기 수는 적으므로 효율성이 낮다고 판단했다.
2. 위의 데이터 분석을 보아, 좋아요와, 게시글 내용은 다른 요구사항을 갖으므로 분리하여 저장하였다.
만약에 서버가 여러대라면 다음과 같은 문제가 생긴다 : 유저 A가 서버1에 좋아요 요청을 보낸 후 재접속하여 서버 2에 접속한다면 이전 좋아요 정보를 읽지 못할 것이다.
따라서 글로벌 캐시전략을 취했고, 디비에는 일정 주기로 write back을 하였다.
요약하면 다음과 같다
- 좋아요 & 게시글 내용 분리
- 좋아요는 주기적으로 DB에 write back
- 게시글 전체 내용은 DB에 write through
- 배치 작업
- 좋아요 수 & 유저-좋아요 데이터 DB 업데이트
- => DB에서 상위 10개 인기 게시글 read
- => Redis에 인기 게시글 update
글로벌 캐싱
글로벌 캐시는 다음과 같은 이점이 있다.
- 트래픽이 많아지면 캐시 서버만 스케일업/아웃 가능. 즉, DB와 독립적으로 유연하게 확장 가능.
- 서버 인스턴스간 정보의 불균형 해소
memcache와 redis중 redis를 사용하였는데, 이 서비스는 게시글 목록을 반환할때 사용자가 좋아요를 했었는지도 반환한다. 따라서 유저-좋아요 정보가 데이터 손실에 민감하므로 유실에 대한 방지가 필요했다. 또한 단기간에 많은 좋아요 수가 하나의 게시글에 몰려서 랭킹 순위가 바뀌어야 하는데 데이터 손실이 일어난다면 사용자 경험에 큰 문제가 있을 것이었다.
따라서 AOF, RDB를 지원하는 Redis를 사용하였다. 또한 Redis는 다양한 자료구조를 지원하며 여러 기능에 관한 인터페이스를 제공한다. 아직 여러 기술에 대해 다뤄본 경험이 없어 GCP환경에서 사소한 기능 하나하나 추가할때마다 새로운 인프라 구축이 어려울 것이라고 생각했고(인프라 추가시 발생할 수 있는 문제의 범위가 엄청 커질 것이다), redis하나로 다양한 기능을 해결할 수 있다. (단 모든 것을 레디스로 해결하려 하면 그만큼 레디스 부하/다운 시 문제가 커지긴 한다)
그냥 관련 없는 추가 질문 : 로컬에서 레디스 사용하면 어떻게 되나?
로컬에서 레디스를 사용하는 것은 이 프로젝트의 환경에서 좋을 지는 모르겠다.
결국 localhost로 요청을 보내야하며 loopback interface로 통신을 하게 될텐데, 이는 곧 4,3계층 까지 내려가야 한다.
UDS(unix domain socket)를 사용한다면 해결이 될거 같다.
그럼에도 불구하고 위의 오버헤드를 줄일 수는 있지만 결국 문제는 cpu 스케쥴링이다.
이 프로젝트에서는 2코어 cpu, 멀티스레드 기반의 spring을 사용한다. 즉 여러 스레드의 cpu 경합이 존재한다. 이러한 상황에서 클라이언트가 서버에게 요청을 보내면 서버가 바로 응답 할 것인가?
레디스 서버로의 스케쥴링, 그리고 작업 후 클라이언트로의 스케쥴링, 그리고 스케쥴링 전에 대기큐에서의 블로킹이라는 시간이 존재한다.
다음글
https://loftspace.tistory.com/46
인기 게시글 도입을 위한 과정 - 자료구조의 선택 그리고 추가적인 고민
좋아요 수, 유저-좋아요, 인기 게시글 목록을 저장할 자료구조레디스의 공식문서를 보았을 때 다음과 같은 방식을 고안했다. 방법 1. 좋아요 수 카운팅을 HashSet으로 관리, 랭킹 목록은 List로 저장
loftspace.tistory.com
'개발' 카테고리의 다른 글
인기게시글 도입을 위한 과정 - 추가적인 고민 (0) | 2025.04.25 |
---|---|
트랜잭션 데드락 - 고민 (0) | 2025.04.06 |
트랜잭션 데드락 - 외래키 제약조건 (0) | 2025.04.06 |
인기 게시글 도입을 위한 과정 - 자료구조의 선택 (0) | 2025.04.06 |
인기 게시글 도입을 위한 과정 - 기획 (0) | 2025.02.17 |