Q1.
미션 자료로 제공된 피그마를 보고 ERD를 설계한 후 제 1,2,3 정규화를 통해 제 1,2,3 정규형을 만들고 각각 중복된 데이터가 어떻게 변화하였고 어떠한 이점이 있었는 지 작성하여 주세요.
A1.
우선 제1정규형, 제2정규형, 제3정규형이 무엇인지 떠올려보자.
1. 제1정규형
조건
- 모든 테이블은 기본 키를 가져야 한다.
- 각 컬럼은 원자 값(atomic value)만 가져야 한다. (즉, 하나의 셀에 여러 값이 들어가면 안 됨)
- 반복되는 그룹(예: 같은 속성을 여러 컬럼으로 나눠 저장)은 없어야 한다.
예시
아래는 자녀 이름을 child1, child2, child3와 같은 컬럼으로 나눠 저장했기에 제1정규형을 위배한다.

이는 아래와 같이 정규화를 시킬 수 있다.

2. 제2정규형
조건
- 제1정규화를 만족한다.
- 기본키가 아닌 모든 속성이 기본키에 완전 함수 종속되어야 한다. (기본키에 일부만으로 기본키 외의 다른 속성을 결정할 수 있으면 안된다.)
예시
아래에서 기본키는 (part, warehouse) 일 때, warehouse_address는 warehouse로만 결정할 수 있다.
따라서 기본키에 완전 함수 종속이 아니므로 제2정규형을 위배한다.

이는 아래와 같이 정규화할 수 있다.

3. 제3정규형
조건
- 제2정규화를 만족한다.
- 기본키에 속하지 않은 모든 속성이 이행적 함수 종속이 아니어야 한다. (기본키 이외의 속성이 그 외의 다른 속성을 결정할 수 없어야 한다.)
예시
아래에서 기본키는 emp_num이지만, dept_name은 dept_num에 의존한다. 즉, dept_num -> dept_name이 존재하므로 이행적 함수 종속이기에 제3정규형을 위반한다.

이는 아래와 같이 정규화할 수 있다.

미션
이제 미션을 기반으로 살펴보자.
아래는 피그마를 보고 내가 설계한 ERD이다.
정규화를 특별히 고려하려고 하진 않았지만, 내 머리 속에서 고려를 했었을테니 정규화가 꽤나 반영된 상태다.

우선 위의 이미지에서는 가게에 영업시간이 json으로 들어가 있다.
성능적인 것을 고려했을 때 이런 식으로 많이 한다고 들었는데, 엄연히 말하면 제1정규형에 위배된다.
한 컬럼에 여러 값이 들어간다고 볼 수 있을테니..!
그래서 가게 테이블에서 영업시간 테이블을 별도롤 분리해서 아래와 같이 만들 수 있다.


이 외에는 정규형을 만족한다고 생각하여 추가적으로 작성하지는 않겠다.
Q2.
피그마의 홈 부분에서 한 사람이 “미션 도전!” 버튼을 빠르게 여러 번 눌렀을 때 여러 가지 이유(비동기 로직 등)로 요청이 지연되어 완전히 처리하기 전 두 번 요청이 들어갈 수 있습니다. 이를 해결할 수 있는 방법에 대해 작성하여 주세요 (ERD 직접적으로 관련이 있기보다는 설계할 때 한번쯤 고민해보면 좋을 것 추가시켜 놓았습니다) (다양한 방법이 있으니 찾아봐 주세요)
A2.
해당 문제는 프론트엔드 레벨에서도 처리할 수 있고, 백엔드 레벨에서 처리할 수도 있다.
1. 프론트엔드 레벨에서 처리
(1) 버튼 비활성화
- 버튼을 클릭하면 그 직후 즉시 버튼을 비활성화 시킨 뒤에, 서버에서 응답을 받은 뒤 다시 활성화 시켜준다.
// 버튼 클릭 이벤트각 발생하면 호출되는 함수
const handleClick = async () => {
if (isLoading) return; // 이미 요청이 진행중이면 중복 클릭 무시
setIsLoading(true); // 버튼 비활성화 (isLoading 상태를 true로 변경) -> 버튼을 다시 눌러도 중복 요청 발생 X
await api.challengeMission(); // 서버로 미션 도전 API 요청. await를 이용하여 요청이 완료된 후 다음 코드 실행.
setIsLoading(false); // 버튼 재활성화 (isLoading 상태를 false로 변경)
};
- 장점: 구현이 간단하고 UX 직관적이다.
- 단점: 브라우저 JS에 의존하므로 서버에서 중복 요청 방지도 필요하다.
(2) 디바운스(Debounce) 처리
- 일정 시간 내에 발생한 클릭 이벤트를 하나로 합친다.
const handleClick = debounce(async () => {
await api.challengeMission();
}, 500);
- 장점: 빠르게 클릭을 방지할 수 있다.
- 단점: 서버에서 중복 요청 방지도 여전히 필요하다.
2. 서버 레벨에서 처리
(1) 중복 요청 검증
- 서버에서 사용자-미션 테이블에 userId와 missionId 모두 일치하는 컬럼이 있는지 확인하고 있다면 중복 요청을 무시하거나 에러를 반환한다.
@Transactional
public void challengeMission(Long userId, Long missionId) {
if (missionRepository.existsByUserIdAndMissionId(userId, missionId)) {
throw new MissionAlreadyChallengedException();
}
missionRepository.save(new Mission(userId, missionId));
}
- 장점: 클라이언트 무력화 상황에도 안전
- 단점: 요청이 들어올 때마다 DB를 조회해서 이미 존재하는지 확인해야 하므로 DB 부하가 증가한다.
(2) DB 레벨에서 UNIQUE 제약
- user_id + mission_id 컬럼에 unique index를 설정해준다.
- 중복 INSERT 시 DB에서 거부한다. (서버에서 에러 처리)
- 최종 안전망으로, race condition을 방지할 수 있다.
- race condition: 동시에 여러 프로세스/스레드가 같은 자원(데이터)에 접근하면서 실행 순서에 따라 결과가 달라지는 상황
ALTER TABLE user_mission
ADD CONSTRAINT uq_user_mission UNIQUE(user_id, mission_id);
- 비슷한 방법으로, 검색 성능 향상과 함께 중복 방지 기능을 제공하는 "unique index"도 사용할 수 있다.
CREATE UNIQUE INDEX idx_user_mission ON user_mission(user_id, mission_id);
참고 자료
1. https://www.ibm.com/kr-ko/think/topics/database-normalization
데이터베이스 정규화란 무엇인가요? | IBM
데이터베이스 정규화는 데이터 무결성을 개선하고 이상 현상을 방지하며 중복성을 줄이기 위해 데이터를 특정 테이블 구조로 구성하는 데이터베이스 설계 프로세스입니다.
www.ibm.com
'UMC' 카테고리의 다른 글
| [UMC] 미션 2. Servlet vs Spring MVC | AOP (0) | 2025.09.24 |
|---|