조회수 기능 구현하기 - 쿠키를 사용한 중복 검사 (with Spring Boot)

haaaalin·2023년 10월 13일
2
post-thumbnail

[WHY] 조회수 기능이 필요할까?

우리 서비스는 프로젝트 공유 및 팀 모집 플랫폼을 개발하고 있다.

하지만 추후에 기업 회원 기능을 넣어, 기업과 개발자가 매칭될 수 있도록 추가 기능 개발을 고려하고 있고, 이 외에도 로그를 수집할 예정이라 풍부한 데이터를 위해 조회수 기능이 필요하다는 생각이 들었다.

[HOW1] 조회수 집계는 어떻게 할까?

조회수 기능 개발을 결정 지은 이후에, 가장 우려되는 것은 중복 조회 처리였다.

중복 조회는 어떤 상황인지 살펴보자.

예를 들어, 사용자 A가 포스트 하나를 조회해서 조회수가 1 증가했다고 가정하자. 하지만 뒤로 가기를 누른 후, 다시 그 글을 조회했을 때 또 조회수가 1 증가해야 할까?

조회수가 또 증가하게끔 처리하면, 조회수를 손쉽게 조작할 수 있기 때문에 중복 조회는 불가능하게끔 하거나, 시간차 기준이 필요하다고 판단했다.

네이버 블로그 기준

네이버 블로그의 조회수(포스트 방문 횟수)는 30분 이내의 재방문은 가산되지 않고 있었다.

카카오 블로그(티스토리) 기준

(개별 포스트 기준은 아니지만) 티스토리는 방문자수와 조회수 데이터를 둘 다 제공해 조회수가 진짜인지 판별할 수 있게 했다.

유튜브와 같은 영상 조회수는 아무래도 재생 시간 등과 같은 여러 변수가 더 많기 때문에, 글 조회수를 제공하는 서비스 기준으로, 그 중에서도 조회수를 중요시 여기는, 조회수가 곧 수익이 되는 블로그 서비스를 찾아보았다.

우리 서비스는 블로그 개념이 아니라, 개별 글의 조회수를 제공할 예정이기 때문에, 방문자 수는 제공하지 않되, 하루 기준으로 중복 조회 기준이 리셋되도록 구현하기로 결정했다.

같은 사용자가 어제 조회했던 글을, 오늘 조회한다면 조회수가 가산될 예정이다.

조회수 기능 명세

  • 하루 뒤에 같은 글을 읽는 다면, 조회수 가산
  • 방문자 수는 제공 X
  • 회원, 비회원 모두 조회수 집계 대상

[HOW2] 조회수 중복은 어떻게 잡을까?

1. IP 사용하기

  • 접속 장소의 문제점

IP는 장소에 따라 유동적으로 변할 수 있어, 근처 여러 기기로 비회원 또는 회원으로 접속해도 동일 유저로 식별할 가능성이 있다.

  • Mac Address

Mac Address는 어떤 장소이든 식별이 가능하지만, 기기를 변경하면 다른 유저로 식별된다.

2. 세션 사용하기

  • stateful한 서버

Stateless 상태의 서버를 유지하기 위해 토큰 방식으로 로그인을 구현했다. 때문에 조회수 기능에 세션을 이용하면, 모순이 발생해 썩 내키지 않는다.

또한 서버의 리소스를 사용하기 때문에 세션 양이 많아진다면 그만큼 서버에 부하가 발생한다.

  • 쿠키보다 좋은 보안

아무래도 사용자 정보를 서버에 저장하므로 쿠키보다는 보안이 좋다.

3. 쿠키 사용하기

  • 취약한 보안

서버가 아닌 사용자에게 저장되는 것이라, 조작과 탈취가 쉽기 때문에 보안에 취약하다. 이는 곧 조회수를 조작할 수 있다는 말이다.

결정

일단 쿠키를 사용하기로 결정했다!

  1. 비회원 사용자도 집계 가능

    일단 세션을 사용하면 비회원인 사용자의 조회수를 집계하기 어려울 것 같아, 쿠키를 선택했다.

  2. 조회수 조작에 대한 우려

    사실 조회수는 오로지 추후에 추가될 기업 매칭 기능을 사용할 때, 참고용 데이터로 사용되거나 로그 수집용 이었기 때문에 조작이 가능하더라도 우리 서비스에 큰 취약점은 아니라고 판단했다.

  3. IP 사용

    IP를 사용하는 것이 어찌보면 거의 정확한 조회수 집계가 가능한데, IP를 사용하면 DB 테이블을 따로 생성해 관리 해주어야 한다는 점이 마음에 걸렸다.

    DB 테이블을 생성해주면서까지 큰 기능인지 생각했고, DB I/O 지연 시간이 우려되어 인메모리 DB 사용도 고려해보았지만, 굳이 이렇게까지 해야할 기능은 아니라고 생각했다.

최종적으로 쿠키를 사용하기로 결정 ‼️

조회수 집계 기능 구현하기

일단 생각한 흐름은 다음과 같다.

  • 프로젝트 상세 조회 API 호출 시 조회수 업데이트 실시
  • 이때 View_Count 이름을 가진 쿠키를 확인
    • 값에 해당 프로젝트 ID가 있다면, 조회수 가산 X
    • 쿠키가 아예 없거나, 값에 해당 글 ID가 없다면 조회수 가산 O
  • 조회수를 새로 업데이트한 경우, 쿠키도 따라서 업데이트 후에 response 값에 넣어 반환

코드를 통해 자세히 살펴보자.

아래는 쿠키를 확인한 후, 조회수를 올리는 함수이다.

public Cookie addViewCount(HttpServletRequest request, Long projectId) {
    Project project = this.getProjectById(projectId);

    Cookie[] cookies = request.getCookies();
    Cookie oldCookie = this.findCookie(cookies, "View_Count");

    if (oldCookie != null) {
        if (!oldCookie.getValue().contains("[" + projectId + "]")) {
            oldCookie.setValue(oldCookie.getValue() + "[" + projectId + "]");
            oldCookie.setPath("/");
            oldCookie.setMaxAge(60 * 60 * 24);
            project.addViewCount();
        }
        return oldCookie;
    } else {
        Cookie newCookie = new Cookie("View_Count", "[" + projectId + "]");
        newCookie.setPath("/");
        newCookie.setMaxAge(60 * 60 * 24);
        project.addViewCount();
        return newCookie;
    }
}

쿠키 유무를 먼저 확인 후에, contains 함수를 이용해 해당 쿠키 값에 projectId를 포함하고 있는 지 확인하고 있다.

쿠키가 없거나 현재 조회하려는 글의 id 값이 존재하지 않는다면, 새롭게 쿠키를 설정해 준 후, 조회수를 업데이트하도록 구현했다.

Cookie 값 예시
”[1][32][5]” → ID가 1, 32, 5인 글을 조회한 상태

쿠키 수명이 제대로 설정이 되고 있는 걸까?

아니었다.

위와 같은 방식을 사용하게 된다면, 다음과 같은 문제가 발생한다.

만약 사용자가 ID가 1인 글을 조회한 후, 22시간 후에 ID가 3인 글을 조회한다면 쿠키의 수명이 새롭게 24시간 후로 설정이 된다.

즉, ID가 1인 글을 조회하고, 24시간 후가 아닌 46시간 후에 조회수를 올릴 수 있다.

결론적으로 쿠키 수명이 글을 조회할 때마다 갱신되기 때문에 죽지 않는 쿠키가 발생한다..!

글마다 쿠키를 생성할까?

조회한 글마다 쿠키를 생성한다고 가정해보자.

한 사이트에 저장 가능한 쿠키는 최대 20개이고, 하나의 최대 쿠키 크기는 4KB로 제한되어 있다.

따라서 조회한 글마다 쿠키를 생성하게 되면, 21번째로 조회하는 글부터는 쿠키가 생성되지 않아, 조회수 조작을 마음대로 할 수 있다.

세션 쿠키를 이용해보자

24시간이라는 제한을 차라리 포기하고, 브라우저를 닫으면 없어지는 쿠키인 세션 쿠키를 이용하기로 결정했다.

즉, 쿠키의 만료 시점을 설정하지 않고, 쿠키를 생성해 주는 것

👨‍💻 최종 코드

위에서 살펴봤던 코드에서 MaxAge를 설정하는 코드만 빠진 모습을 확인할 수 있다.

public Cookie addViewCount(HttpServletRequest request, Long projectId) {
    Project project = this.getProjectById(projectId);

    Cookie[] cookies = request.getCookies();
    Cookie oldCookie = this.findCookie(cookies, "View_Count");

    if (oldCookie != null) {
        if (!oldCookie.getValue().contains("[" + projectId + "]")) {
            oldCookie.setValue(oldCookie.getValue() + "[" + projectId + "]");
            oldCookie.setPath("/");
            project.addViewCount();
        }
        return oldCookie;
    } else {
        Cookie newCookie = new Cookie("View_Count", "[" + projectId + "]");
        newCookie.setPath("/");
        project.addViewCount();
        return newCookie;
    }
}

마무리하며

사실 잘 몰랐던 때에는 조회수 기능이 단순히 글을 조회할 때마다 조회수를 올리면 된다고 생각했다. 사실 조회수가 큰 부분을 차지하지 않는 서비스에서는 그렇게 구현한 것 같았다. (클릭만 해도 조회수가 계속 올라가던 서비스도 몇 개 봤었다👀)

하지만 조회수 조작을 방지하기 위해 중복 체크 로직이 추가적으로 들어가야 한다는 것을 알았고, 이에 여러 가지 방법을 찾아본 시간이 되었다. 또한 로그인 상태 확인이 아닌 다른 경우에도 쿠키를 이렇게 사용할 수도 있다는 것을 깨달았다.

후에는 조회수가 굉장히 큰 부분을 차지하는 수익성 서비스를 한 번 개발하며, 더 꼼꼼한 조회수 조작 방지를 위해선 어떻게 해야하는 지 탐구해보며 구현해 보고 싶다.

profile
한 걸음 한 걸음 쌓아가자😎

0개의 댓글