인프런 영문 브랜드 로고
인프런 영문 브랜드 로고

인프런 커뮤니티 질문&답변

soap님의 프로필 이미지

작성한 질문수

스프링부트로 직접 만들면서 배우는 대규모 시스템 설계 - 게시판

조회수 어뷰징 방지 정책 설계

각 사용자는 게시글 1개당 1번 조회수 증가'일 경우 설계 질문 드립니다.

해결된 질문

작성

·

133

·

수정됨

0

안녕하세요 쿠케님!

강의에 나온 10분의 TTL이 없을시 조회수 증가 관련되서 질문드립니다!'

 

현 상황에서 게시글 접근시 마다 조회수 증가 API를 호출하고 있습니다.

테이블 정보는 다음과 같습니다.

  

Board 테이블
id(auto-increment) 
readCount
BoardRead 테이블
id(auto-increment) 
boardId
userId

 

게시글 상세 접근시 마다 호출하는 '조회수 증가 API'의 흐름은 다음과 같습니다.

1. 해당 게시글을 유저가 조회 했는지 검증 (BoardRead 테이블에서 검증)

1-1. 조회 한 이력이 있으면 return;

2. Board 비관적 락 조회

3. Board 테이블 readCount 업데이트

4. BoardRead 테이블 insert

 

Board의 readCount는 게시글 조회수를 나타내고, BoardRead는

'각 사용자는 게시글 1개당 1번 조회수 증가'를 검증하기 위한 용도 입니다.

'각 사용자는 게시글 1개당 1번 조회수 증가' 정책을 반드시 가져가야 한다면

BoardRead 테이블에 있는 데이터도 레디스로 옮겨야 할까요?

그런데 조회수 데이터는 계속해서 쌓일테고 비즈니스에 중요하지 않은 데이터가 레디스 메모리만

차지하는 느낌이 들어서 꺼려지더 라구욤..

 

강의 내용대로 TTL을 걸수 밖에 없는건가 고민도 듭니당..

(레디스는 클러스터 환경으로 사용하고 있습니다.)

 

기능은 그대로 유지하되 비관적 락을 뺄 수 있는 방법이 있을까요?

 

※ 번외로 트래픽 바로 몰리니까 비관적 락 로직 때문인지 잠금 이슈 나서 디비 바로 터졌버렸네요 하하하 ㅠ

비관적 락을 선호하지 않는 이유를 체감해버렸다..

 

※ 뇌 + GPT 갈구니까 아래와 같은 여러 결론이 나왔습니다.

해결책1

1. Redis SET 자료구조로 중복 체크, 최초 조회면 TTL 걸어줌

2. 분산락(Redis)을 걸고, 

   Board 테이블 readCount 업데이트 및 BoardRead Insert 수행

3. 락 해제


해결책2

1. Redis SET 자료구조로 중복 체크, 최초 조회면 TTL 걸어줌

2. 비동기로 Board 테이블 readCount 업데이트(낙관적 락 적용) 및 BoardRead Insert 수행


작성하면서 문득 'readCount를 정규화 할까?' 했는데 스케이링 넘 클것 같네요..

이유는 테이블 설계를 JPA의 상속을 활용하는 방안으로 했기 때문에 readCount 필드가 '게시판'이란 추상 클래스에 위치해 있습니다.

주저리 주저리 적어봤는데 머릿속에 혼란이 오네요 ㅠㅠ


자기전 마지막 생각

레디스의 incr를 이용해 조회수 관리, 조회수 데이터는 mysql에 주기적으로 백업

 

 

 

답변 2

1

쿠케님의 프로필 이미지
쿠케
지식공유자

soap님, 안녕하세요!

 

BoardRead 테이블에 있는 데이터도 레디스로 옮겨야 할까요?

조회수 증가에 대해서 휘발성이 있는게 아니라, 영구적으로 게시글 당 1회만 가능한 것이죠!?

그런거라면 레디스는 과한 느낌이 있네요.

그냥 저렴한 테이블에 저장하셔도 충분할 것 같습니다!

 

1. 해당 게시글을 유저가 조회 했는지 검증 (BoardRead 테이블에서 검증)

1-1. 조회 한 이력이 있으면 return;

2. Board 비관적 락 조회

3. Board 테이블 readCount 업데이트

4. BoardRead 테이블 insert

기능은 그대로 유지하되 비관적 락을 뺄 수 있는 방법이 있을까요?

BoardRead 테이블에 boardId/userId로 유니크 인덱스를 만들면, 조회 시점의 비관적 락은 필요 없습니다!

BoardRead 테이블 삽입을 먼저 수행하고, 성공했으면 그냥 Board 테이블의 readCount를 업데이트해주면 됩니다.

유니크 제약으로 실패한 요청은 카운트 증가 처리를 안해주면 됩니다.

Board 테이블에 바로 update 쿼리를 날려도 찰나의 레코드 락이 생기긴 하지만, 대부분의 경우 게시글 단위의 레코드 락은 크게 문제 없을 것이라고 좋아요 수 강의에서도 설명합니다.(게시판이 아니라 게시글 단위 맞죠..?!)

이 찰나의 레코드 락과 DB 부하가 우려가 되는 상황이라면,

비동기로 큐에 넣어두었다가 천천히 처리할 수도 있고,

처음에는 레디스에 저장 후 주기적으로 백업하는 전략도 가능한 것이고요.(자기 전 생각하신 방법이네요!)

해결책 1/2도 결국 락을 잡는 범위가 넓어지는거라, 그냥 DB 유니크 제약조건 이용하는게 제일 좋을 것 같네요!

김우철님의 프로필 이미지

영구적으로 게시글 당 1회만 가능한 것이죠!?

=> 맞습니다!

게시판이 아니라 게시글 단위 맞죠..?!

=> 맞습니다!

 

마지막 질문이 있습니닷!

유니크 제약으로 실패시 발생하는 Unique 에러같은 경우 정크로 판단하여 알림에서 제외하시는 걸까요?

 

쿠케님의 프로필 이미지
쿠케
지식공유자

앗 확인이 늦었네요!

Unique 에러가 발생한다면, 조회수 데이터는 이미 1회 삽입되었음을 의미하므로, 나머지는 유효하지 않다고 판단하여 제외합니다!

0

인프런 AI 인턴님의 프로필 이미지

안녕하세요, 인프런 AI 인턴입니다. soap님.

조회 수를 관리하면서 발생하는 비관적 락과 조회 수 증가 정책 문제는 시스템 성능 및 데이터 정확성을 고려할 때 매우 중요한 부분입니다. 특히, 각 사용자가 특정한 게시글을 단 한번 읽도록 해야 한다는 정책의 경우 더욱 신중하게 접근해야 합니다.

비관적 락을 줄이는 방법

‘각 사용자는 게시글 1개당 1번 조회수 증가’라는 정책을 유지하면서 비관적 락을 최소화하기 위해 몇 가지 방법을 고려할 수 있습니다:

  1. 비관적 락을 낙관적 락으로 전환:

    • 비관적 락 대신 낙관적 락을 사용하여 성능을 개선할 수 있습니다. 낙관적 락은 충돌이 발생한 경우에만 재시도하게 됩니다. JPA에서는 버전 필드를 통해 이를 구현할 수 있습니다.
  2. Redis 활용 방안:

    • BoardRead 테이블의 데이터를 Redis에 캐시하여 읽기 작업을 가속화할 수 있습니다. Redis는 데이터 일관성보다는 성능을 중시하는 경우에 적합합니다. 만약 데이터를 영구적으로 저장할 필요가 없다면, 일정 기간 후 자동으로 데이터가 제거되는 TTL 기능을 설정할 수 있습니다.
  3. 혼합 접근법:

    • BoardRead의 데이터를 Redis에 저장하면서, 비정기적으로 데이터베이스로 동기화하는 방법도 있습니다. 이렇게 하면 Redis의 장점을 활용하면서 데이터 유실을 최소화할 수 있습니다.
  4. 비동기화 방식 도입:

    • 비동기 작업으로 조회수 증가 로직을 처리하여 데이터베이스 잠금을 줄일 수 있습니다. 예를 들어, 메시지 큐를 활용해 조회 기록을 비동기적으로 저장할 수 있습니다.
  5. 게시글 조회수 데이터 일치성 조정:

    • 조회수 데이터가 즉시 일치하지 않아도 된다면, 근사치를 허용하는 방향으로 정책을 완화하여 트랜잭션의 부담을 줄일 수 있습니다.

특히 높은 트래픽 상황에서는 데이터베이스의 무거운 락보다 비동기 및 인메모리 캐시 솔루션을 활용하여 성능과 데이터베이스 부하를 관리하는 것이 중요합니다.

관련된 방법론과 실전 예제를 참조하고 싶다면 다음 링크가 도움이 될 것입니다: 조회수 중복 방지 설계 고민.

저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드리고 있습니다. 현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏 추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.

soap님의 프로필 이미지

작성한 질문수

질문하기