Skip to content

FEAT : 온보딩 반복일정 등록 API 중복 요청 방어 - #368

Merged
Jinseong01 merged 5 commits into
developfrom
feat/367-onboarding-routine-duplicate
Jun 1, 2026
Merged

FEAT : 온보딩 반복일정 등록 API 중복 요청 방어#368
Jinseong01 merged 5 commits into
developfrom
feat/367-onboarding-routine-duplicate

Conversation

@Jinseong01

@Jinseong01 Jinseong01 commented Jun 1, 2026

Copy link
Copy Markdown
Collaborator

👀 관련 이슈

#367

✨ 작업한 내용

1. 온보딩 반복일정 등록 API에 사용자 단위 no-wait 분산락을 적용했습니다.

현재 서비스는 동일 계정의 다중 세션 로그인이 가능한 구조이므로, 여러 기기나 세션에서 같은 사용자의 반복일정 저장 요청이 동시에 들어올 수 있습니다. 반복일정 등록 API는 한 번의 요청에서 여러 개의 Routine을 저장하고, 오늘 요일에 해당하는 반복일정을 기반으로 Schedule까지 함께 생성하는 사용자 단위 작업입니다.
처음에는 user_id, weekday, start_time, end_time 조합에 대한 DB 유니크 제약을 고려했지만, 이는 동일한 Routine row의 중복만 방지할 수 있습니다. 동일 사용자가 여러 세션에서 서로 다른 반복일정 목록으로 동시에 등록 요청을 보내는 경우에는 각 요청의 데이터가 서로 다르므로 유니크 제약에 걸리지 않고 모두 저장될 수 있다고 판단했습니다.
이 경우 사용자 입장에서는 하나의 반복일정 등록 작업만 수행했다고 기대하지만, 실제로는 여러 요청이 모두 성공하여 Routine과 Schedule이 중복 생성되거나 서로 다른 등록 결과가 함께 반영될 수 있습니다. 이에 반복일정 등록 작업 전체를 사용자 단위로 보호하기 위해 no-wait 분산락을 적용했습니다. 동일 사용자의 등록 요청이 동시에 들어오면 하나의 요청만 처리하고, 나머지 요청은 즉시 실패시켜 동일 사용자의 반복일정 등록 작업이 동시에 수행되지 않도록 보장했습니다.

2. 기존 반복일정 저장 여부 검증을 추가했습니다.

현재 서비스는 동일 계정의 다중 세션 로그인이 가능한 구조이므로, 한 기기에서 반복일정 저장을 완료한 뒤 다른 기기에서 뒤늦게 동일 API를 호출할 수 있습니다. 분산락은 동시에 들어온 요청만 제어할 수 있고, 첫 요청이 완료된 이후의 시간차 재호출은 막을 수 없기 때문에 기존 반복일정 저장 여부 검증을 추가했습니다.

3. 온보딩 반복일정 등록 API 테스트를 추가했습니다.

4. 팔로우 API의 분산락을 제거했습니다.

기존에는 중복 팔로우 요청을 방어하기 위해 DB 유니크 키와 분산락을 모두 적용하려 했으나, 팔로우의 경우 핵심 정합성이 follower_user_id, following_user_id 조합의 중복 저장 방지로 귀결되기 때문에 DB 유니크 키만 사용하는 것이 더 적합하다고 판단했습니다. 분산락을 유지하면 Redis 의존성, 락 획득/해제 처리, 장애 시 영향 범위가 추가되는 반면, 팔로우 API에서 얻는 이점은 유니크 제약 위반을 사전에 줄이는 정도에 가깝습니다.
따라서 모든 중복 요청 방어에 분산락을 일괄 적용하기보다는, DB 제약으로 해결 가능한 경우와 애플리케이션 레벨의 동시성 제어가 필요한 경우를 구분해 적용했습니다. 온보딩 반복일정 등록 API처럼 동일 사용자의 여러 세션에서 서로 다른 데이터로 요청이 들어올 수 있고, 사용자 단위 등록 작업 전체의 중복 실행을 제어해야 하는 경우에는 분산락을 적용했습니다. 반면 팔로우 API처럼 follower_user_id, following_user_id 조합 자체가 중복 방지 기준이 되는 경우에는 DB 유니크 키와 예외 변환만으로 단순하고 명확하게 처리하도록 정리했습니다.

5. 온보딩 리마인드 알림 등록 API의 중복 저장 방어를 추가했습니다.

리마인드 알림 등록 API는 온보딩 흐름상 반복일정 등록 이후 호출되는 단계입니다. 반복일정 등록 API에서 사용자 단위 락과 기존 저장 여부 검증을 통해 다중 세션의 반복일정 등록 성공을 1회로 제한하므로, 리마인드 알림 단계에서는 주로 버튼 연타나 동일 요청의 중복 저장을 방어하면 된다고 판단했습니다. 리마인드 알림은 user_id, minutes_before 조합, 스케줄 리마인더는 schedule_id, reminder_time 조합이 중복 방지 기준으로 명확하므로, 분산락 대신 DB 유니크 키와 예외 변환으로 처리했습니다.

🌀 PR Point

X

🍰 참고사항

X

📷 스크린샷 또는 GIF

X

Summary by CodeRabbit

Release Notes

  • New Features

    • 온보딩 루틴 저장 시 중복 등록 방지 기능 추가
  • Bug Fixes

    • 친구 팔로우 기능의 동시성 처리 개선
    • 온보딩 루틴 저장 중 동시 요청으로 인한 데이터 무결성 문제 해결
  • Refactor

    • 동시성 제어 유틸리티 최적화로 시스템 성능 향상

@Jinseong01 Jinseong01 self-assigned this Jun 1, 2026
@Jinseong01 Jinseong01 added ✨ Feature 기능 개발 🔨 Refactor 코드 리팩토링 labels Jun 1, 2026
@Jinseong01 Jinseong01 linked an issue Jun 1, 2026 that may be closed by this pull request
3 tasks
@coderabbitai

coderabbitai Bot commented Jun 1, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

개요

Redisson 분산 락 유틸을 즉시 획득 방식으로 리팩토링하고, 친구 팔로우에서 락 기능을 제거하며, 온보딩 루틴 저장에 새로운 락 기반 처리와 중복 저장 검증을 도입합니다.

변경사항

동시성 락 관리 리팩토링

레이어 / 파일(s) 요약
Redisson 락 유틸 즉시 획득 방식 추가
src/main/java/umc/teumteum/server/global/util/RedissonLockUtil.java
executeWithLockNoWait 및 acquireLockNoWait 메서드를 추가하여 락을 즉시 획득하는 방식을 지원합니다. DEFAULT_TIME_UNIT 상수를 도입하고 tryLock(0, 시간단위) 호출로 대기 없이 즉시 시도합니다.
친구 팔로우 락 기능 제거
src/main/java/umc/teumteum/server/domain/friend/controller/FriendController.java
FriendLockService 의존성을 제거하고 FriendService.follow()를 직접 호출하도록 변경합니다. FriendLockService 인터페이스 및 FriendLockServiceImpl 구현은 완전 삭제됩니다.

온보딩 루틴 저장 락 및 중복 방지

레이어 / 파일(s) 요약
온보딩 락 서비스 신규 추가
src/main/java/umc/teumteum/server/domain/user/service/OnboardingLockService.java, src/main/java/umc/teumteum/server/domain/user/service/OnboardingLockServiceImpl.java, src/main/java/umc/teumteum/server/domain/user/controller/OnboardingController.java
OnboardingLockService 인터페이스 및 구현을 신규 추가하여 saveRoutinesWithLock 메서드를 제공합니다. OnboardingController에서 UserService 대신 OnboardingLockService를 주입하고, saveRoutines 엔드포인트를 락 기반 호출로 변경합니다. ONBOARDING_ROUTINES_LOCK 키로 사용자별 분산 락을 획득합니다.
온보딩 중복 저장 방지 및 저장소 확장
src/main/java/umc/teumteum/server/domain/user/repository/RoutineRepository.java, src/main/java/umc/teumteum/server/domain/user/service/OnboardingServiceImpl.java
RoutineRepository에 existsByUser() 메서드를 추가합니다. OnboardingServiceImpl의 saveRoutines()에서 저장 전 validateRoutineNotSaved() 검증을 호출하여 이미 저장된 사용자를 UserErrorStatus.INVALID_STEP 예외로 차단합니다.

테스트 통합 및 확장

레이어 / 파일(s) 요약
온보딩 락 동시성 통합 테스트
src/test/java/umc/teumteum/server/integration/user/service/OnboardingLockServiceTest.java
FriendLockServiceTest를 OnboardingLockServiceTest로 완전 교체합니다. OnboardingLockService와 OnboardingService를 주입받아 멀티스레드 환경에서 saveRoutinesWithLock의 동시성 안전성을 검증하고, routineRepository 및 scheduleRepository 결과를 확인합니다.
온보딩 서비스 단위 테스트 확장
src/test/java/umc/teumteum/server/unit/user/service/OnboardingServiceTest.java
ScheduleRepository 목 객체를 추가하고 savedRoutine_fail 테스트를 추가하여, 중복 저장 시나리오에서 INVALID_STEP 예외 발생을 검증합니다.

시퀀스 다이어그램

sequenceDiagram
  participant Client
  participant OnboardingController
  participant OnboardingLockService
  participant OnboardingService
  participant RoutineRepository
  Client->>OnboardingController: saveRoutines(request)
  OnboardingController->>OnboardingLockService: saveRoutinesWithLock(request, user)
  OnboardingLockService->>OnboardingService: saveRoutines(request, user)
  OnboardingService->>RoutineRepository: existsByUser(user)
  alt 반복일정 이미 존재
    RoutineRepository-->>OnboardingService: true
    OnboardingService-->>OnboardingLockService: 예외 발생
  else 반복일정 미존재
    RoutineRepository-->>OnboardingService: false
    OnboardingService->>OnboardingService: 루틴 엔티티 변환 및 저장
  end
Loading

예상 코드 리뷰 노력

🎯 3 (Moderate) | ⏱️ ~25 분

관련 이슈

  • UMC8-TeumTeum/BE#367: 온보딩 중복 저장 방지를 위한 사용자별 락과 중복 검증 요청을 구현합니다.

관련 PR

  • UMC8-TeumTeum/BE#261: 친구 팔로우 락 구현을 추가했으나, 본 PR에서 해당 기능이 제거되므로 역방향 변경입니다.
  • UMC8-TeumTeum/BE#362: 본 PR의 FriendLockServiceTest 제거와 관련하여 친구 팔로우 락 동시성 테스트가 영향을 받습니다.

제안 레이블

✅ Test

🐰 락의 춤을 다시 벌이며,
친구 팔로우는 소박하게,
온보딩 루틴은 든든하게 보호받아,
중복 저장 마법은 사라지고,
분산 락이 모두를 안아준다! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 34.62% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목이 주요 변경 사항을 명확하게 요약하고 있습니다. '온보딩 반복일정 등록 API 중복 요청 방어'는 PR의 핵심 목표인 동시 요청 방어 기능을 직접적으로 설명합니다.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/367-onboarding-routine-duplicate

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

github-actions Bot commented Jun 1, 2026

Copy link
Copy Markdown

Test Results

57 tests   57 ✅  4s ⏱️
12 suites   0 💤
12 files     0 ❌

Results for commit 2a0bc98.

♻️ This comment has been updated with latest results.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@src/main/java/umc/teumteum/server/domain/user/service/OnboardingServiceImpl.java`:
- Around line 352-357: The validateRoutineNotSaved method currently throws
OnboardingException with UserErrorStatus.INVALID_STEP which is ambiguous for
duplicate routine saves; add a new enum value
UserErrorStatus.ROUTINE_ALREADY_REGISTERED (e.g., HttpStatus.CONFLICT, code
"ONBOARDING4092", message "반복일정이 이미 등록되어 있습니다.") and update
validateRoutineNotSaved to throw new
OnboardingException(UserErrorStatus.ROUTINE_ALREADY_REGISTERED) when
routineRepository.existsByUser(user) returns true so the error clearly signals a
duplicate routine registration.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 7f8da0af-a933-4578-bce1-928db8839487

📥 Commits

Reviewing files that changed from the base of the PR and between e673a9a and de8a4db.

📒 Files selected for processing (11)
  • src/main/java/umc/teumteum/server/domain/friend/controller/FriendController.java
  • src/main/java/umc/teumteum/server/domain/friend/service/FriendLockService.java
  • src/main/java/umc/teumteum/server/domain/friend/service/FriendLockServiceImpl.java
  • src/main/java/umc/teumteum/server/domain/user/controller/OnboardingController.java
  • src/main/java/umc/teumteum/server/domain/user/repository/RoutineRepository.java
  • src/main/java/umc/teumteum/server/domain/user/service/OnboardingLockService.java
  • src/main/java/umc/teumteum/server/domain/user/service/OnboardingLockServiceImpl.java
  • src/main/java/umc/teumteum/server/domain/user/service/OnboardingServiceImpl.java
  • src/main/java/umc/teumteum/server/global/util/RedissonLockUtil.java
  • src/test/java/umc/teumteum/server/integration/user/service/OnboardingLockServiceTest.java
  • src/test/java/umc/teumteum/server/unit/user/service/OnboardingServiceTest.java
💤 Files with no reviewable changes (2)
  • src/main/java/umc/teumteum/server/domain/friend/service/FriendLockServiceImpl.java
  • src/main/java/umc/teumteum/server/domain/friend/service/FriendLockService.java

@Jinseong01
Jinseong01 merged commit 5ec5b6b into develop Jun 1, 2026
4 checks passed
@Jinseong01
Jinseong01 deleted the feat/367-onboarding-routine-duplicate branch June 1, 2026 10:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨ Feature 기능 개발 🔨 Refactor 코드 리팩토링

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEAT] 온보딩 반복일정 저장 중복 요청 방어

1 participant