이번 작업은 단순 기능 추가가 아니라, 인프라 구조 변경과 비즈니스 로직 개선이 함께 이루어진 작업이었다.
크게 두 가지 흐름으로 정리된다.
- Redis 제거 및 NestJS 부팅 에러 트러블슈팅
- 복구코드 재발급 기능 구현 (TDD 기반)
1. Redis 제거 과정에서 발생한 ioredis ENOTFOUND 에러
문제 상황
테트리스 랭킹 조회 페이지에서 다음 에러가 발생했다.
[ioredis] Unhandled error event: Error: getaddrinfo ENOTFOUND flexible-hen-82321.upstash.io
이상한 점은 이미 Redis 캐시 코드는 제거한 상태였다는 것이다.
- score.service.ts에서 redis 호출 제거
- 캐시 로직은 더 이상 사용하지 않음
그런데도 애플리케이션은 Redis 연결을 시도하고 있었다.
원인 분석: “호출 제거”와 “모듈 제거”는 다르다
문제의 핵심은 NestJS 구조에 있었다.
RedisModule은 여전히 AppModule과 ScoreModule에 import 되어 있었다.
AppModule → RedisModule
ScoreModule → RedisModule
RedisService 내부의 숨은 부작용
RedisService에는 다음 코드가 있었다.
async onModuleInit() {
this.client = new Redis(process.env.REDIS_URL ?? 'redis://localhost:6379')
}
ioredis는 생성 시점에 바로 연결을 시도한다.
즉:
모듈이 살아있는 순간 Redis 연결도 같이 시작된다
결과
- 코드 호출 제거 ❌
- RedisModule 유지 ❌
- onModuleInit 실행 ❌
- Upstash 연결 실패 ❌
→ ENOTFOUND 에러 발생
추가 문제: RateLimitGuard의 숨은 Redis 의존성
캐시는 제거했지만 RateLimitGuard는 여전히 Redis를 사용하고 있었다.
redis.incr()
redis.expire()
즉 구조는 이렇게 되어 있었다:
- 캐시 ❌
- 레이트리밋 ❌ (여전히 Redis 의존)
선택지
1) Redis 유지
- Upstash 재발급
- REDIS_URL 교체
2) Redis 제거
현재 상황:
- 트래픽 낮음
- 캐시 사용 없음
- 운영 단순화 필요
결론:
Redis 제거가 더 합리적
Redis 제거 작업
1. 모듈 제거
- AppModule / ScoreModule → RedisModule 제거
2. 서비스 제거
- RedisService DI 제거
- 캐시 코드 제거
3. Redis 폴더 삭제
- module
- service
- test
4. 테스트 변경
- Redis mock 기반 테스트 → DB 기반 테스트로 변경
5. RateLimitGuard 변경
Redis 기반 → 메모리 기반 Fixed Window 방식
type Bucket = { count: number; resetAt: number }
private readonly buckets = new Map<string, Bucket>()
트레이드오프
장점
- 외부 Redis 제거
- 배포 단순화
- 비용 감소
- 장애 포인트 감소
한계
- 멀티 인스턴스 환경에서 공유 불가
- 서버 확장 시 rate limit 정확도 깨짐
핵심 교훈
“코드를 지웠다”와 “의존성을 제거했다”는 완전히 다르다
NestJS에서는 특히 다음이 중요하다:
- Module imports
- Provider lifecycle
- onModuleInit side effect
2. 복구코드 재발급 기능 구현 (TDD 적용)
Redis 제거 작업과 함께 복구코드 재발급 기능도 개선했다.
요구사항
복구코드를 재발급하되 기존 기록은 유지해야 한다
초기 문제
처음 구현은 단순했다.
- 새 코드 생성
- 쿠키만 교체
하지만 문제가 발생했다.
문제 상황
DB에는 복구코드 hash 기준으로 데이터가 저장되어 있었기 때문에:
- 코드만 변경 → 기존 기록 연결 끊김
- 결과적으로 데이터가 사라진 것처럼 보이는 문제 발생
해결 방향: 데이터 마이그레이션
복구코드 변경 시 기존 데이터를 함께 이동시키는 구조로 변경했다.
핵심 구현 (AuthService.resetCode)
async resetCode(oldCode: string): Promise<string> {
const newCode = this.generateCode()
await this.prismaService.score.updateMany({
where: { recoveringCode: hashCode(oldCode) },
data: { recoveringCode: hashCode(newCode) }
})
return newCode
}
구조 설명
- 기존 hash → 새로운 hash로 일괄 업데이트
- Prisma updateMany 활용
- HMAC-SHA256 기반 hash 구조 유지
API
PATCH /auth/reset
흐름
- 쿠키에서 oldCode 추출
- resetCode 호출
- DB updateMany 실행
- newCode 쿠키 저장
TDD 적용
RED → GREEN → REFACTOR 방식으로 진행
테스트 케이스
- 쿠키 없음 → BadRequestException
- 쿠키 있음 → 코드 재발급 + 데이터 유지 확인
결과
✔ 8 / 8 테스트 통과
3. 전체 흐름
클릭
→ PATCH /auth/reset
→ DB: oldHash → newHash updateMany
→ 쿠키 교체
→ UI 상태 업데이트
4. 결론
이번 작업에서 핵심은 단순 기능 구현이 아니라 구조적인 문제 해결이었다.
- Redis 제거로 인한 부팅 에러 해결
- NestJS 모듈 구조 이해
- RateLimitGuard 재설계
- 복구코드 데이터 무결성 유지
- TDD 기반 기능 구현
5. 실제 적용: Markdown 기반 SDD 문서
이번 복구코드 기능에서 SDD 방식이 적용되었다.
예를 들어 복구코드 기능은 다음처럼 정리되었다.
- SDD 적용 과정
docs/features/recovering_code.md
(초안 내용)
### 기능 추가 구현
복구코드 재발급 기능을 백엔드 및 프론트엔드 각 부분에 TDD로 검증해서 구현해줘(작업 순서: TDD -> 구현 -> 리펙토링)
## API
- PATCH /reset
# TDD(API)
- 존재하지 않는 복구코드를 재발급하는 경우
- 존재하는 복구코드를 재발급하는 경우
- 중복된 부분이 많으면 리펙토링 필수
## WEB
- 버튼 스타일 : 레트로 게임 느낌(선택적)
- hover / active 상태 존재
- 클릭시 loading되고 서버에서 처리가 되면 loading 풀리게끔
# TDD(WEB)
- 마우스 대면 hover 상태인가?
- 클릭하면 active 상태이고 loading 중인가?
하지만 실제 구현 과정에서 다음 문제가 발견되었다.
- 테스트 기준이 모호함
- 데이터 유지 조건이 누락됨
그래서 요구사항을 다음과 같이 수정했다.
(최종 요구사항)
### 기능 추가 구현
1. 복구코드 재발급 기능을 백엔드 및 프론트엔드 각 부분에 TDD로 검증해서 구현해줘(작업 순서: TDD -> 구현 -> 리펙토링)
2. TDD는 작성 후 각 단계에 실행해서 확인해야 해.(RED -> GREEN -> REFACTORING)
3. 복구코드 재발급 기능을 복구코드를 재발급하되 내 기록들을 유지해야 해
## API
- PATCH /reset
# TDD(API)
- 존재하지 않는 복구코드를 재발급하는 경우
- 존재하는 복구코드를 재발급하는 경우
- 중복된 부분이 많으면 리펙토링 필수
## WEB
- 버튼 스타일 : 레트로 게임 느낌(선택적)
- hover / active 상태 존재
- 클릭시 loading되고 서버에서 처리가 되면 loading 풀리게끔
# TDD(WEB)
- 마우스 대면 hover 상태인가?
- 클릭하면 active 상태이고 loading 중인가?
최종 정리
결국 이번 작업을 통해 느낀 핵심은 하나였다.
기능을 “끄는 것”과 시스템에서 “완전히 제거하는 것”은 다르다
그리고 기능 개발에서 더 중요한 것은
코드 자체가 아니라, 데이터 흐름과 구조를 이해하는 것이다.
🔗 직접 플레이해보기
이번 글에서 정리한 내용은 실제 서비스에 반영되어 있습니다.
아래 링크에서 확인할 수 있습니다.
https://tetris-retro-web.vercel.app/
tetris_retro_web
tetris-retro-web.vercel.app
'프로젝트 > 레트로 테트리스' 카테고리의 다른 글
| 테트리스 레트로 개발 회고 — 성능 개선부터 배포까지 (0) | 2026.06.10 |
|---|---|
| Claude Code로 테트리스 만들기 4부 - 모바일 터치 조작 구현 (0) | 2026.04.21 |
| Claude Code로 테트리스 만들기 3부 - 반응형 레이아웃 구현 (0) | 2026.04.20 |
| Claude Code로 테트리스 만들기 2부 - 줄 삭제와 점수 시스템 구현 (0) | 2026.04.19 |
| Claude Code로 테트리스 만들기 1부 - 충돌 감지 구현 (0) | 2026.04.19 |