[ Introduction > Blog ]
[블로그 소개] DB - ERD
[블로그 소개] DB - ERD
이번 글에서는 블로그에 사용한 DB와 그 ERD를 그리는 과정을 소개해드리려고 합니다.
DB는 MySQL을 사용했습니다. 사용한 이유는 혼자서 만들기엔 익숙한 DB를 사용하는 것이 낫다고 판단했습니다.
ERD(DB설계)를 그리기 전에 이상적인 소프트웨어 공학 개발 순서에서는 만들 제품에 대한 정의와 검증을 합니다. 그리고 아래 나열한 과정들이 ERD를 그리기 전에 수행해야할 과정들입니다.
레퍼런스 조사, 시장 조사, 아이디어 구체화 및 경쟁력 분석
요구사항 정의 및 기능 목록 작성
와이어프레임/화면 설계 - 순서는 언제든 바뀜.(시스템 논리 구조부터 잡느냐 vs 화면 먼저 그린 후 역으로 설계하냐)
use Case 정의(누가 어떤 기능을 사용하는가, 어떤 데이터를 CRUD 하는지 확인)
user Flow 정의 (기능이 언제 데이터를 생성/수정/조회 하는가, 데이터 CRUD 타이밍 확인)
class Diagram 정의 (객체들의 관계 조회, ERD 구조의 뼈대)
유즈케이스, 사용자 흐름, 클래스 다이어그램을 합쳐서 검증.
데이터 모델링 (ERD)
하지만 실제 현업에서 개발을 할 때는 위 과정과 순서를 그대로 따르지 않습니다. 목표를 달성하기 위한 시간과 인력이 한정되어 있고 소프트웨어 개발 방법론(폭포수, 애자일..)에 따라서도 달라지기 때문입니다.
저는 혼자서 블로그를 다 만들어야하기 때문에 다음과 같은 간단한 과정으로 ERD 설계를 했습니다.
블로그 레퍼런스 조사
기능 정의
DB 설계 (ERD)
구현 중 추가하고 싶은 기능이 있으면 기능 정의 추가 => ERD 변경
혼자 개발하기 때문에 저에게 모든 결정권이 있고, 모든 변경 내역과 플로우를 알고 있습니다. 그래서 구현 중에 기능을 추가하거나 수정하여도 저가 모두 인지하며, 인력이 부족하기 때문에 위와 같이 간단한 과정으로 ERD를 설계한 것입니다.
아래는 저가 블로그를 만들때 정의한 기능 정의서 입니다. 이 기능정의서를 바탕으로 DB를 설계했습니다.
📄 기능 정의서 (축약)
1. 사용자(User)
기능
Google OAuth 로그인
JWT 방식 세션 검증
유저의 Id값은 절대 사용자에게 노출 하지 않음.(보안)
속성
개인 정보 보호를 위한 최소 정보 저장 (google_sub)
확장성을 위한 oauth provider 정보
역할 (RBAC)
2. 게시글(Article)
기능
게시글 작성
게시글 수정/삭제
게시글 조회
게시글 검색
속성
제목 (SEO 영향)
내용 (SEO 영향)
작성자
카테고리 (SEO 영향)
라벨 (React, TailwindCss v4.0과 같은 티켓, SEO 영향)
이력 관리
게시글 변경 시 article_history 테이블에 이전 내용 백업
3. 댓글(Comment)
기능
댓글 작성
댓글 수정/삭제
댓글 조회
속성
내용
작성자
게시글 id
댓글 id(대댓글)
이력 관리
댓글 변경 시 comment_history 테이블에 이전 내용 백업
4. 카테고리(Category)
기능
게시글 분류
프론트엔드 라우팅에 사용
카테고리 CRUD
카테고리 출력 순서 변경
속성
카테고리 명
부모 카테고리
순서
5. 라벨(Label)
기능
게시글에 여러 라벨 부여 가능
속성
라벨 명
6. 좋아요(Like)
기능
사용자별 좋아요 추가/취소 가능
게시글에 대한 좋아요 수 카운팅
속성
게시글
사용자
1. ERD 소개
ERD는 바로 DDL문을 생성하여 사용하기 위해 MySQL 워크벤치를 사용하여 그렸습니다.
기능 확장을 위해 아직 쓰지 않는 Column들도 있고, 성능을 위해 비정규화한 테이블도 있고 is_edit과 is_deleted 를 1개의 status로 합치고 싶은 아쉬운 부분도 있는 테이블입니다.

ERD를 만들때 특별히 신경쓴 부분들을 위주로 설명 드리겠습니다.
1.1 master 테이블과 history 테이블 분리
속성 값이 자주 안바뀌고 핵심이 되는 속성들을 포함하면 mst(master)테이블로 정의하였고, 변경 이력의 기록이 필요하다고 판단한 테이블은 history 테이블을 별도로 추가했습니다. article_mst는 실수로 내용을 다 지우거나 editor 기능 업데이트 시 문제가 생길 수 있기 때문에 history 테이블이 필수라고 생각했습니다. 그리고 댓글 기능은 악의적인 댓글을 쓰고 수정하는 경우가 발생하면 그 변경 이력이 필수라고 생각하여 history 테이블을 추가했습니다.
1.2 검색 엔진과 MySQL
블로그 검색 기능은 title과 content를 기준으로 작동하도록 하고싶었습니다.
MYSQL 자체 기능만으로 검색 기능을 구현할 방법은 다음과 같습니다.
Like 연산
REGEXP 정규식 연산
MATCH 연산
FULLTEXT Index
이 중 당연히 인덱싱하는 FULLTEXT index가 가장 빠르기 때문에 이 방법을 사용했습니다 그런데 이 방법은 띄어 쓰기를 기준으로 단어 단위로 인덱싱하여 한글 검색에서 정확도가 낮았습니다. 그래서 글자 수 단위로 인덱싱하는 N-gram parser를 이용하여 FULLTEXT index을 적용했여 더 빠르고 정확한 검색 기능이 구현됐습니다.

언젠가 DB가 많아지면 mySQL만 쓰기는 성능이 안나올 것입니다. 그 때는 Elasticsearch db를 연동하거나 다른 DB를 사용해야할 수 있지만, 그 정도로 많은 글을 쓸려면 오랜 시간이 걸릴 것 같습니다.
1.3 카테고리, 라벨 테이블의 자연키
category_mst 테이블과 label_mst 테이블은 pk로 자연키를 가집니다.
처음 화면 구상을 할 때, 카테고리별 게시글 목록은 URL을 카테고리 이름을 쓰도록 정의했습니다. 그래서 카테고리는 고유하기 때문에 그 이름 자체를 id로 써서 카테고리 이름으로 조회시 성능이 좋도록 설계했습니다.
라벨 테이블은 pk를 라벨명(name)으로 설정하여 특정 라벨을 찾을때 별도의 인조키(id)값을 찾을 필요가 없게 만들었습니다. 이렇게 만든 이유는 article 저장시 같은 label이 있으면 새롭게 id값을 할당한 같은 이름의 label이 생기면 기능 확장과 하여 부여하면 안되기 때문입니다. 추후 라벨로 게시글을 찾는 기능을 만든다면 라벨로 게시글 찾는 기능을 만들 때, 같은 이름의 다른 id값을 가진 라벨이 많아 성능상 문제가 생기고 데이터 중복 저장의 문제도 생기기 때문입니다.
1.4 연결 테이블(article_label_rel, article_user_likes)의 식별관계
기능 추가가 많을 것으로 생각하여 유연하게 테이블을 만들고 싶어 비식별 관계로 대부분 구성했습니다. 하지만 연결 테이블은 식별 관계로 구성했습니다.
article 수정할 때 label도 수정할 수 있습니다. 그래서 article 수정시 label을 삭제할 때 삭제되는 라벨을 사용하는 article이 없다면 그 라벨도 삭제되도록 기획했습니다. 이렇게 설계한 이유는 2가지가 있습니다.
쓸모없는 데이터가 많아지는 것을 원치 않는다.
label 검색 시 자동완성 기능이 있을 때, 사용하지 않는 label을 필터하기 위해 필드 추가와 별도의 인덱싱은 원하지 않는다.
그래서 식별관계로 article_label_rel을 구현한다면 데이터 무결성을 지키며 쓸모 없는 데이터가 많아지는 것을 더 확실히 차단할 수 있고 그에 따라 조회 성능도 더 좋아질 것이라 판단했습니다.
article_user_likes(좋아요 기능) 연결테이블을 식별관계로 한 이유는 가장 강제 삭제되면 안되는 중요한 테이블 두 개를 연결하기 때문입니다. 비록 article이나 user 모두 soft delete를 하고 있지만 단순히 중요하기 때문에 식별 관계로 만들었습니다. 하지만 지금 와서 보니 설계 미스라고도 생각이 듭니다.
1.5 인덱싱(Indexing)
블로그 데이터베이스는 10개가 넘는 인덱스가 걸려있습니다. 특히 article_mst는 다양한 기준으로 조회를 하기 때문에 가장 많은 인덱스를 걸었습니다. category 기준의 인덱스, view count 기준 인덱스, create_at기준 index, 검색을 위한 제목과 내용의 FULLTEXT index 등이 걸려있습니다. 지금은 article 개수가 적어서 오히려 index를 사용하는 것이 조회 성능이 안나올 수 있습니다. 하지만 추후 많은 글을 쓰게 되면 저가 생성한 index들이 빛날 것이라 생각합니다.