1️⃣ 문제 상황
선착순 강의 신청 페이지를 구현하던 중 이상한 현상을 발견했다.
- 강의 목록(/lectures)에서
- “강의 신청하기”를 누르면 상세 페이지로 이동
- 그런데 상세 페이지에 들어가자마자
- 신청 버튼을 누르지도 않았는데 이미 신청된 것처럼 동작
- 어떤 경우에는 사용자가 직접 눌러야 하고,
- 어떤 경우에는 자동으로 신청된 것처럼 보였다.
즉,
“왜 버튼을 누르지 않았는데 신청이 되는 것처럼 보일까?”
라는 의문이 생겼다.
2️⃣ 처음에 했던 오해
처음에는 이런 생각을 했다.
- 버튼에 onclick이 잘못 걸린 건가?
- 페이지 로드 시 POST /queue가 자동으로 호출되는 건가?
- 브라우저가 버튼을 자동으로 submit하는 건가?
하지만 Network 탭을 확인해보니,
페이지에 들어가자마자 신청 API(POST /queue) 가 호출되지는 않았다.
👉 즉, 진짜로 “자동 신청”이 일어나고 있는 건 아니었다.
3️⃣ 원인 분석: 진짜 문제는 “자동 신청”이 아니었다
문제의 핵심은 localStorage + 페이지 로드 시 자동 폴링이었다.
내가 구현한 구조 요약
- 신청 시 서버에서 userKey(UUID)를 발급
- 이 userKey를 브라우저 localStorage에 저장
- 이후 페이지 새로고침/재접속 시에도
- userKey가 있으면 “내 대기 상태”를 다시 조회(폴링)
문제의 코드 일부는 아래와 같았다.
window.onload = startPolling;
그리고 startPolling() 내부에는 이런 로직이 있었다.
const userKey = localStorage.getItem(`lecture:${lectureId}:userKey`);
if (!userKey) return;
applyBtn.disabled = true;
resultArea.classList.remove('hidden');
이게 무슨 의미냐면
- localStorage에 userKey가 남아 있으면
- 페이지에 들어가자마자 자동으로
- 버튼 비활성화
- “대기열 등록 중…” UI 표시
- 대기열 상태 조회 시작
👉 사용자는 이걸 보고
“어? 내가 누르지도 않았는데 신청된 것 같은데?”
라고 느끼게 된 것이다.
즉,
실제로는 신청이 자동으로 된 게 아니라, 이전에 신청했던 기록을 자동으로 ‘이어보기’ 하고 있었던 것
4️⃣ 왜 “가끔은 자동, 가끔은 클릭”처럼 보였을까?
이 현상은 브라우저 상태 차이 때문에 더 헷갈렸다.
- 같은 브라우저/같은 강의
- 이전에 신청했으면 → localStorage에 userKey 있음 → 자동으로 대기 UI
- 새 창, 시크릿 모드, 다른 강의
- userKey 없음 → 버튼 눌러야 시작
그래서 테스트하다 보면:
- 어떤 창에서는 “들어가자마자 신청된 느낌”
- 어떤 창에서는 “직접 눌러야 신청됨”
이렇게 보였던 것이다.
5️⃣ 해결 방향: UX를 명확하게 분리하자
문제의 핵심은 기능이 아니라 UX(사용자 경험) 였다.
내가 원했던 UX는 이거였다❗️
- 페이지에 들어왔다고 해서
- 자동으로 신청되는 느낌 ❌
- 사용자가 의도를 가지고
- “신청하기” 또는
- “이전 신청 이어하기”
- 를 선택하게 하고 싶었다.
그래서 해결 방향을 이렇게 잡았다.
6️⃣ 해결 방법: “이어하기 UI”를 명시적으로 분리
✅ 변경 전
- 페이지 로드 → userKey 있으면 → 바로 폴링 시작
- 사용자는 “자동 신청”처럼 느낀다
✅ 변경 후
- 페이지 로드 시:
- userKey가 있으면
- “이전에 신청한 기록이 있습니다” UI만 표시
- 폴링은 시작하지 않음
- userKey가 있으면
- 사용자가 버튼을 눌러야:
- “이어하기” → 폴링 시작
- “새로 신청” → 기존 userKey 삭제 후 다시 신청
핵심 코드 개념
window.onload = () => {
const userKey = localStorage.getItem(storageKey);
if (!userKey) return;
// 자동 폴링 ❌
// 대신 안내 UI만 보여줌
};
resumeBtn.onclick = () => startPolling();
restartBtn.onclick = () => {
localStorage.removeItem(storageKey);
enqueueNew();
};
7️⃣ 결과
- 더 이상 페이지에 들어가자마자
- 신청 버튼이 눌린 것처럼 보이지 않음
- 사용자가 명확한 선택을 하게 됨
- “이전 신청 이어하기”
- “새로 신청하기”
- Redis 대기열 구조는 그대로 유지하면서
- UX 혼란만 해결
8️⃣ 배운 점 (초보자 관점에서 정리)
이번 트러블슈팅을 통해 배운 점은 다음과 같다.
- 자동 실행되는 로직은 기능보다 UX에 더 큰 영향을 준다
- localStorage는 편리하지만 👉 남아 있는 데이터가 의도치 않은 동작을 만들 수 있다
- “자동으로 이어주는 기능” 👉사용자에게 명확하게 안내하지 않으면 버그처럼 보일 수 있다
- 대규모 트래픽 시스템에서는 👉 “상태 유지”와 “사용자 경험”을 함께 설계해야 한다
9️⃣ 마무리
이번 문제는 Redis나 대기열 로직의 문제가 아니라,
“이전 상태를 자동으로 복구하는 UX”를 어떻게 보여줄 것인가에 대한 문제였다.
기능은 정상적으로 동작하고 있었지만,
사용자 입장에서 오해할 수 있는 부분을 직접 겪으면서
기술 구현만큼 UX 설계도 중요하다는 것을 체감할 수 있었다.
'프로젝트 > 스프링 부트 3 백엔드 개발자 되기 Blog + 선착순 강의 프로젝트' 카테고리의 다른 글
| ⏰💡 선착순 강의에서 비관적 락(PESSIMISTIC LOCK)을 사용한 이유? (0) | 2026.02.15 |
|---|---|
| ⏰ 선착순 강의 대규모 트래픽 처리 – Redis 2차 설계 (0) | 2026.02.15 |
| ⏰ 선착순 강의 대규모 트래픽 처리 – Redis 1차 설계 (0) | 2026.02.14 |
| 👥🚨 좋아요 상태 변경 후 본인 게시글 삭제 오류 해결 (FK 제약 & DB CASCADE) (0) | 2026.02.14 |
| 👥💡 좋아요(Like) 기능 설계: 상태, 집계, 동시성까지 고려한 구현 정리 (0) | 2026.02.14 |