티스토리 뷰
미션 기능 설계를 위한 ERD 및 정규화, 그리고 중복 요청 방지 전략
웹 혹은 앱 서비스를 만들다 보면 사용자가 어떤 미션을 수행하고 보상을 받는 시스템을 구현할 일이 종종 있습니다. 이 과정에서 단순히 데이터만 저장하는 구조로는 운영 중 다양한 문제(데이터 중복, 무결성 오류, UI 이상 현상 등)에 부딪히게 됩니다.
이번 포스트에서는 제공받은 피그마 디자인을 참고하여 ERD(Entity Relationship Diagram)를 설계하고, 이를 제1, 제2, 제3 정규형(NF)까지 정규화하여 더욱 견고한 구조로 개선한 사례를 소개합니다.
또한, 유저가 "미션 도전!" 버튼을 빠르게 연타할 경우 발생할 수 있는 중복 요청 문제를 방지하는 설계적 방법도 함께 설명드리겠습니다.
✏️ ERD 설계와 정규화
정규화(Normalization)는 데이터베이스 설계에서 중복 데이터를 제거하고, 데이터 무결성을 확보하기 위해 반드시 고려해야 하는 과정입니다. 이번 설계에서는 사용자(User), 음식(Food), 미션(Mission), 식당(Restaurant) 등의 테이블로 나누어 정규화를 진행했습니다.
✏️ 제1정규형 (1NF) – 원자값(Atomic Value) 보장
제1정규형은 모든 컬럼이 쪼갤 수 없는 원자값만 포함해야 한다는 원칙입니다.
즉, 하나의 셀에 여러 개의 값이 들어가선 안 됩니다.
위처럼 사용자 한 명이 좋아하는 음식들을 하나의 문자열로 저장하면, 나중에 특정 음식 기준으로 검색하거나 개별 삭제/추가가 어렵습니다.
favorite_foods 속성을 별도 테이블로 분리하여, 각각의 선호 음식을 하나의 row로 저장합니다.
음식별 통계, 검색, 추천 기능이 용이해짐
데이터 관리 편의성 향상
관계형 DB의 철학에 부합하는 설계
✏️ 제2정규형 (2NF) – 부분 함수 종속 제거
제2정규형은 복합키의 일부분에만 종속되는 컬럼을 제거하는 것을 말합니다.
이 구조에서는 food_name이 사실상 food_id에만 종속되므로, 여러 유저가 같은 음식을 선호할 경우 음식 이름이 반복 저장됩니다.
음식 정보를 Food 테이블로 분리하고, UserFood는 관계 정보만 담도록 설계합니다.
음식 이름의 중복 제거
하나의 음식 이름만 변경하면 모든 사용자에게 반영됨
저장 공간 절약 및 유지보수 용이
✏️ 제3정규형 (3NF) – 이행적 종속 제거
제3정규형은 기본키가 아닌 속성이 다른 비기본키 속성에 종속되지 않도록 설계하는 정규화입니다.
restaurant_address는 restaurant_name에 종속되어 있기 때문에, 같은 레스토랑을 여러 미션에서 사용할 경우 주소를 중복 입력하게 됩니다.
레스토랑 정보를 별도 테이블로 분리하여, 미션에서는 레스토랑 ID만 참조합니다.
데이터 중복 제거 및 무결성 확보
레스토랑 주소 변경 시 단일 테이블만 수정하면 됨
데이터 변경 이상(Anomaly) 방지
✏️ 정규화 요약
| 1NF | 원자값만 허용 | 다중 값 분리, 관리 용이 |
| 2NF | 부분 종속 제거 | 중복 제거, 무결성 향상 |
| 3NF | 이행 종속 제거 | 이상 현상 방지, 일관성 확보 |
2. 중복 요청 방지를 위한 설계 전략
문제 상황: “미션 도전!” 버튼을 빠르게 여러 번 누르면?
사용자가 "미션 도전!" 버튼을 여러 번 누르면 서버에 여러 요청이 비동기적으로 전송됩니다. 응답이 느려질 경우, 하나의 요청이 처리되기 전에 다른 요청이 동시에 처리되어 같은 미션에 여러 번 도전한 상태가 될 수 있습니다.
이 문제를 해결하기 위해 DB 설계 차원에서 중복 요청을 방지하고, 추가적으로 서버·클라이언트에서의 예외 처리도 고려해야 합니다.
✏️ 테이블 구조 개선: UserMission 테이블 도입
기존에는 Mission 테이블에 user_id, is_completed 등을 함께 넣어 관리했지만, 이를 분리하여 다음처럼 설계합니다.
각 유저가 어떤 미션에 도전했는지를 명시적으로 관리
UNIQUE(user_id, mission_id) 제약으로 같은 유저가 같은 미션에 중복 도전하는 것을 방지
create table UserMission (
id bigint auto_increment primary key,
user_id bigint not null,
mission_id bigint not null,
is_completed tinyint(1) default 0 not null,
created_at datetime(6) not null,
updated_at datetime(6) not null,
-- 중복 도전 방지를 위한 제약
constraint unique (user_id, mission_id),
-- 외래키 설정
foreign key (mission_id) references Mission(id) on delete cascade,
foreign key (user_id) references User(id) on delete cascade
);
✏️ 중복 요청을 막는 추가적인 방법들
| DB 제약 조건 | UNIQUE(user_id, mission_id) | 가장 확실하고 간단한 차단 방식 |
| 트랜잭션 & 락 | SELECT ... FOR UPDATE로 동시 접근 제어 | 동시성 문제 해결 |
| 멱등 키(Idempotency Key) 사용 | 같은 요청은 한 번만 처리되게 UUID 부여 | API 안정성 향상 |
| 프론트엔드 처리 | 버튼 클릭 시 UI 비활성화 or 로딩 표시 | UX 개선, 서버 부하 감소 |
✏️ 마무리: 좋은 설계는 데이터 품질을 결정한다
이번 포스트에서는 ERD를 설계하고, 이를 정규화(1NF ~ 3NF)를 통해 구조적으로 개선한 과정을 소개했습니다.
또한, 동시성 이슈에 대비한 중복 요청 방지 전략도 함께 살펴보았습니다.
정규화를 통해 데이터를 깔끔하게 정리하면, 검색/통계/수정이 쉬워질 뿐 아니라 추후 새로운 기능을 추가하거나 문제를 추적할 때도 큰 도움이 됩니다.
또한 중복 요청 방지 같은 고려는 프로덕션 품질의 완성도를 높이는 핵심 포인트가 됩니다.
'Back-End' 카테고리의 다른 글
| [UMC/시니어 미션#3] (1) | 2025.09.20 |
|---|---|
| [UMC/시니어 미션 #2] (3) | 2025.09.18 |
| [Spring/스프링] - 스프링 부트(Kotlin) Github Actions 자동 배포 (0) | 2025.08.29 |
| [Spring/스프링] - Spring Boot에서 Facade 패턴 도입기 (8) | 2025.08.22 |
| [Spring/스프링] - Spring Boot에서의 MVC 흐름 (4) | 2025.08.21 |
- Total
- Today
- Yesterday
- HTML5
- 스택
- 백준 풀이
- C++
- 이분 매칭
- 투 포인터
- 에라토스테네스의 체
- 자바
- 세그먼트 트리
- 자료구조
- 우선순위 큐
- DFS
- 스프링 부트 crud 게시판 구현
- 유클리드 호제법
- CSS
- 카운팅 정렬
- 유니온 파인드
- 알고리즘 공부
- C++ Stack
- html
- DP
- Do it!
- java
- 자바스크립트
- BFS
- js
- 반복문
- 백준
- c++ string
- 알고리즘
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 | 30 |
