Skip to content

Conversation

@hyerinhwang-sailin
Copy link
Collaborator

Related issue 🛠

Work Description ✏️

  1. 리포지토리
  • ClapRepository: 유저별 조회 메서드 findByUserIdAndStampId 추가
  • StampRepositoryImpl: Postgres RETURNING을 활용한 clapCount 원자 증가 쿼리 구현
  1. 서비스
  • ClapService:
    • 자기글 금지 / 입력값 검증
    • Clap 낙관적 락 재시도(최대 3회)
    • Stamp 총합 네이티브 증가
  • StampService: 총합 조회 로직 추가 (getStampClapCount)
  1. 프레젠테이션
  • POST /api/v2/stamp/{stampId}/clap API 추가
  • 요청/응답 DTO(ClapRequest, ClapResponse) 작성
  • StampResponseMapper에 default of(stampId, applied, total) 추가
  1. 테스트
  • ClapServiceTest로 주요 시나리오(정상, 상한, 락 재시도 등) 단위 검증
  • ClapServiceTest 전체 케이스 PASS (7/7)

동시성/락 설계 의사결정 배경

  1. 왜 Clap은 낙관적 락(@Version + 재시도) 인가?
  • 경합 특성: Clap은 “유저×게시글 1행”만 갱신. 같은 사용자가 같은 순간 동시에 여러 요청을 던질 확률은 상대적으로 낮고, 서로 다른 사용자는 서로 다른 행을 만지는 경우가 대부분.
  • 낙관적 락 장점: 락을 선점하지 않으므로 대기 시간이 없고, 충돌이 일어났을 때만 재시도하면 됨. 요청 수가 늘어도 병목이 적다.
  • 구현 단순성: @Version 하나로 충돌 감지 → ObjectOptimisticLockingFailureException일 때만 짧게 재시도(최대 3회).
    추가로, (stamp_id, user_id) 유니크 제약으로 최초 생성 경합은 DB가 보장하고, 충돌 시 재조회로 해결.
  • 대안 검토 & 배제
    • 비관적 락(PESSIMISTIC_WRITE): 단일 row라도 잠금 유지 시간 동안 대기자가. 생겨 병목. 클랩은 짧고 빈번한 갱신이라 과도.
    • 애플리케이션 분산락/뮤텍스: 분산 환경/운영 비용↑, 고가용성 고려 필요. 카운터. 수준에는 과함.
      => 결론: Clap은 낙관적 락 + 재시도가 성능·단순성·정합성을 동시에 만족.
  1. 왜 Stamp 총합은 네이티브 원자 증가 인가?
  • 경합 특성: 인기 게시글에 여러 유저가 동시에 박수. 같은 stamp_id 한 행(총합)을 여러 트랜잭션이 함께 갱신 → 충돌 확률 높음.
  • 원자 증가 장점:
    • DB 레벨에서 clap_count = clap_count + :increment가 원자적으로 수행되어 정합성 보장.
    • Postgres의 RETURNING으로 증가 후 값/버전을 한 번에 가져옴.
    • JPA @Version 충돌 핸들링/재시도가 필요 없음(총합에 대해서는).
  • 버전 동기화: 네이티브 쿼리에서 version = version + 1까지 함께 수행 → JPA 엔티티의 버전 의미와 일치. (추후 다른 필드를 JPA로 수정할 때도 버전 일관성 유지)
  • 대안 검토 & 배제
    • Stamp도 낙관적 락: 인기 글에서 충돌 빈번 → 재시도 비용↑, 실패율↑.
    • 비관적 락: 한 행을 둘러싼 경합이 높을수록 대기/타임아웃 리스크↑.
    • Redis 카운터/큐: 초고트래픽엔 유효하지만 현재 요구 범위에선 인프라 복잡도↑.
      => 결론: Stamp 총합은 **네이티브 원자 증가(RETURNING)**가 고경합 시나리오에서 가장 안정적·효율적.
  1. 일관성 보장 방식 요약
  • 유저별 상한(50): Clap.incrementClapCount()가 단일 진실원천. 실제 적용량을 반환하므로 서비스는 그 값만 총합에 반영.
  • 총합 반영 순서:
    1. Clap 갱신(@Version 기반) → 실제 적용량 applied 확보
    2. Stamp 총합을 네이티브로 원자 증가(RETURNING)
  • 트랜잭션 경계: addClap() 트랜잭션 내에서 둘 다 처리. 총합 증가 쿼리는 **최종적으로 적용된 수(applied)**만 반영하므로, 부분 실패/중복 반영 방지.
  • 최초 생성 경합: (stamp_id, user_id) 유니크 제약 + DataIntegrityViolationException 캐치 후 재조회로 해결.

Trouble Shooting ⚽️

Related ScreenShot 📷

Uncompleted Tasks 😅

To Reviewers 📢

@hyerinhwang-sailin hyerinhwang-sailin linked an issue Oct 17, 2025 that may be closed by this pull request
1 task
@hyerinhwang-sailin hyerinhwang-sailin self-assigned this Oct 17, 2025
@hyerinhwang-sailin hyerinhwang-sailin added the ✨ Feat 새로운 피쳐 생성 label Oct 17, 2025
Copy link
Member

@jher235 jher235 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 +89 to +101
private Clap createClapSafely(Long userId, Long stampId) {
try {
Clap fresh = Clap.builder()
.userId(userId)
.stampId(stampId)
.clapCount(0)
.build();
return clapRepository.saveAndFlush(fresh);
} catch (DataIntegrityViolationException e) {
return clapRepository.findByUserIdAndStampId(userId, stampId)
.orElseThrow(() -> e);
}
}
Copy link
Member

Choose a reason for hiding this comment

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

이렇게 생성하는 경우에 clapCount까지 한번에 반영하지 않고 insert, update 를 진행하도록 하신 이유가 있을까요??

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

새 Clap 행을 만들 때 바로 clapCount를 더하면
생성 시점 경합이나 상한 계산이 SQL 단으로 분산돼 정합성 관리가 어려워져서
항상 0으로 초기화 후, 증가 로직을 단일 경로(incrementClapCount())로 통일하고자 했습니다!

Copy link
Member

@huncozyboy huncozyboy 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 +113 to +114
int appliedCount = soptampFacade.addClap(userId, stampId, request.getClapCount());
int totalClapCount = soptampFacade.getStampClapCount(stampId);
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 +23 to +24
@ExtendWith(MockitoExtension.class)
class ClapServiceTest {
Copy link
Member

Choose a reason for hiding this comment

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

테스트 코드까지 작성해주셨네요
좋습니다 🚀

@hyerinhwang-sailin hyerinhwang-sailin merged commit 855222d into dev Oct 18, 2025
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨ Feat 새로운 피쳐 생성 size/L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEAT] 솝탬프 박수 기능 구현

3 participants