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

좋아요 기능 버그 해결을 위한 캐싱 자료구조 및 로직 변경 #788

Merged
merged 33 commits into from
Feb 3, 2024

Conversation

mcodnjs
Copy link
Collaborator

@mcodnjs mcodnjs commented Jan 29, 2024

📄 Summary

#784

기존 문제점 간단 요약

  1. 캐시에 없을 경우 DB 조회를 하지만, 조회한 결과를 캐시에 업데이트하지 않음
  2. 좋아요 업데이트 시, 캐시에 존재하는 키들에 대해서만 업데이트 수행. 없는 키들에 대해선 업데이트 무시하는 문제
  3. DB와 캐시의 정합성 불일치
    1. isLike True, False 검사를 하지 않고 레디스에 있는 값 전체 saveAll
    2. batchUpdate 시 unique 키 예외 발생으로 batch 전체가 실패하는 문제 발생 ⇒ cache → db 반영이 안됨
  4. 커뮤니티 전체 조회의 경우 캐시 조회 로직 사용하지 않음 → db와 캐시 정합성이 맞지 않아 세부 조회랑 좋아요 값이 달리지는 문제 발생

개선방안

우리가 필요한 데이터와 Redis의 자료구조

우리가 좋아요에서 필요한 데이터

  • 멤버가 특정 여행을 좋아요 했는지 여부 (isLike)
  • 여행의 좋아요 개수 (likeCount)

기존에는 LikeCount, MemberLike 2개의 값을 통해 관리를 하였는데, 2개로 관리하게 되면 두 데이터에 대한 정합성을 맞추기도 어렵고, 캐싱 전략을 세우기도 어려웠어요 .. 그래서 다양한 자료구조를 지원하는 Redis의 장점을 이용해서 구조를 좀 변경했어요

Redis Set 자료구조 사용

<tripId>: Set<meberId>

생각한 key-value 형태는 위와 같은데요, tripId를 key로 갖고, 좋아요 한 멤버의 ID를 Set 으로 갖는 형태에요. 알고리즘을 잘하시는 여러분들은 아시다시피 Set은 데이터를 삽입하고 삭제하는데 O(1)의 시간복잡도를 갖고, 중복된 데이터가 들어갈 일도 없어요. 그래서 아래 구조를 채택했고 우리가 필요한 명령어들에 대해 찾아봤어요.

SADD like:1 1 # trip_id 1 여행에 member_id 1의 좋아요 추가
SADD like:1 2 # trip_id 1 여행에 member_id 2의 좋아요 추가

SREM like:1 1 # trip_id 1 여행에 member_id 1의 좋아요 삭제

SCARD like:1 # trip_id 1 여행의 좋아요 개수 -> 1

SISMEMBER like:1 1 # trip_id 1 여행에 member_id 1의 좋아요 여부 -> 0
SISMEMBER like:1 2 # trip_id 1 여행에 member_id 2의 좋아요 여부 -> 1

위 명령어는 모두 O(1)의 시간복잡도를 갖고, 이 명령어들로 우리가 필요한 데이터를 모두 뽑을 수 있어요. 따라서 스프링에서 위 명령어를 사용하도록 구현했어요.

좋아요 캐시 읽기/쓰기 전략?

좋아요 조회

  • TripId를 캐시에 조회
    → HIT → 캐시 결과를 클라이언트에 반환
    → MISS → 없는 TripId들을 묶어서 db에 1회 조회 → db 조회 결과를 클라이언트에 반환 → 비동기적으로? db 조회 결과를 캐시에 업데이트

⇒ 읽기 전략으로 말하자면, Look Aside 패턴이라고 할 수 있을거 같아요

좋아요 업데이트

  • 캐시에 업데이트 요청 온 TripId 조회
    → HIT → 좋아요 업데이트 사항 캐시에 반영
    → MISS → 없는 TripId를 db에 조회 → 조회한 결과를 캐시에 쓰기 → 좋아요 업데이트 사항 캐시에 반영
  • 캐시에 업데이트된 값들은 스케줄링으로 DB에 한번에 업데이트! (기존 로직 그대로)
    • 스케줄링 주기는 캐시의 TTL 보다 짧게 설정.
    • TTL 주기는 90분, 스케줄링 주기는 1시간

⇒ 이것도 쓰기 전략으로 말하자면, 기존에 사용하고 있던 Write Back 패턴이라고 할 수 있을거 같아요

스프링 구현

RedisRepository → RedisTemplate으로 변경

Redis를 spring-data-redis를 사용하여 구현하는 방법은 2가지가 있는데, 1) 현재 사용하고 있는 RedisRepository를 통하여 JPA 처럼 사용하는 방식(하이레벨), 2) RedisTemplate을 사용하는 방식(로우레벨)이 있어요.

1번 방법인 RedisRepository는 Redis의 자료구조를 사용하는데는 한계가 있었어요 .. 그리고 이 방법은 저장할 때 키에 해당하는 데이터를 모두 삭제하는 DEL 명령어를 날리고 기존 데이터 + 새로 들어온 데이터 를 합쳐서 다시 HMSET 명령어를 날리더라구요. 조회할 때도 findById()의 익숙한 명령어로 처리할 수 있지만, O(n)명령어를 사용하길래 비효율적으로 느껴졌어요. (찾아보니 실제로 더 오래걸린다고 함. 날리는 명령어가 서로 다름)

그래서 조금 복잡하더라도 RedisTemplate을 통한 구현을 통해 위에서 언급한 Set 자료구조를 사용하는 걸 목표로 했어요. 크게 복잡한건 없고, 조금 생소한 메서드들(레디스에서 제공하는 메서드 명이랑 같음)이 존재할 뿐 .. 편하게 구현되어 있어요.

고민

구현하다가 문제를 맞닥뜨렸는데 Redis는 empty set과 null을 구분하지 못한대요 ..
이게 무슨 말이냐묜 스프링으로 치면 이 아래 둘을 구분하지 못하는거 ?

  • Set emptySet = new HashSet<>();
  • Set nullSet;

이게 언제 문제가 되냐? 좋아요 수가 0개인 경우 문제가 됩니다.

좋아요 수가 0개인 경우 항상 Set이 비어있잖아요! 그럼 레디스는 그냥 키 자체가 존재하지 않는다고 판단해요. 애초에 빈 emptySet을 넣을 수 있는 명령어도 없구요! 1개 남은 값을 삭제하면 Set의 개수가 0개이기 때문에 자동으로 키가 삭제됩니다 .. 우리는 그게 캐싱이 안되서 키가 존재하지 않는건지, 좋아요 수가 0개라서 존재하지 않는건지 알 방법이 없어요 ㅠ 그럼 좋아요 수가 0개인 Trip은 항상 DB 조회를 하게 됩니다

관련해서 Redis issue가 있길래 첨부했어요. 차차 업데이트될거 같은데 어쨌든 현재는 이 둘을 구분할 수 없음.

redis/redis#6048

그래서 대응 방법이 2가지가 있어요.

  1. 저희만의 플래그 value를 하나 집어넣는 방법
  2. 현재 Like 캐시와 TTL이 아주 똑같고 정합성이 완전히 보장되는 likeCount를 하나 더 만드는 방법

2가지 방식 다 장단점이 있는데 그냥 2번보다는 1번이 간단해보여서 1번으로 구현했어요
이 방법 외에 구조를 바꿔서라도 더 좋은 방법이 있다면 많은 의견 부탁드리겠습니다 ..!

🙋🏻 More

그래도 배운게 많은 핫픽스였습니다.. 핫픽스라기엔 일주일 걸렸는데 생각할게 넘 많았어요 기존의 로직도 잘 모르고 있었음
구현하면서 고려할 것도 넘 많고 진짜 자잘한 버그가 많이 생겨서 수동 QA를 많이 했는데, 리뷰 받으면서 테스트를 좀 추가해놓겠습니다 ....

@mcodnjs mcodnjs self-assigned this Jan 29, 2024
Copy link

github-actions bot commented Jan 29, 2024

📝 Jacoco Test Coverage

Total Project Coverage 80.37% 🍏
File Coverage [87.28%]
LikeInfo.java 100% 🍏
LikeElement.java 100% 🍏
RedisConfig.java 100% 🍏
LikeSyncScheduler.java 99.07% 🍏
LikeService.java 94.66% 🍏
LikeElements.java 92.86% 🍏
CommunityService.java 85.19% 🍏
LikeRedisConstants.java 76.92%
CustomLikeRepositoryImpl.java 67.46%

Copy link
Member

@jjongwa jjongwa left a comment

Choose a reason for hiding this comment

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

image

야무지게 버그 고쳐주셔서 감사합니다 🙇

Copy link
Collaborator

@LJW25 LJW25 left a comment

Choose a reason for hiding this comment

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

리뷰..라기엔 컨벤션 리뷰밖에 없긴 하지만..? 수고하셨습니다 리 더 라온
어마어마한 핫픽스였군요.

그래서 대응 방법이 2가지가 있어요.

  1. 저희만의 플래그 value를 하나 집어넣는 방법
  2. 현재 Like 캐시와 TTL이 아주 똑같고 정합성이 완전히 보장되는 likeCount를 하나 더 만드는 방법

2가지 방식 다 장단점이 있는데 그냥 2번보다는 1번이 간단해보여서 1번으로 구현했어요

대 박 찬 성

Comment on lines 132 to 133
final String key = generateLikeKey(tripId);
if (TRUE.equals(redisTemplate.hasKey(key))) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

이 두개 묶어서 boolean 함수로 빼는건 어떨까요? 예를들면 isTripInRedis() 같은걸로?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

아아아 .. 진짜 좋은데 이 부분이 넘넘넘 애매한거 같아요 ㅠㅠ
결론은 바꾸지 않았는데 .. 못하겠어서 안하거임. 안하고 싶어서 안하거 아님..
key가 여러 곳에서 사용되고, 저 분기문이 이곳에서만 사용되는게 아니라서 ㅠㅠ
홍고말처럼 Redis 영향이 미치는 애들은 따로 클래스 분리해서 사용해야할거 가타요

Comment on lines 127 to 129
private Map<Long, LikeInfo> getLikeInfoByTripIds(final Long memberId, final List<Long> tripIds) {
final Map<Long, LikeInfo> likeInfoByTrip = new HashMap<>();

Copy link
Collaborator

Choose a reason for hiding this comment

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

메소드 순서!
getLikeInfoByTripIds를 getTripDetail 위로 올리는건 어떨까요? 호출 순서대로!
저는 지금이 음 이게 어디서 호출돼서 무슨용도로 쓰이는거지? 가 한눈에 안들어온다고 생각했는데 Controller에서 호출되는 기준이면 지금 그대로 유지도 괜찮긴 합니당.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

제가 보기에도 어지럽습니다 .. 우선 반영했습니다 ㅎㅎ
refactor: 메서드 위치 변경

Comment on lines 141 to 142
final List<LikeElement> likeElementByTripIds = customLikeRepository.findLikeElementByTripIds(nonCachedTripIds);
likeElementByTripIds.addAll(getEmptyLikeElements(likeElementByTripIds, nonCachedTripIds));
Copy link
Collaborator

Choose a reason for hiding this comment

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

위에는 List<LikeElement>가 likeElementByTripIds고 아래 메소드명은 getEmptyLikeElements인데 통일시키면 어떨까요?
둘의 반환값이 결국 같은 종류인것 같아서요!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Comment on lines 31 to 32
private void removeMemberInLike(final String key, final Long memberId, final Boolean isLike,
final SetOperations<String, Object> opsForSet) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

줄바꿈! 한줄에 하나!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

리팩토링하면서 메서드 내에서 가지게 만들었습니닷!

refactor: 업데이트 메서드 인자 수정 및 로직 리팩토링

Copy link
Collaborator

@hgo641 hgo641 left a comment

Choose a reason for hiding this comment

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

좋은데요? 개운하네요

나중에 속도 측정하시게 되면 저도 알려주세요 헷❤️

Comment on lines 31 to 32
private void removeMemberInLike(final String key, final Long memberId, final Boolean isLike,
final SetOperations<String, Object> opsForSet) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

오 opsForSet 인자로 넘겨주는 대신, 아예 멤버 변수로 두는 것도 좋을 것 같아요

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

요놈 RedisTemplate 빈 주입 받고 할당할 수 있는 애라 멤버 변수는 어려울거 같고 ..? 메서드 내 지역변수로 다시 할당해줬습니다!

refactor: 업데이트 메서드 인자 수정 및 로직 리팩토링

@@ -34,4 +48,46 @@ private MapSqlParameterSource getLikeToSqlParameterSource(final Likes likes) {
.addValue("tripId", likes.getTripId())
.addValue("memberId", likes.getMemberId());
}

Copy link
Collaborator

Choose a reason for hiding this comment

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

요 아래 메서드들 왜 LikeRepository 에서 CustomLikeRepository로 옮겨진 건가요? @Query 가독성이 나빠서인가요?.? 궁금합니닷.
달라진 게 parseMemberIds()가 추가된 것 말고는 모르겠는데 혹시 이전 메서드에서는 memberIds가 제대로 안불러와졌던건가요?..?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

음음 일단, 기존에 사용하던 쿼리랑 뽑아오는 값이 달라요!!! GROUP_CONCAT 쪽 주의!!

-- 기존
l.tripId, 
COUNT(l.memberId), 
EXISTS(SELECT 1 FROM Likes l_1 WHERE l_1.memberId = :memberId AND l_1.tripId = l.tripId)

-- 현재
l.trip_id AS tripId, 
COUNT(l.id) AS likeCount, 
GROUP_CONCAT(l.member_id) AS memberIds

Redis 자료구조를 Set으로 사용하면서 특정 멤버가 좋아요 했는지 여부 뿐만 아니라 memberId 정보를 모두 캐싱해야 했어요!!
그래서 isLike를 뽑아오던 기존에 쿼리 대신 Set<memberId> 이 친구를 뽑아오는 쿼리로 변경하였는데,
하이버네이트 문법 상으로 GROUP_CONCAT은 적용할 수 없는 한계가 있었고, nativeQuery 옵션을 주면 쿼리는 잘 날라가는데 반환값이 LikeElement로 매핑이 안되서 직접 파싱해줘야 했어요 ㅠㅅㅠ 그래서 쩔 수 없이 직접 구현했습니다 ㅎㅎ ..

Comment on lines +32 to +33
redisTemplate.setValueSerializer(new GenericToStringSerializer<>(Long.class));
return redisTemplate;
Copy link
Collaborator

Choose a reason for hiding this comment

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

야무지네요

Comment on lines 21 to 23
public LikeInfo toLikeMap(final Long memberId) {
return new LikeInfo(this.getLikeCount(), this.isLike(memberId));
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

메서드명 toLikeInfo로 통일 어떠신가요 헷
아 근데 LikeInfo랑 LikeElement 변수명 진심 너무 헷갈리네요. 얘네 코드 볼 때마다 dto들어가서 변수 뭐뭐있는지 항상 보고오는듯 ㅋㅋ ㅠ

근데 마땅한 이름은 생각이 안나서 답답레전드
총 likeCount랑 member의 like정보를 하나의 dto에 넣은 순간 이름 겁나 애매해짐ㅋㅋㅋ 하

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

일단 메서드는 통일완! refactor: toLikeInfo로 메서드 네이밍 변경
저도 넘 헷갈려요 .. 근데 나도 진짜 마땅한 이름이 안떠올라 ㅠㅠ

LikeInfo -> LikeSummary (요약본)
LikeElement -> LikeDetail (세부정보까지 있음)
이건 어때요 .. ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

근데 LikeInfo 자체를 삭제해볼 수 있지 않을까 .. ?

final Map<Long, Boolean> tripLikeStatusMap = memberLike.get().getLikeStatusForTrip();
if (tripLikeStatusMap.containsKey(tripId)) {
return new LikeElement(tripId, likeCount.get().getCount(), tripLikeStatusMap.get(tripId));
private Map<Long, LikeInfo> getLikeInfoByTripIds(final Long memberId, final List<Long> tripIds) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

CommunityService에서 reids에서 like를 가져오고 캐싱하는 메서드를 다른 클래스로 분리하면 좋을 것 같네요!!! 근데 할 일이 많아지니까... 일단 여기까지 머지하고 차차 리팩토링하는 것도 좋습니닷

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

완전 느끼고 있는데요 ..
머리를 좀 더 식히고 다음 기회에 리팩토링 하는 것도 좋은거 같아요 히히

Copy link
Collaborator Author

@mcodnjs mcodnjs left a comment

Choose a reason for hiding this comment

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

흠 .. 아니 여러분 저 진짜 꼼꼼하게 확인하고 버그 체킹도 했는데 테스트를 짜면서 진짜 신기하게 또 버그가 나더라구요 진짜 신기하죠? 네 그리고 레디스 도입하면서 로직이 넘넘 복잡해진거 같아요 ㅠ 클린 코드 한다 했지만 다시 보니 첨보는 여러분들이 보기에 정말 쉽지 않았을거 같네요! 하하! 죄송합니다!

분기문으로 레디스에 없으면 디비에서 조회하고 걔를 또 캐시에 저장하고 등등 이런 복잡하고 중복되는 로직도 생겼어요 .. 나중에 누가 리팩토링해줬으면 좋겠다. 일단 테스트는 추가해놨습니다! 별다른 의견이 없다면 내일 중으로 머지하는걸로 할게요오

@@ -34,4 +48,46 @@ private MapSqlParameterSource getLikeToSqlParameterSource(final Likes likes) {
.addValue("tripId", likes.getTripId())
.addValue("memberId", likes.getMemberId());
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

음음 일단, 기존에 사용하던 쿼리랑 뽑아오는 값이 달라요!!! GROUP_CONCAT 쪽 주의!!

-- 기존
l.tripId, 
COUNT(l.memberId), 
EXISTS(SELECT 1 FROM Likes l_1 WHERE l_1.memberId = :memberId AND l_1.tripId = l.tripId)

-- 현재
l.trip_id AS tripId, 
COUNT(l.id) AS likeCount, 
GROUP_CONCAT(l.member_id) AS memberIds

Redis 자료구조를 Set으로 사용하면서 특정 멤버가 좋아요 했는지 여부 뿐만 아니라 memberId 정보를 모두 캐싱해야 했어요!!
그래서 isLike를 뽑아오던 기존에 쿼리 대신 Set<memberId> 이 친구를 뽑아오는 쿼리로 변경하였는데,
하이버네이트 문법 상으로 GROUP_CONCAT은 적용할 수 없는 한계가 있었고, nativeQuery 옵션을 주면 쿼리는 잘 날라가는데 반환값이 LikeElement로 매핑이 안되서 직접 파싱해줘야 했어요 ㅠㅅㅠ 그래서 쩔 수 없이 직접 구현했습니다 ㅎㅎ ..

Comment on lines 132 to 133
final String key = generateLikeKey(tripId);
if (TRUE.equals(redisTemplate.hasKey(key))) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

아아아 .. 진짜 좋은데 이 부분이 넘넘넘 애매한거 같아요 ㅠㅠ
결론은 바꾸지 않았는데 .. 못하겠어서 안하거임. 안하고 싶어서 안하거 아님..
key가 여러 곳에서 사용되고, 저 분기문이 이곳에서만 사용되는게 아니라서 ㅠㅠ
홍고말처럼 Redis 영향이 미치는 애들은 따로 클래스 분리해서 사용해야할거 가타요

Comment on lines 127 to 129
private Map<Long, LikeInfo> getLikeInfoByTripIds(final Long memberId, final List<Long> tripIds) {
final Map<Long, LikeInfo> likeInfoByTrip = new HashMap<>();

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

제가 보기에도 어지럽습니다 .. 우선 반영했습니다 ㅎㅎ
refactor: 메서드 위치 변경

final Map<Long, Boolean> tripLikeStatusMap = memberLike.get().getLikeStatusForTrip();
if (tripLikeStatusMap.containsKey(tripId)) {
return new LikeElement(tripId, likeCount.get().getCount(), tripLikeStatusMap.get(tripId));
private Map<Long, LikeInfo> getLikeInfoByTripIds(final Long memberId, final List<Long> tripIds) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

완전 느끼고 있는데요 ..
머리를 좀 더 식히고 다음 기회에 리팩토링 하는 것도 좋은거 같아요 히히

Comment on lines 21 to 23
public LikeInfo toLikeMap(final Long memberId) {
return new LikeInfo(this.getLikeCount(), this.isLike(memberId));
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

일단 메서드는 통일완! refactor: toLikeInfo로 메서드 네이밍 변경
저도 넘 헷갈려요 .. 근데 나도 진짜 마땅한 이름이 안떠올라 ㅠㅠ

LikeInfo -> LikeSummary (요약본)
LikeElement -> LikeDetail (세부정보까지 있음)
이건 어때요 .. ?

Comment on lines 21 to 23
public LikeInfo toLikeMap(final Long memberId) {
return new LikeInfo(this.getLikeCount(), this.isLike(memberId));
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

근데 LikeInfo 자체를 삭제해볼 수 있지 않을까 .. ?

Comment on lines 141 to 142
final List<LikeElement> likeElementByTripIds = customLikeRepository.findLikeElementByTripIds(nonCachedTripIds);
likeElementByTripIds.addAll(getEmptyLikeElements(likeElementByTripIds, nonCachedTripIds));
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Comment on lines 31 to 32
private void removeMemberInLike(final String key, final Long memberId, final Boolean isLike,
final SetOperations<String, Object> opsForSet) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

요놈 RedisTemplate 빈 주입 받고 할당할 수 있는 애라 멤버 변수는 어려울거 같고 ..? 메서드 내 지역변수로 다시 할당해줬습니다!

refactor: 업데이트 메서드 인자 수정 및 로직 리팩토링

Comment on lines 31 to 32
private void removeMemberInLike(final String key, final Long memberId, final Boolean isLike,
final SetOperations<String, Object> opsForSet) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

리팩토링하면서 메서드 내에서 가지게 만들었습니닷!

refactor: 업데이트 메서드 인자 수정 및 로직 리팩토링

@mcodnjs mcodnjs merged commit 58a90de into develop Feb 3, 2024
1 check passed
@mcodnjs mcodnjs linked an issue Feb 5, 2024 that may be closed by this pull request
@mcodnjs mcodnjs changed the title 좋아요 조회 및 업데이트 버그 수정 좋아요 기능 버그 해결을 위한 캐싱 자료구조 및 로직 변경 Feb 5, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

커뮤니티 여행 좋아요 버그 수정
5 participants