좋아요 기능에 관한 고민중...

·2022년 7월 17일
17

사이드

목록 보기
2/2

왜 이런 고민을 하냐면 팀프로젝트의 좋아요가 이딴식이다.

(절대 벌어지면 안되는 일들이 벌어지고 있다...)

팀원이 설계를 했는데, 사실 나도 어떻게 해야하는지 명확한 답을 알지 못했다.
검색을 해보고 싶진 않았고 내 지식의 범위 내에서 처리를 해보고 싶었는데

  1. Lock을 건다
    => 이건 절대 아니라고 생각했다. 왜냐하면 한명이 그 글에 점유권이라고도 볼 수 있기 때문에
  2. 프론트에서 처리한다
    => 이게 맞다고 생각을 했는데 프론트가 바빠서 작업을 못했따.

그래서 결국 저 상태로 정리가 되었는데....

토이,사이드프로젝트에 둘다 좋아요가 필요하게 됐다ㅎㅎ
사실 커뮤니티를 만든다면 없는 곳을 보기 드물기 때문에 결국은 해결해야만 하는 기능 중 하나라고 생각한다.

뭔가 보고 해보고 싶다 라는 생각이 들어서 벨로그에서 저 광클을 해봤다.

굳이 벨로그로 뭔가 하는 이유는 단순하다.

  1. 오픈소스임
  2. 트래픽이 많이 발생한다는 사실을 알고 있음
  3. 직접적으로 사용하고 있기 때문에 어떤 상황에 어떤게 벌어지는지 명확하게 확인가능

너무 좋아

벨로그는 광클을 해도 튀지 않고 안정적(??)인 모습이 보여지는 것을 알아볼 수 있었는데
코드가 궁금해져서 금방 찾아가지고 뜯어왔다.

likePost: async (parent: any, args, ctx) => {
      if (!ctx.user_id) {
        throw new AuthenticationError('Not Logged In');
      }

      createLikeLog({
        ip: ctx.ip,
        postId: args.id,
        userId: ctx.user_id,
      });

      // find post
      const postRepo = getRepository(Post);
      const post = await postRepo.findOne(args.id);

      if (!post) {
        throw new ApolloError('Post not found', 'NOT_FOUND');
      }

      // check already liked
      const postLikeRepo = getRepository(PostLike);
      const alreadyLiked = await postLikeRepo.findOne({
        where: {
          fk_post_id: args.id,
          fk_user_id: ctx.user_id,
        },
      });

      // exists
      if (alreadyLiked) {
        return post;
      }

      const postLike = new PostLike();
      postLike.fk_post_id = args.id;
      postLike.fk_user_id = ctx.user_id;

      try {
        await postLikeRepo.save(postLike);
      } catch (e) {
        return post;
      }

      const count = await postLikeRepo.count({
        where: {
          fk_post_id: args.id,
        },
      });

      post.likes = count;

      await postRepo.save(post);

      const unscored = checkUnscore(post.body.concat(post.title));
      if (!unscored) {
        const postScoreRepo = getRepository(PostScore);
        const score = new PostScore();
        score.type = 'LIKE';
        score.fk_post_id = args.id;
        score.score = 5;
        score.fk_user_id = ctx.user_id;
        await postScoreRepo.save(score);
      }

      setTimeout(() => {
        searchSync.update(post.id);
      }, 0);

      return post;
    },

출처 => https://github.com/velopert/velog-server/blob/master/src/graphql/post.ts
1128번째 줄

뭔가 보면 특별하게 더 작업을 했다는 것이 보이질 않아서 더 의문이였다.

내가 생각하고 있는 로직은 아래와 같다.

  1. 게시글의 ID, 유저의 정보를 캐시로 저장하고 리턴한다. 한 200초정도?
  2. 그 사이에 들어온 정보에 대하여 DB에 저장한다.
  3. 광클을 할 경우 캐시에서 바로 값을 꺼내서 존재하면 좋아요가 해제된다.

이런식으로 캐시를 사용해서 처리를 하는 것이 정답이라고 생각했는데.....
위에 코드에는 그런게 없어서 더더욱 이상했다(...어떻게한거지?)

그래서 프론트 코드도 한번 봤다. 프론트에서 뭔가 했나 싶어서

솔직히 나는 아직 프론트 코드를 읽을 줄 몰라서 이게 맞는지도 잘 모르겠지만
맞다면? 캐싱의 작업이 없는 것 같다. . .

정답은 그냥.....

일단 내가 생각한대로 구현해보고 광클해봐야겠다!!

profile
물류 서비스 Backend Software Developer

6개의 댓글

comment-user-thumbnail
2022년 7월 20일

덕분에 궁금해져서 찾아보았습니다.

https://github.com/velopert/velog-client/blob/e112be0350b51c67fc5f481f914fa0be5599c096/src/containers/post/PostViewer.tsx

const onLikeToggle = async () => {
if (loadingLike || loadingUnlike) return;
...생략

프론트 코드의 좋아요 버튼 이벤트리스너입니다.
로딩 중이라면 저렇게 리턴을 시켜버립니다.
따라서 요청이 이루어지는 중에는 재요청이 되지 않기 때문에 안정적으로 동작합니다.
프론트에선 보통 이런 식으로 광클을 대비하는 듯 합니다.
저도 이런 식으로 작업해왔습니다.

1개의 답글
comment-user-thumbnail
2022년 7월 21일

벨로그에서는 좋아요 버튼을 반복 클릭하여도 좋아요 숫자가 튀거나 줄지 않고 정합성이 깨지지 않는 이유는 다음 링크의 좋아요 DB 모델인 PostLike 엔티티를 확인해보시면 알 수 있습니다.

https://github.com/velopert/velog-server/blob/614d97b0dd983d8547938506c163e46de8861dbf/src/entity/PostLike.ts#L18

18번째줄을 보시면, 'fk_post_id'(게시글 아이디)와 'fk_user_id'(유저 아이디)에 동시에 유니크 제약이 걸려있기 때문입니다. 이 유니크 제약을 이용하면 따로 락이나 트랜잭션 기능을 사용하지 않아도 좋아요 숫자의 정합성을 유지할 수 있게 됩니다.

유니크 제약은 데이터베이스상에 특정 컬럼이 같은 값을 가지는 데이터를 중복적으로 추가할 수 없음을 의미합니다. 벨로그의 좋아요 기능에서는, 게시글 아이디와 유저 아이디 쌍이 각각 같은 데이터를 중복적으로 추가할 수 없다는 것을 의미합니다. 즉, 같은 사람이 한 게시글에 좋아요 버튼을 아무리 빠르게 반복적으로 클릭을 하더라도 데이터베이스상에서는 유니크 제약에 의해 추가가 되지 않게됩니다.

3개의 답글