[좋아요 수 기준 HOT 게시판 이동 시 동시성 이슈 해결] 비관적 락을 이용한 게시글 좋아요 수 Race Condition 해결

2025. 4. 9. 15:17·프로젝트/Wedle
728x90
반응형

✨ 들어가며

유저가 게시글에 좋아요를 누르면 일정 수치 이상일 때 'HOT 게시판'으로 이동시키는 기능을 구현했습니다. 이 기능은 좋아요 수가 많을수록 더 많은 유저들에게 노출되도록 유도하는 핵심적인 UX입니다.

그런데 분명 좋아요 수가 3 이상인데도 HOT 게시판에 올라가지 않거나, 반대로 좋아요 수가 3 미만인데도 HOT 게시판에 남아있는 경우가 생겼습니다. 다수의 사용자가 동시에 좋아요를 누르거나 취소할 때 Race Condition이 발생했습니다.


🧨 기존 코드의 문제점

post.increaseLike();
if (post.getLikeCount() >= 3) {
    moveToHotBoard(post);
}
 post.decreaseLike();
if (post.getLikeCount() < 3) {
	removeToHotBoard(post);
}

초기에는 좋아요 수는 Post 객체의 likeCount 필드로 관리됐고, 매 호출 시 증가 혹은 감소시켰습니다. 

  • 동시성 문제: 여러 사용자가 동시에 좋아요를 누르면 post.getLikeCount()는 정확하지 않을 수 있습니다. 예를 들어, 좋아요 수가 2일 때 두 사용자가 동시에 좋아요를 누르면, 둘 다 post.getLikeCount()가 2라고 보고 moveToHotBoard()를 호출하지 않았습니다.
  • DB와 불일치: 좋아요 수는 별도 테이블 PostLike을 기반으로 계산되지만 이를 별도로 유지하는 필드를 수동으로 관리하고 있었기에  결국 Post 객체와 DB의 상태가 불일치할 수 있었습니다.

🚀 비관적 락을 사용한 개선

1. 비관적 락(Pessimistic Lock) 적용

동시성을 가장 확실하게 제어하기 위해 Post 조회 시 PESSIMISTIC_WRITE 락을 걸어 변경 중인 데이터를 다른 트랜잭션에서 읽지 못하게 했습니다.

@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("select p from Post p where p.id = :id")
Optional<Post> findByIdWithPessimisticLock(@Param("id") Long id);

 

2. PostLike 저장/삭제 → countByPostId로 좋아요 수 계산

long likeCount = postLikeRepository.countByPostId(postId);

좋아요 수를 직접 증가시키지 않고, PostLike 테이블에서 카운트하여 정확한 수치를 기준으로 HOT 게시판 이동 여부를 판단하도록 했습니다.

 

3. 좋아요 트랜잭션 분리 및 원자화

  • PostLike 삽입/삭제
  • Post 비관적 락 조회
  • postLikeRepository.countByPostId()로 현재 정확한 좋아요 수 조회
  • 기준 수치 이상이면 moveToHotBoard() 호출 or 기준 수치 미만이면 removeFromHotBoard() 호출
Post post = postRepository.findByIdWithPessimisticLock(postId).orElseThrow(PostNotFoundException::new);
long likeCount = postLikeRepository.countByPostId(postId);

if (likeCount >= HOT_BOARD_THRESHOLD && !post.isHotBoard()) {
    moveToHotBoard(post);
}
Post post = postRepository.findByIdWithPessimisticLock(postId).orElseThrow(PostNotFoundException::new);
long likeCount = postLikeRepository.countByPostId(postId);

if (likeCount < HOT_BOARD_THRESHOLD && post.isHotBoard()) {
	removeFromHotBoard(post);
}

📊 효과는?

  • Race Condition 제거
    • 다수 사용자가 동시에 좋아요를 누르는 상황에서도 일관된 로직으로 HOT 게시판 진입 조건을 판단
    • Pessimistic Lock을 사용해 게시글 데이터를 선점적으로 잠금 처리하여, 중복 이동이나 조건 무시 방지
  • 정확한 좋아요 수 기반 판단
    • Post 객체 내 증가형 likeCount 필드가 아닌, 항상 최신 상태의 PostLike 테이블을 직접 카운트하여 정확하고 실시간 반영된 수치 사용
    • 이로 인해 좋아요 수 오류 및 동기화 문제 제거
  • 트랜잭션 안정성 확보
    • 좋아요 저장, 삭제, 좋아요 수 계산, HOT 게시판 이동을 동일 트랜잭션 내에서 처리하여 중간 실패나 일관성 깨짐 방지
    • HOT 게시판 이동 여부 판단도 함께 묶여 비즈니스 로직의 신뢰성 강화

✅ 정리하며

좋아요 수는 단순해 보이지만, 동시성 문제가 숨어 있는 의외의 난관이었습니다 🥹
아무 고민 없이 단순히 수치를 증가시키는 로직만 두면 여러 사용자가 동시에 접근하는 상황에서 의도치 않은 버그가 쉽게 발생할 수 있었습니다.

👉🏻 이를 해결하기 위해 비관적 락(Pessimistic Lock) 을 도입하고, 좋아요 수를 PostLike 테이블에서 직접 계산하는 구조로 변경했습니다. 그 결과, 트래픽이 몰리는 상황에서도 일관성과 신뢰성을 유지할 수 있게 되었습니다.

 

728x90
반응형
저작자표시 비영리 변경금지 (새창열림)

'프로젝트 > Wedle' 카테고리의 다른 글

[자주 조회되는 데이터 Redis 캐싱] – 학사 캘린더 캐싱  (1) 2025.04.09
[JPA 성능 최적화] @ManyToOne(fetch = FetchType.LAZY) 와 Fetch Join 활용하기  (0) 2025.04.08
[JPA 성능 최적화] 반복되는 save() 대신 saveAll()로 벌크 저장  (0) 2025.04.08
[Spring Boot/Kafka/Stomp] 실시간 채팅 구현 – 7. 채팅 전송 및 조회  (0) 2025.03.19
[Spring Boot/Kafka/Stomp] 실시간 채팅 구현 - 6. 채팅방 생성  (0) 2025.03.19
'프로젝트/Wedle' 카테고리의 다른 글
  • [자주 조회되는 데이터 Redis 캐싱] – 학사 캘린더 캐싱
  • [JPA 성능 최적화] @ManyToOne(fetch = FetchType.LAZY) 와 Fetch Join 활용하기
  • [JPA 성능 최적화] 반복되는 save() 대신 saveAll()로 벌크 저장
  • [Spring Boot/Kafka/Stomp] 실시간 채팅 구현 – 7. 채팅 전송 및 조회
예롱메롱
예롱메롱
  • 예롱메롱
    예롱이의 개발 블로그
    예롱메롱
  • 전체
    오늘
    어제
    • 전체보기 (274)
      • 프로젝트 (35)
        • Wedle (12)
        • 인스타그램 클론 코딩 (13)
        • 스프링 부트와 AWS로 혼자 구현하는 웹 서비스 (10)
      • 인프런 Spring 강의 정리 (79)
        • 스프링 입문 - 코드로 배우는 스프링 부트, 웹 .. (7)
        • Spring 핵심 원리 - 기본편 (9)
        • 모든 개발자를 위한 HTTP 웹 기본 지식 (8)
        • 자바 ORM 표준 JPA 프로그래밍 - 기본편 (11)
        • 실전! 스프링 부트와 JPA 활용1 - 웹 애플리.. (6)
        • 실전! 스프링 부트와 JPA 활용2 - API 개.. (5)
        • 실전! 스프링 데이터 JPA (7)
        • 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술 (7)
        • 스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 (11)
        • 실전! Querydsl (8)
      • Cloud (3)
      • Spring (6)
        • spring boot (5)
        • 소셜로그인 (1)
      • Docker (2)
      • DevOps (0)
      • Coding Test (114)
        • Programmers (37)
        • Baekjoon (76)
      • KB It's Your Life 6기 (1)
      • CS (18)
        • 알고리즘 (13)
        • 컴퓨터 구조 (1)
        • Operating System (0)
        • Network (0)
        • Database (4)
      • git (1)
      • Language (15)
        • Java (5)
        • C++ (6)
        • Python (4)
    • GITHUB GITHUB
    • INSTAGRAM INSTAGRAM
  • hELLO· Designed By정상우.v4.10.3
예롱메롱
[좋아요 수 기준 HOT 게시판 이동 시 동시성 이슈 해결] 비관적 락을 이용한 게시글 좋아요 수 Race Condition 해결
상단으로

티스토리툴바