25/3/17
UMC 동아리 8기 스프링 부트 스터디 1주차 데이터베이스 설계 부분을 진행하며, 공부했던 내용과 미션을 정리하는 포스트입니다.
📚 개념 정리
Structured Query Language
관계형 데이터베이스를 사용하는 시스템에서 데이터를 관리할 때 사용하는 언어
입출력 속도보다 정확도를 중시할 때 선택하면 좋다.
관계가 잘 정의된 구조화된 데이터에 적합하다.
MySQL, PostgreSQL, Oracle Database
NoSQL
Not Only SQL 즉, SQL을 사용하지 않는 DBMS
key-value 구조, document 구조 등이 존재
MongoDB, Redis, neo4j, DynamoDB
💥 미션 및 해결 과정
> ❓ 주어진 기획 플로우, 디자인 프로토타입을 보고 직접 데이터베이스를 설계
기획 플로우와 디자인 프로토타입 파일은 유출이 불가능하므로, 기능을 간단하게 나열하겠다.
회원가입, 로그인 및 로그아웃
지역별로 가게가 존재, 가게를 방문하는 미션을 해결하여 포인트를 모으는 서비스
구체적인 파일을 보고 필요하다고 느낀 데이터들은 다음과 같다.
```
회원 : 이름, 성별, 생년월일, 주소, 선호하는 음식 종류, 이메일, 휴대전화, 보유 포인트, 탈퇴, 사장님 여부
지역 : 지역명
지역 별 : 몇 개의 미션을 달성하였는지
알림 : 가게명, 알림 종류
알림 설정 : 종류(새로운 이벤트, 리뷰 답변, 문의 내역)에 따라 수신 여부 결정
문의 내역 : 제목, 유형, 문의 내용, 사진, 답변 여부
+ 문의 답변
가게 : 가게명, 가게 종류(중식당 등), 어떤 지역에 해당하는지, 주소, 별점
리뷰 : 작성자, 별점, 리뷰 내용, 리뷰 사진
+ 리뷰 답글 (사장님 답글)
미션 : 미션 금액, 적립금액, 남은 날짜, 진행 여부(진행 중, 진행 완료)
```
정규화 진행 전의 ERD

제 1정규형
제 1정규형으로 정규화하기 위해서는 도메인을 원자값으로 변경해주어야 한다.
예시로 정규화 진행 전의 ERD를 보자. 사용자가 어떤 음식 종류들을 선호하는지 저장하는 유저 내의 food_type 컬럼 때문에 도메인의 원자값이 지켜지지 않는다. 사용자는 여러 음식 종류를 선호할 수 있기 때문이다.

정규화를 진행시킨 제 1정규형의 테이블은 위와 같다.
제 1정규형으로 변화시키며 PK의 중복이 없어졌다. 기본 키로 데이터베이스를 조회할 때의 성능이 향상됐을 것이다.
제 2정규형
제 2 정규형이 되기 위해서는 부분 함수 종속을 제거 해야 한다. 즉, 기본 키가 아닌 일부 컬럼에 종속된 데이터들을 다른 테이블로 분리해야 한다. 제 1정규형 이미지를 보면 store 테이블에 region_name과 같이, 지역에 대한 컬럼들이 들어가 있다. region_name은 지역에 해당하는 데이터이므로 분리가 필요하다.
부분 함수 종속들을 제거하고 나면 다이어그램은 다음과 같이 변한다.

제 2정규형으로의 변화를 통해 데이터 수정이 용이해졌다. 만약 제 1정규형의 테이블에서 region_name을 변경시키고 싶다면 store 테이블 내의 모든 데이터를 다 수정해야 했을테니...
제 3정규형
제 3정규형이 되기 위해서는 이행적 종속을 제거 해야 한다. 기본 키가 아닌 키들끼리 서로 종속되어 있는 상황을 없애야 한다. 예를 들어 제 2정규형에서 review 테이블에도 region_id가 있는데, store_id로 region_id를 알 수 있으므로 이를 제거하여 이행적 종속을 제거할 수 있다.

불필요한 데이터를 제거하여 저장 공간을 효율적으로 사용할 수 있게 된 것 같다. 제 1차에 비해 훨씬 이해하기 쉽고, 간단해보인다.
---
> ❓ 홈 화면에서 한 사람이 “미션 도전!” 버튼을 빠르게 여러 번 눌렀을 때 여러 가지 이유(비동기 로직 등)로 요청이 지연되어 완전히 처리하기 전 두 번 요청이 들어갈 수 있습니다. 이를 해결할 수 있는 방법이 있을까요?
이러한 문제를 멱등성 문제라고 하는 것 같다. 멱등성은 연산을 여러 번 적용해도, 같은 결과를 반환하는 성질을 말하는 것이다. 덮을 멱에 무리 등 자를 써서 연산 무리(여러 번 적용했으니)를 하나로 덮어버린다? 이런 의미인 것 같다..(아닐 수도 있음)
제일 먼저 생각 난 방법은 1⃣ 좋아요처럼 취소해도 되는 기능이 아닌 미션 도전과 같은 기능이라면 처리 하는 동안 DB에 락을 거는 방식이다. 하지만... DB에 락을 거는 방법은 만약 서버 인스턴스가 다중화되어 있는 상황 이라면 적용할 수 없다는 문제가 있다.
2⃣ 데이터베이스 제약 조건을 사용하여 유니크 제약 조건을 설정하는 방법이 있다. 이미 등록된 데이터가 있으면 저장되지 않을테니, 안정적인 것 같다.
```
ALTER TABLE user_mission
ADD CONSTRAINT unq_mission
UNIQUE (mission_id, user_id, store_id, status);
```
3⃣ 혹은 Redis를 사용해 고유 키를 저장하는 방법도 있다. UNIQUE 제약 조건을 거는 것과 방식은 비슷하다. 요청을 받았을 때, Redis에 미리 지정해둔 고유 키(예시 : 유저 아이디+미션 아이디+스토어 아이디+미션 생성 일자)로 중복 요청인지를 확인해 처리할 수 있다.
4⃣ 먼저 들어온 요청을 받아들여 적용하고, 또 똑같은 요청이 들어오면 전 요청을 취소하고 현 요청을 처리하는 보상 트랜잭션 방법도 있다. 데이터 일관성을 강력하게 유지할 수 있지만 요청을 한 번 더 처리해야 하므로 위 두 방법에 비해 추가적인 자원 소모가 있을 수 있다.
💭 회고
기획을 쭉 보고 생각나는 대로 ERD를 작성하고, 필요하면 정규화를 추가로 진행하는 것은 자주 해봤지만 1차, 2차, 3차 정규형을 나눠서 ERD를 작성하는 것은 처음 해보는 작업이었다. 어렵지 않을 거라 생각했는데, 제 1정규형을 구현하기 위해서 억지로 데이터들을 분리하지 않는 것도 힘든 일이었다..😅
그동안은 정규화의 장점을 개념으로만 익히고 외웠는데, 직접 일일히 정규화를 해보니 훨씬 장점이 와닿았다. 그리고 정규화에 대해서도 조금 더 확실하게 익힌 것 같아서 의미 있는 미션 수행이었던 것 같다 ㅎㅎ
프론트엔드에서 버튼이 클릭된 후, 다음 클릭부터는 막아두는 처리를 해두면 멱등성 문제가 해결될 거라 생각해서 백엔드 단에서의 멱등성 문제 해결에 대해 고민해본 적이 없었다. 생각해본 적이 없던 문제라 흥미롭게 느껴져 해결할 수 있는 방법을 더 열심히 찾아보게 됐다. 프론트엔드에서도 처리하고, 백엔드에서도 따로 처리하면 예외를 더욱 꼼꼼히 처리할 수 있기 때문에 이러한 처리가 꼭 필요할 것 같다.