[Article] 조회수, 어뷰징

Kooks·2025년 11월 27일

SpringBoot

목록 보기
6/7
post-thumbnail

조회수는 게시글이 조회된 횟수만 저장하면 된다.

데이터 일관성

  • 비교적 덜 중요하다.
  • 모든 조회 내역을 보여주진 않는다. 단순히 조회된 횟수만 보여주면 된다.
  • 불일치가 발생하더라도 사용자가 인지하기 어렵다.

쓰기 트래픽

  • 비교적 많다.
  • 단순히 게시글 조회만 해도 쓰기 작업이 필요하다.

데이터 일관성이 덜 중요하고, 다른 데이터에 의해 파생되는 데이터가 아니므로, 트랜잭션이나 안전한 저장소가 반드시 필요하진 않다. 또한 디스크는 접근 비용이 비싸다. 디스크보다 더 빠른 저장소를 고려해 볼 수 있다.

그렇다면 조회수 관리를 위해 In-memory Database를 사용해볼 수 있다. Redis

Redis는 휘발성이다. 종료되면 저장되어 있던 데이터는 사라진다. 그렇다면 Redis에는 백업이 필요하다.
약간의 데이터 유실을 허용했기 때문에 모든 데이터를 백업할 필요는 없다.

  • 시간 단위 백업 X
  • 개수 단위 백업 O

조회수 어뷰징 방지

게시글을 조회하면 조회수가 올라간다. 어뷰저는 특정 게시글을 여러 번 조회해서 데이터를 조작할 수 있다. 조회수 기반으로 인기글이 산정되는 경우, 올바르지 않는 어뷰징으로 인기글이 선정될 수 있다.

어뷰징을 방지하기 위한 조회 여부는

  • 로그인 사용자
    사용자 식별
  • 비로그인 사용자
    IP, User-Agen, 브라우저 쿠키, 토큰 등

로그인 사용자만 고려하고 설계하게 한다.

  • 조회수 증가 요청이 오면, Redis에 TTL = 10분으로 데이터를 저장
  • key? + userId가 된다.
  • 이미 저장된 데이터가 있으면 저장에 실패하는 명령어를 사용
  • 데이터 저장 성공 여부에 따라, 증가 및 증가하지 않는다.
요청 1Redis요청 2
조회수=3
조회수 증가 분산 락 요청 key=articleId+userId, TTL=10분조회수=3 lock=(key=articleId+userId, TTL=10분)
setIfAbsent -> True 응답조회수=3 lock=(key=articleId+userId, TTL=10분)
조회수=3 lock=(key=articleId+userId, TTL=10분)조회수 증가 분산 락 요청 key=articleId+userId, TTL=10분
조회수=4 lock=(key=articleId+userId, TTL=10분)setIfAbsent -> False 응답
조회수 증가조회수=4 lock=(key=articleId+userId, TTL=10분)종료

동일 사용자가 2번의 조회수 증가 요청이 있을 때의 상황이다.

code

@Service
@RequiredArgsConstructor
public class ArticleViewService {

    private final ArticleViewCountRepository articleViewCountRepository;
    private final ArticleViewCountBackUpProcessor articleViewCountBackUpProcessor;
    private final ArticleViewDistributedLockRepository articleViewDistributedLockRepository;

    private static final int BACK_UP_BACH_SIZE = 100;
    private static final Duration TTL = Duration.ofMinutes(10);

    public Long increase(Long articleId, Long userId) {
        if (!articleViewDistributedLockRepository.lock(articleId, userId, TTL)) {
            return articleViewCountRepository.read(articleId);
        }

        Long count = articleViewCountRepository.increase(articleId);

        if(count % BACK_UP_BACH_SIZE == 0){
            articleViewCountBackUpProcessor.backUp(articleId, userId);
        }

        return count;
    }

로그인 사용자 기준으로 조회수를 관리하기로 했고, 조회수 증가에서 articleId, userId로 락의 여부를 확인한다.

  • .lock(articleId, userId, TTL) 에서 사용자 A가 해당 게시글을 조회한 여부가 있는지 확인한다. 처음 조회한다면 락을 획득하고 아니면 락 획득에 실패해 해당 게시글의 조회수를 반환한다.
  • 락 획득에 성공했다면 조회수를 증가한다.
  • 개수 단위로 백업을 하기로 했었다. 100단위로 백업 여부를 체크해 조회수를 저장한다.
profile
I'm kooks

0개의 댓글