이번 기능은 어디에나 있는 좋아요 같은 기능이다. 좋아요 한 게시물에 대해서 확인할 수도 있는 기능들과 비슷하다. 다만 게시글이 아닌 소설에 그런 선호라는 기록을 남기는 것이고, 내가 읽은 소설이 내가 선호하는 작품이라면 마지막으로 읽은 페이지를 기록해야 한다.

마지막 읽은 페이지 기록


단순히 생각하면 다음과 같이 구현할 수 있다.

Untitled

위 그림과 같이 선호작품을 관리하는 테이블에 마지막으로 읽은 페이지에 대한 정보를 가지는 컬럼을 추가하여 구현하는 것이다.

{
	"novelId" : 1,
	"episodeId" : 1,
	"page" : 42
}

그래서 사용자가 소설의 에피소드를 읽다가 나오거나, 브라우저를 강제 종료 했을 때, 프론트엔드에서 해당 정보를 보내고, 이 정보를 가지고 백엔드에서 종료된 시점의 소설이 선호작인지, 선호작이라면 episodeIdpage를 이용해 선호작 테이블에 해당 정보를 반영하게 되는 것이다.

하지만 이는 소설을 매번 종료할 때 마다, DB에 추가 쿼리가 발생한다. 또한 선호작품이라면 계속해서 UPDATE 쿼리가 발생하는 것이다. 추가적으로 동시성 문제가 일어날 가능성도 다분하다. 실제로 선호 테이블은 FK키를 가지고 있기 때문에 데드락이 걸릴 위험성도 있다.

어떻게 추가 쿼리를 가지지 않고, 구현할 수 있을까

가장 먼저 Redis를 활용하는 방안을 생각했다. 이용자가 소설의 에피소드를 조회한 시점에 해당 소설을 선호하고 있는지의 여부를 같이 프론트로 응답한다. 그래서 해당 값이 True라면 프론트에서 마지막 페이지를 기록하는 API를 호출하게 된다.

{
  "novel": {
    "id": 2,
    "title": "테스트2",
    "author": "a",
    "description": "a",
    "freeType": "FREE",
    "genre": "FANTANSY"
  },
  "episodeTitle": "3의 오아시스",
  "sequence": 17,
  "hit": 0,
  "price": 0,
  "createDate": "2023-04-16T08:07:01.053Z",
  "isFavorite": false,
  "contents": [
    {
      "id": 1,
      "page": 0,
      "content": "1페이지의 내용입니다."
    },
    {
      "id": 2,
      "page": 1,
      "content": "2페이지의 내용입니다."
    }
  ]
}

위 JSON 데이터는 소설의 특정 에피소드를 조회할 때 반환되는 데이터이다. 잘 보면 isFavorite 이라는 데이터가 있다. 해당 데이터가 true이면 종료 시에 마지막으로 읽은 페이지를 기록하는 API를 호출하는 것이다.

그리고 서버에서는 해당 데이터를 Redis에 보관한다. 사용자 별로 선호하는 작품의 마지막으로 읽은 페이지를 Hash 자료구조로 보관하는 것이다. 그리고 선호작품을 조회했을 때, 조회된 소설의 ID를 가지고, Redis에서 마지막으로 읽은 페이지의 값과 함꼐 이용자에게 응답하는 것이다.

Untitled

조금 더 단단하고, 깔끔하게 설계를 했다면 좋겠지만, 시간이 부족해서 일단 위와 같이 하드코딩된 부분이 있고 구조가 매끄럽지 못하다. 나중에 시간이 나면 더 깔끔하게 리팩토링을 해봐야겠다.

Redis 에서는 key가 소설 ID가 되고, value는 {episodeId}:{page} 로 구성된다. 그렇기 때문에 : 를 구분자로 정하여 split을 해서 해당 데이터를 DTO로 변환시킨다.