[springboot / 게시판 API] DB 설계 v0.5 - ERD 작성
Goal
- 요구사항에 부합하는 DB설계를 해보자
ERD (Entity-Relationship Diagram) 란?
요구사항을 분석하여 그림으로 그려내어 그 관계를 도출한 다이어그램을 말한다. 즉, ERD는 엔티티(entity) 간의 관계(relationship)를 도표(diagram)로 표시한 그림이다.
요구사항 참고
[springboot / 게시판 API] 개발환경 및 요구사항 정의
개발 환경 Intellij , springboot, java11, mybatis, mariadb(mysql) 특이사항 Lombok 사용 x 클래스는 VO or DTO로 Map 사용 x 게시판 API 개발 기능 요구사항 회원 가입 기능 회원 로그인 기능 회원이 접근 가능한 게시
dev-rosiepoise.tistory.com
사용툴
erd cloud
1차 ERD
어떤 관점으로 설계를 했는지?
요구사항 중에서 엔티티를 먼저 도출하려 했다. 회원/비회원 => 회원Entity, 게시글 crud=>게시판,QNA entity, 댓글curd => 댓글 entity. 3지를 러프하게 뽑고 기능별 컬럼 도출을 시작하였다.
회원별 권한이나 게시판 카테고리를 따로 관리하기 위해서 별도의 entity를 도출하였고 관계를 정의하였다.
고민한 점
- 내가 실무에서 사용한건 PK를 항상 UUID - varchar로 두었다. 그런데 사용해보진 않았지만 db를 공부하다보니, Long(int/bigint)로 할 지 고민이 되었음.
- 위 테이블에선 권한이나 카테고리를 따로 관리하는 테이블을 뺐는데, 보통 공통그룹tb-공통상세tb에 따로 두고 코드 관리를 하였기 때문에 따로 관리해야할 지 고민이 되었음.
- 회원 PK를 로그인ID 또는 이메일로 할지 고민이 되었지만, 일단은 PK에 통일성을 두고자 회원ID를 PK로 둠.
- 최초 요구사항은 1.회원 접근 가능 2.비회원 접근 가능 3.회원+비회원 접근 가능이었다.
나는 게시판을 세개를 만들기엔 중복 컬럼이 많다고 판단을 해서 게시판tb에 권한을 주려고 했지만, 또 비회원은 아무래도 입력받는 컬럼이나 validation도 상이 하다고 판단되어서 전체전용 QNA 테이블을 하나 더 만들었다(회원+비회원 모두 접근가능). 비회원용 게시판은 따로 필요없지 않을까? 어짜피 전체전용QNA을 사용할 수 있는데,,? 그래서 게시판 개념 tb가 2개를 생성함 - 그래도 댓글은 테이블 하나에 관리하고 회원전용과 전체전용 게시판에서 쓰도록 할까? 했지만 애초에 비회원은 컬럼명이 상이하다고 판단해서 테이블을 따로 둔것처럼,, 뭔가 일단 찝찝하지만 댓글도 각각 두개씩 만들었음. 그리고 댓글은 회원만 쓸 수 있도록 자진 판단하여 회원테이블과 엮음
- 대용량 시스템에서 그 수가 많아질 경우를 대비하여 댓글수도 게시판 테이블에서 컬럼으로 업데이트 치면서 관리를 했었어야 할지 고민함. 하지만 이는 동시성 이슈가 있을 수 있을 것 같아서 하지 않음. 게시글 id로 댓글 tb 조인해서 가져오는게 나을것 같다고 생각
피드백
- 일단 의도한건 1.회원 접근가능, 2.비회원 접근 가능, 3.회원+비회원 접근 가능의 기능이었으나, 추후 고객 요구사항에 따라 유형이 다른 게시판이 생긴다면 ( ex. 파일 추가가 가능한 게시판 등) 위와 같은 설계를 했을 때 성격이 다른 게시판 테이블을 또 만들 것인가?
- 일단 추후 요구사항을 늘어날 것을 더 분석하지 못했고, 회원+비회원의 개념으로만 접근하고 테이블 설계를 함. 그리고 그 권한의 개념으로 접근하더라도 테이블을 회원용, 비회원용으로 두는 것은 적절치 않음!
- 위와 같은 디비 구조라면 유형별로 테이블이 늘어나야만 함 이는, 게시판에 대한 권한을 세팅하는 테이블과 실제로 게시글을 작성하는 테이블 그리고 그에 대한 댓글 구조로 가져가야함.
- 그에 따른 요구사항 추가할 때마다 댓글도 테이블의 수 만큼 늘릴 것인가?
- 비회원용 게시판 및 댓글은 입력값이 일부 상이할 수 있는 컬럼들은 nullable로 두는 것이 적절함
- 따라서 댓글도 게시판 만큼 그 수를 늘리지 않아도 됨
- 댓글은 회원만 쓸 수 있는 건에 대한건 요구사항 분석을 정확히 하지 않았고, 비회원도 작성할 수 있다면 이도 nullable로 두는 것이 적절.
- 댓글수 관리를 필드를 관리한다고 했을 때, 어떤 동시성 이슈를 고려했는가?
- 트랜잭션에 관한 동시성 이슈가 있을 것 같다고 판단했다. 댓글이 추가되거나 삭제시마다 컬럼으로 관리하는 것은 동시 사용자가 많아졌을 때, db에서는 한 트랜잭션이 시작되고 끝날 때까지 lock이 걸리는데 다른 트랜잭션은 접근할 수 없고 기다려야하기 때문에 두지 않았다고 말씀드렸고, 그럼 이를 극복하기 위해선 어떻게 할 것인가?에 대한 질문의 답으로는 해당 댓글tb에 인덱스를 걸어서 속도 문제를 개선할 수 있을 것 같다고 피드백 주심.
- auto increment vs uuid
- mysql -auto increment
- auto increment의 재시작 이슈: auto increment값은 유일하지 않다. 즉, 글을 쓰고 지우는 경우에는 그 값을 잘 유지를 하지만 디비를 재시작하면 기존에 어떤 식으로 됐는지를 관리하는게 아니라 현재 테이블의 max값을 가지고 처리를 해서 문제가 생김.
- 또한 분산환경에서 단일 서버가 아닌 다중 서버이고 액티브-액티브 구성(다 접속 가능하게 받는 방법)일 경우 재부팅이 될 경우 그 메모리에 있는 increment값이 바뀔 수 도 있음. 그러다 보니 다중서버에서 다른 서버에서 예전 값을 가지고 있을 경우 이슈가 발생
- 그렇기 때문에 auto increment는 offset을 조종할 수 있고, 이를 이용하여 회피할 수 있음. 그렇지만 재시작 이슈는 해결 x
- oracle - sequence
- 내가 요청했을 했을 때 무조건 그 값은 한 번만 사용이 됨
- 오브젝트로 관리됨
- 이 부분이 솔루션화가 잘 되어 있음. auto increment와 같은 문제 x
- uuid (unique universial id)
- 지구상에서 이와 겹치는 값은 없다 고유한 값을 의미
- 분산환경에서는 안전하고, 데이터에 대한 정보 노출을 하지 않아 보안상 안전
- auto increment pk보다 더 많은 저장소 필요로함 (128bit)
- fk로 uuid를 사용하면 더 많은 저장 공간을 사용하게 됨
- 테이블과 인덱스의 크기가 켜져 db의 디스크와 메모리를 많이 사용함
- mysql -auto increment
- 로그인 id or 이메일을 pk
- 성능 저하
- 서비스 정책상 아이디를 바꿀 수 있는 경우가 있기 때문에 추천하지 않는다
- 공통 코드 관리 vs 관련 테이블로 관리 vs enum
- 공통 코드 - 테이블에서 코드를 직접 보지 않으면, 어떤 것을 의미하는지 알 수없음 또는 중첩하여 사용할 가능성 -> 어플리케이션 코드를 이해하는데 내 어플리케이션 코드 바깥의 환경에 제한을 받으면 좋은 설계는 아님
- 권한 같은 것을 enum으로 해도 되지 않을까? -> 하지만, 테이블로 관리하면 별도의 처리를 하지 않아도 되지만 enum으로 관리하면 코드를 재배포 해야함
2차 ERD
피드백 반영
- 게시판과 댓글 테이블을 하나씩 두고, 회원과 비회원 접근권한에 따른 입력값이 다를 컬럼들은 Nullable로 두었다
- 권한 별로 게시판 세팅을 위해 권한, 권한메뉴, 메뉴로 다시 테이블을 나누었다.
- 게시글의 물리적삭제를 하지 않기 위해 삭제여부 컬럼을 추가하였다.
피드백
- 위의 구조에서는 메뉴와 게시판과 연관 관계가 없다. 이를 의도하고 설계한건지?
- 같은 테이블을 사용해서 조건에 따라 필요한 컬럼만 셀렉해서 alias로 두고 게시판으로 동적으로 만들 의도(여기서 내가 잘못이해함)였다고 답변. 하지만 mybatis는 가능하지만 jpa는 ntt랑 클래스가 매핑이 되야하기 때문에 이방식은 x
- 서로 다른 분류의 게시판글은 어떻게 보여줄려고 한건지?
- 위의 설계대로 라면 서로 다른 분류의 게시글은 보여줄 수 없고, 그에 따른 글 넘버링도 어려움
- 또한, 모든 글이 보드 테이블에 기록되어 보드 테이블의 크기가 지고 보드에 종속됨
- 만약 메뉴ID가 게시판의 FK라면?
- 만약 메뉴를 지우는 행위를 할 때, 게시물이 지워질 수 있는 가능성. 메뉴에 종속된 설계 x
- 그리고 DML을 수행할 때, DDL(rollback x)이 일어나는 것은 좋은 설계가 아님
- 만약 메뉴를 지우는 행위를 할 때, 게시물이 지워질 수 있는 가능성. 메뉴에 종속된 설계 x
- 권한메뉴를 저렇게 설계한 이유는 무엇인지?
- 권한을 표기하기 위해서는 테이블 하나로는 불가
- 현재 시나리오에서는 권한에 대한 큰 중심을 두지 않았기 때문에, 이를 의도 x
- 레벨만 두고도 충분히 해결이 가능 - 레벨체크만 하면 되니까..
총평 : 1차 erd에서는 메뉴가 없었고, 권한 이행을 자체적으로 생각하면 안됬음. 요구사항 분석이 미흡하였고, 솔루션을 만들고 시스템을 만들기 위해서는 '내 생각'에 의해서 하면 안되고, 누가봐도 이해할 수 있도록 설계해야함. 이 설계의 문제점은 나만 이해할 수 있다는 점.
문서만 보고 직관적이고도 의도가 도출이 가능한 설계를 해야한다. 유지보수를 위해서.
흑흑,,,,,,,ㅠㅠ 정말 듣고보니 나만 이해할 수 있는 설계였다고 한다. 다시 수정 !!!!!
3차 ERD
version 1 - 요구사항의 회원, 비회원만 충족하기 위해 권한 tb 삭제하고 회원 tb, 게시판 세팅 tb에서 레벨로 관리
version 2 - 권한 tb을 두고 레벨관리, 게시판 세팅 tb과는 fk 관계
피드백 반영
- 메뉴를 없애고 그리고 요구사항에 충실하기 위해 카테고리는 삭제했고, 게시판 세팅, 게시글, 댓글로 다시 변경하였다.
- ver1, ver2를 나눈 이유는 현재 요구사항에는 권한과 게시판세팅에 레벨만 두고 충분히 접근 권한을 핸들링할 수 있다고 판단해서 ver1로 두었고, 시스템 확장성을 고려했을 때는 접근 권한을 관리하는게 맞지 않는가?에 대한 생각으로 ver2를 다시 나누었다.
피드백
- ver1 보다는 ver2가 낫다.
- 무언가를 설계할 때 확장성을 고려하는 것이 좋다.
- 예를 들어, ver1에서 회원 등급이 그린, 핑크, 블랙, 골드 가 있을 때 각 레벨을 90, 80, 70, 60 이라고 한다고 가정했을 때, 블랙 등급을 삭제or 수정한다면 어떻게 되는가? -> 현재 ver1 erd에서는 회원과 게시판 세팅 두군데를 삭제 or 해야하는데 그 과정에서 회원이랑 게시판세팅이 다르게 처리될 가능성이 존재한다 (어플리케이션이 아닌 dml로 처리할 경우)-> 게시판 세팅 쪽에는 레벨을 15로 수정, 게시글에는 레벨을 25로 수정하면 서로 상이하게 존재하게 됨 -> 또한 회원과 게시판에 그 수가 많을 경우 두군데를 업데이트해야 하므로 레코드 실행시간이 오래 걸림 (2n) -> 이는 좋은 설계가 아님
- ver2는 일단 비식별로 해야하며 (식별로 잘못 세팅함), 1:1이 아닌 1:many가 되어야 함. 또한 위와 같이 삭제의 과정을 거친다고 하더라도, 애매하지만 처리하기에는 ver1보다는 낫다
- 또한 게시판 세팅과 게시글도 1:1 이 아닌, 1:many가 되어야함
- char vs varchar
- char - 고정된 크기의 컬럼
- varchar - 가변으로 크기를 예측할 수 없을 때 사용권장
- mysql - text
- oracle - clob
- 여부 / tinyint vs char/varchar
- tinyint - 가변 길이가 아니기 때문에 빠르게 읽어올 수 있으나 상태의 의미를 숫자로 표현하면 데이터만 보고 명확하게 알 수 없음
- char/varchar - 문자로 할 경우 데이터만 보고 유추 가능, 문자 길이 별로 후처리가 들어가고 읽는데 시간 소요
4차 ERD
요구사항과 api design을 하면서, 고민되는 부분이 몇가지 있었다. 현재 기준으로 가져갔을 때, 해당 게시물에 댓글 수를 따로 조회해서 가져와야한다. 사실 그렇게 하는 건 문제가 없긴 했는데, api를 디자인하다보니 게시글 상세 조회하는 api를 작성하고 댓글수를 또 다시 조회하면 오퍼레이션이 늘어나는 부분에서 다시 댓글 수를 관리해야하는 고민이 생겼다! 그래서 다시 피드백을 받기로 했다! 끝인줄 알았지만 끝은 없는 것
피드백
- 댓글 수 select vs 필드 udpate
- 댓글 수를 select하는 방식 - 댓글의 수정 또는 삭제가 발생했을 때 db의 최소 단위인 트랜잭션 아이솔레이션 때문에 격리가 된다. insert시에는 크게 문제가 없지만 update,delete에서 반드시 lock이 걸린다. => 인덱스를 잘 걸었다면, 크게 문제가 없지만 인덱스를 잘못 걸었다면 락이 걸림
- 댓글 수 필드를 관리해서 update하는 방식 - 댓글을 등록하거나 삭제시 컬럼 개수를 업데이트하고, 그걸 상세 조회시 가져옴
- 나 : 그런데, 저번에 update하는 방식 또한 lock이 걸리니까 그 방법도 권장하지 않지 않나요?
- 멘토님 : 맞다. 하지만 글 수가 많지 않으면 이 방법을 추천하고 (정규화가 좋을 때도 있지만 역정규화가 좋을 때도 있음). 또한, 대용량 테이블일 때도 트래잭션을 이용해서 빠르게 lock을 해제할 수 있는 방법이 있다. -> 이는, 트랜잭션의 전파옵션의 required_new를 이용하면 된다(부모 트랜잭션의 영향을 안 받고 별개로, 자식이 트랜잭션이 부모의 트랜잭션에 포함되지 않는다) -> 락이 걸리는 것의 문제점 : 트랜잭션 최소 업무단위가 게시판 작성으로 한정되어 있다 (그러다보니 댓글을 쓸때 카운트도 올리는 그런 상황) -> 댓글 수 업데이트를 required_new를 주어 lock걸리는 시간을 최소화하는 것이 셀렉트로 조회하는 것보다는 나을 수 있음
- ( lock이 걸리니까 무턱대고 업데이트는 안되라고 하기보다, 알고 있는 내에서 어떻게 해결할까를 판단하는게 개발자로서 필요한 항목)
- 나 : 조인을 해서 가져오는 방법은 추천하지 않나요?
- 멘토님 : rdms에서 조인을 하면 조인에 관련된 버퍼를 사용한다. 버퍼는 유한한 자원이 아니다. 연속으로 하는 것은 성능에서 이득을 보지만, 그렇지 않으면 성능에 이득이 없다 -> 특히, 아우터 조인같은 경우 카디널리티 곱이 발생할 수 있다
- ex) 10000개의 게시글에 댓글이 100가 있을 경우 수행시간은 10000*100이 됨-> 카디널리티 곱은 피하는 것이 좋음
5차 ERD (최종)
피드백 반영
실제 요구사항 말고 api를 작성하다 보니, 기본적인 crud가 필요하기 때문에 작성일자와 수정일자 필드를 추가하였으며, 게시글에는 댓글수를 관리하는 컬럼을 따로 만들었다.
참고하면 좋을 포스팅
https://dev-rosiepoise.tistory.com/106
[DB] DML, DCL, DDL, TCL
Goal SQL의 종류에 대해 알아본다 DML, DCL, DDL, TCL의 정의와 그 차이에 대해서 알아본다 SQL의 종류 DDL (Data Definition Language) : 데이터 정의 언어 DML (Data Manipulation Language) : 데이터 조작 언어 DCL (Date Contr
dev-rosiepoise.tistory.com