Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Impl likeCount and isLiked Logic #79

Merged
merged 16 commits into from
Dec 1, 2022
Merged

Impl likeCount and isLiked Logic #79

merged 16 commits into from
Dec 1, 2022

Conversation

davin111
Copy link
Member

@davin111 davin111 commented Nov 30, 2022

related #67, #78

  1. 강의평 리스트를 전달할 때 내가 해당 강의평을 공감했는지 나타내는 is_liked를 필드를 추가
  2. 여러 명이 동시에 like_count update 할 때, lost update 문제가 발생할 수 있는 부분을 optimistic lock 으로 방지
  3. GET /v1/tags/main/{id}/evaluations에서 cache hit 된 강의평 리스트 전달 시, is_likedlike_count 는 따로 DB 에서 가져와 덮어쓰도록 로직 추가
  4. 동일 유저가 동일 강의평에 대해 공감/공감 취소한 경우, 200 -> 409 로 스펙 변경 cc. @woohm402

@@ -13,12 +12,9 @@ data class EvaluationWithLectureDto @QueryProjection constructor(
val lifeBalance: Double,
val rating: Double,
val likeCount: Long,
@get:JsonProperty("isHidden")
@field:JsonProperty("isHidden")
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FasterXML/jackson-module-kotlin#80 이런 이슈 때문에 넣었던 건데 예전에 업데이트 하면서 필요 없어짐.

Comment on lines +17 to +18
@DynamicUpdate
@OptimisticLocking(type = OptimisticLockType.DIRTY)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Version을 위한 칼럼 따로 만드는 게 번거롭다고 여겨져서 이렇게 했습니다. 의견 주셔도 좋습니다.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

요것도 괜찮네요

Comment on lines +193 to +202
postHitProcessor = { dtos ->
val evaluationsIds = dtos.map { it.id }
val likes = evaluationLikeRepository.findAllByLectureEvaluationIdIn(evaluationsIds)
dtos.map { dto ->
dto.copy(
likeCount = likes.count { it.lectureEvaluation.id == dto.id }.toLong(),
isLiked = likes.any { it.lectureEvaluation.id == dto.id && it.userId == userId },
)
}
},
Copy link
Member Author

@davin111 davin111 Nov 30, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cache hit 해놓고도 DB 에 접근하는 게 슬프지만, 일단 너무 많은 걸 당장 바꾸지 않고 기능이 완벽하게 동작하는 것에 집중했습니다. single thread 인 Redis 없이 DB 로만 해결하는 방법도 일부러 해보고 싶었고요.

더 좋은 방식에 대한 의견은 남겨놔주세요. ex> like_count 를 Redis 로 관리하자 등. (그런다면 is_liked 도 그렇게 해야할까?)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

like 관련 로직은 새로 서비스를 하나 파서 분리하면 좋을 것 같습니다.

제 개인적인 의견으로는 둘 다 인메모리 캐싱을 해도 될 거 같아요.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

해당 캐시 키는 user id 와 evaluation id 로 생각하시나요?

유저가 공감/공감 취소할 때마다 캐시 날리구요?

Redis 가 아닌 인 메모리로 보시는 이유도 궁금합니다.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

인메모리가 더 빠르고, api 스펙 자체가 리소스 부담을 줄이기 위해 캐시 size를 작게 잡아도 miss가 많이 생길것 같지 않아서요.


private fun JPAQueryFactory.selectEvaluationWithLectureDto() = select(
private fun JPAQueryFactory.selectEvaluationWithLectureDto(userId: String) = select(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

쿼리가 많이 빡세네요.. 추후에 개선 포인트로 두면 좋을 것 같습니다

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ㅋㅋ 좋습니다. 쿼리가 빡세다고 판단하신 이유는 join 3개 붙어서인가요?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

넵. 근데 무엇보다 where 절에서 tag 구하려고 avg 들어가는게 있는데 풀스캔이 돌 것 같아서 요것만 잘 분리해도 좋을 것 같아용.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

select le.id                             as col_0_0_,
       le.user_id                        as col_1_0_,
       le.content                        as col_2_0_,
       le.grade_satisfaction             as col_3_0_,
       le.teaching_skill                 as col_4_0_,
       le.gains                          as col_5_0_,
       le.life_balance                   as col_6_0_,
       le.rating                         as col_7_0_,
       le.like_count                     as col_8_0_,
       le.is_hidden                      as col_9_0_,
       le.is_reported                    as col_10_0_,
       IF(elike.id is null, false, true) as col_11_0_,
       le.from_snuev                     as col_12_0_,
       sl.year                           as col_13_0_,
       sl.semester                       as col_14_0_,
       sl.lecture_id                     as col_15_0_,
       lecture.title                     as col_16_0_,
       lecture.instructor                as col_17_0_
from lecture_evaluation le
         inner join semester_lecture sl on le.semester_lecture_id = sl.id
         left outer join evaluation_like elike
                         on le.id = elike.lecture_evaluation_id and elike.user_id = '61f55ab5b7a1c400103aed78'
         inner join lecture on sl.lecture_id = lecture.id
where (
        sl.lecture_id in (select sl_tmp.lecture_id
                          from lecture_evaluation le_tmp
                                   inner join
                               semester_lecture sl_tmp on le_tmp.semester_lecture_id = sl_tmp.id
                          where le_tmp.is_hidden = false
                          group by sl_tmp.lecture_id
                          having avg(le_tmp.rating) >= 4.0)
    )
  and sl.classification = '교양'
  and le.is_hidden = false
order by le.id desc
limit 20;

참고로 현재 쿼리... ㅋㅋㅋ

where subquery 로 실행되는 부분을 따로 query 해서 page size (20) 개수만 가져온 다음,
in 이용한 query 하도록,
application 에서 이렇게 2개로 분리하는 식을 생각하신 건가요? (즉, in에 들어가는 개수를 줄일 수 있어서..?)

avg() 쓰는 이상 기본적으로 lectureEvaluation 대상으로 풀 스캔 도는 거 자체는 어쩔 수가 없지 않나 궁금해서 질문입니다.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@PFCJeong 질문이 많아 죄송해요! 요거 시간 나실 때 답해주시면 추후 PR 에 참고하겠습니다. (요것도 #79 (comment)?)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

칼럼을 추가해서 강의평이 추가, 삭제, 수정될 때마다 미리 통계를 내서 태그 달아두면 좋을 것 같았어요.. 대대적인 공사가 필요하겠지만..

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아하 ㅋㅋㅋ 비정규화해서 저장해두는 거네요. 그렇게 하면 정말 대공사가 필요하긴 하겠군요.

Comment on lines +17 to +18
@DynamicUpdate
@OptimisticLocking(type = OptimisticLockType.DIRTY)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

요것도 괜찮네요

Comment on lines +193 to +202
postHitProcessor = { dtos ->
val evaluationsIds = dtos.map { it.id }
val likes = evaluationLikeRepository.findAllByLectureEvaluationIdIn(evaluationsIds)
dtos.map { dto ->
dto.copy(
likeCount = likes.count { it.lectureEvaluation.id == dto.id }.toLong(),
isLiked = likes.any { it.lectureEvaluation.id == dto.id && it.userId == userId },
)
}
},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

like 관련 로직은 새로 서비스를 하나 파서 분리하면 좋을 것 같습니다.

제 개인적인 의견으로는 둘 다 인메모리 캐싱을 해도 될 거 같아요.

Copy link
Member

@subeenpark-io subeenpark-io left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

큰 이견은 없는데,

동일 유저가 동일 강의평에 대해 공감/공감 취소한 경우, 200 -> 409 로 스펙 변경

요거 이유가 뭔가요?

@davin111
Copy link
Member Author

davin111 commented Dec 1, 2022

@davin111 davin111 merged commit 4e3f5fc into develop Dec 1, 2022
@davin111 davin111 deleted the feature/likes branch December 1, 2022 15:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants