📌 ERD 설계 가이드 (+ 직접 겪은 ERD 설계 경험)
728x90

프로젝트를 진행하면서 꼭 거쳐야 하는 과정이 있다. 매우 중요한 부분이고 팀원들이 모두 참여해야 한다.

바로 "ERD 설계"이다.

처음에 ERD를 설계할 때는 막막한 감정이 가장 먼저 떠올랐던 것 같다. 하지만 몇 번의 과정을 거치면서 어떤 요소를 고려하면서 ERD를 구성해야 하는지에 대해서 생각을 해보게 되었다.

 

ERD를 구성하면서 겪었던 어려움들 그리고 추가적으로 알게된 내용에 대해서 공유하고자 한다.

누군가 ERD를 구성하는 데에 막막함을 느낀다면 도움을 받았으면 좋겠다.

 

ERD는 데이터 모델링을 시각적으로 표현한 다이어그램이다. 데이터베이스를 설계할 때 해당 다이어그램을 기준으로 하게 되어 팀원 모두가 인지하고 있어야 하고 같이 설계해야 효율적으로 작업을 할 수 있다.

ERD의 중요 구성 요소로는 "엔티티" (-> 객체), "속성" (-> 컬럼), "관계" (-> 연결선)가 있다. 해당 요소들을 통해서 데이터가 어떻게 조직되고 연결되는지 표현할 수 있다.

ERD를 통해서 데이터베이스 구조를 시각적으로 표현함으로 모두의 이해도를 높이고, 데이터의 정합성을 보장하고 정규화 과정을 통해서 중복을 최소화할 수 있다. 또한, 시스템을 확장하거나 변경할 때 ERD를 기준으로 진행하면 되어서 유지보수에 큰 도움이 된다.

프로젝트 설계 과정에서 정말 중요한 과정이라고 볼 수 있다.

 

그러면 ERD를 설계할 때 어떤 식으로 해야하는가에 대해 생각해 보면 크게 3가지로 단계를 나눌 수 있다.

  • 개념적 모델링
  • 논리적 모델링
  • 물리적 모델링

 

일단 개념적 모델링이 우선되어야 한다.

우리가 구상한 프로젝트에 대해서 비즈니스 도메인을 분석하고 주요 데이터 항목에 대해서 독립 객체(엔티티)를 도출해야 한다.

예로, 온라인 쇼핑몰을 구상하고 있다면 엔티티는 사용자(User), 상품(Product), 주문(Order)으로 크게 나눌 수 있다.

또 엔티티들에 대해서 관계를 정의해야 한다.

ERD 에서 관계 설정하는 것을 보면, 한 엔티티에서 몇 개의 다른 엔티티 개체와 대응하는지 제약 조건을 표기하기 위해 선을 통해서 표현하는 것을 알 수 있다.

  • One to one : 1 대 1 대응
  • One to many : 1 대 다 대응
  • Many to one : 다 대 1 대응
  • Many to many : 다 대 다 대응

처음엔 N:M 관계가 존재할 수 있다. 추후 논리적 모델링 시에 Many to many는 지양하고 연결 테이블을 통해서 나누어 관리해야 한다. 다대다 관계를 지양해야 하는 이유는 따로 정리해 두었다.

DB 설계 시 다대다(ManyToMany) 관계를 사용하면 안 되는 이유

 

DB 설계 시 다대다(ManyToMany) 관계를 사용하면 안 되는 이유

DB 설계를 하다 보면 나도 모르게 다대다 관계를 생각해서 ERD를 그리는 경우가 있다.이때마다 다대다 관계는 하면 안 되지!라는 생각을 갖고 다시 중간에 entity를 추가하여 일대다, 다대일 관계로

keepgoingforever.tistory.com

간단하게 다대다 관계는 초기에 설계가 간단해 보일 수 있지만, 실제 운영에서는 문제가 발생할 가능성이 크고, 조인 테이블을 명시적으로 정의하여 일대다-다대일로 풀어내는 방식이 개발 생산성과 유지보수성 모두에서 좋기 때문에 다대다 관계로 진행하면 안 된다라고 생각하면 된다.

예로, 온라인 쇼핑몰에서 상품들이 있으면 해당 상품별로 업체들이 제각각 설정되어 있을 것이다. 그러면 간단하게 생각하면 상품 엔티티가 있고 업체 엔티티가 있는데 N:M 관계로 연결된다고 결론이 지어진다. 하지만 이 경우 개발에 어려움이 있기 때문에  1:N 그리고 N:1으로 나누어야 한다. 

상품 - 업체 (N: M -> X)
상품 - 업체별 상품 - 업체(1:N, N:1 -> O)

상품 : 상품명, 상품 분류, 판매코너
업체 : 업체명, 공장 위치

업체별 제품에는 제품명, 업체명 외래 식별자가 있다.
(이 경우 식별 관계임. 비식별로 하려면 업체별 제품에 고유 id가 있어야 함. 해당 내용에 대해서는 논리적 모델링 단계에서 이야기하겠다.)

 

그리고 각 엔티티에 대해서 데이터 생성, 조회, 수정, 삭제(CRUD)가 일어나는 패턴에 대해서 파악해서 ERD 설계를 진행해야 한다. 

또 스토리지에 대해서 RDBMS를 사용할 건지 NoSQL을 사용할 건지에 대한 충분한 고민도 포함되어야 한다.

  • RDBMS 사용이 적절한 경우: 정형 데이터 중심, 강한 정합성이 필요한 경우(예: 금융 시스템, ERP 등).
  • NoSQL 사용이 적절한 경우: 대규모 데이터 처리, 유연한 스키마가 필요한 경우(예: 소셜 네트워크, 로그 데이터 관리 등).
  • Hybrid 접근 방식: 일부 데이터는 RDBMS, 일부는 NoSQL을 사용하는 혼합형 아키텍처 고려.

만약 NoSQL을 사용한다면 collection 간에 reference를 할지, embed를 할지 결정해서 스키마를 결정해야 한다.(논리적 모델링 단계에서 진행)

Reference vs Embed

  • 참조(Reference): 정규화된 형태로 데이터 저장 (데이터 일관성 유지)
    • 자주 수정되는 데이터는 Reference를 사용하는 것이 좋다.(예: 주문 상태, 재고 수량.)
  • 내장(Embed): 비정규화된 형태로 데이터 저장 (읽기 성능 향상, 업데이트 비용 증가)
    • 거의 변경되지 않는 정적인 데이터는 Embed를 사용하는 것이 좋다.(예: 사용자의 기본 프로필 정보.)
  • 읽기 최적화 vs 쓰기 최적화를 고려하여 선택

 


 

이제 논리적 모델링 단계로 넘어가 보겠다.

이제 독립적으로 표현한 엔티티들에 대해서 상세한 속성을 설정해야 한다. 

예로, User에 대해서 name, email, password 등 상세한 속성을 부여해야 한다.

상세 속성을 부여할 때 위와 같이 특징적인 속성들도 있지만 이외에 생성일시(created_at), 수정일시(updated_at) 이러한 속성들에 대해서도 생각하며 진행해야 한다. 아마 개발을 진행하면 해당 컬럼들이 중요하게 작용할 때가 많기 때문이다.

또한, 논리 삭제/물리 삭제에 대해서도 고민을 해서 상세 컬럼을 정의해야 한다. 만약 기획 과정에서 바로 물리 삭제되면 안되는 레코드들 혹은 추후 관리되어야할 데이터들에 대해서는 삭제되었다고 시스템에서는 처리하지만 데이터베이스 내에서는 존재하는 논리삭제 방식을 사용해야하는 경우가 있다. 이 경우 is_deleted, deleted_at 등 컬럼을 추가해서 엔티티를 표현해야 한다.

 

그리고 엔티티에 대해서 속성을 설정하고 보면, 중복된 데이터들이 있을 수 있다. 이 경우에 정규화 과정을 통해서 데이터 중복을 최소화하는 방향으로 나아가야 한다. 최대한 엔티티를 쪼개는 방향으로 나아가는 것이 좋다.

예로, 배달의 민족 클론 코딩 ERD를 보면 주문 테이블에 결제 관련 데이터들도 들어가 있는 것을 볼 수 있다. 팀프로젝트를 진행했을 때 해당 부분에 대해서 주문과 결제 엔티티로 나누어 설계하였다. receipt 테이블이 독립적으로 분리되어 결제 관련 데이터를 따로 저장하였다. 이를 통해 아래에 얻은 이점을 정리하였다.

  • 결제와 주문 데이터의 책임 분리를 명확히 하여 데이터 정합성을 높인다.
  • 추가적으로 다른 정보가 들어갈 때에도 주문, 결제에 대해서 따로 독립적으로 확장할 수 있다.

 

정규화 전
정규화 후

 

그리고 아까 언급했던 식별/비식별 관계에 대하여 말해보겠다. 

  • 식별 관계: 부모 엔티티의 기본 키(PK)를 자식 엔티티의 PK로 포함하는 것이다.
  • 비식별 관계: 부모 엔티티의 PK를 자식 엔티티의 일반 속성으로 포함하는 것이다.

그래서 아까 업체별 제품에 대해서 id 값을 따로 추가하지 않으면 식별 관계가 되고 id 값을 추가하면 비식별관계가 되는 것이다.

식별 관계의 경우, 무결성을 강하게 유지할 수 있는 장점과 변경 시에 부담이 증가한다는 단점이 있다. 비식별 관계의 경우에는 확장성이 높고, 독립적인 관리가 용이하다는 특징이 있다.

요즘엔 거의 비식별 관계로 설계하는 추세라고 들었다.

그리고 아까 언급했던 연결 테이블을 통해서 다대다 관계를 풀어주는 작업을 해야 한다. 

 


 

다음으로 물리적 모델링 단계로 넘어가 보겠다.

해당 과정은 가장 실질적으로 DB에 대해서 어떻게 구성할 건지에 대해서 정의하는 단계라고 볼 수 있다.

 

일단 필드명 네이밍에 대해서 유의해야 한다.

보통 DBMS별 대소문자 구분 이슈로 인해서 snake_case를 사용한다. (예: user_profile, order_history 등)

그리고 db에서 id, name 등 여러 엔티티에서 자주 쓰이는 필드들이 있다. 이 경우 모든 엔티티에 대해서 동일하게 작성할 건지, 엔티티별로 이름을 추가해서 user_id, product_id 등 앞에 엔티티를 붙여서 표현하는 방법이 있다.

이 경우는 팀원들과 상의하여 결정하면 좋을 것 같다. 나는 보통 앞에 entity 명을 붙여주는 것이 명시적으로 표현된다고 생각해 파악하기 편하여 선호하는 편이다.

 

또 도메인을 설계해야 한다. 필드별로 어떤 데이터타입을 사용할 건지도 중요한 부분이다.

특히 가장 고민되었던 부분은 pk를 어떤 데이터 타입으로 구성할까에 대한 것이다.

이 부분에 대해서는 따로 정리를 해두었다.

[DB] PK (Primary Key) ID 길이가 성능에 영향을 미칠까?

 

[DB] PK (Primary Key) ID 길이가 성능에 영향을 미칠까?

Primary Key ?PK (Primary Key)는 데이터베이스에서 각 행(row)을 고유하게 식별하기 위한 열(column)이다.PK는 효율적인 데이터 검색, 외래 키 참조, 데이터 무결성 보장을 위해 사용된다. PK 길이가 길어질

keepgoingforever.tistory.com

현재는 분산 서버를 사용하고 있지 않아서 보통 프로젝트에서 pk로 bigint를 사용하고 있다.

 

또, varchar, char, text에 대한 부분이다.

간단하게 VARCHAR는 255자 이하의 짧은 텍스트 (검색 가능)에 사용하고,  TEXT는 길이가 긴 텍스트 (검색 인덱스 불가능)에 사용한다. 또 char는 짧은 길이이고, 빠른 인덱스가 필요할 때 사용한다.

그리고 timestamp vs datetime 타입 선택도 DBMS별로 잘 선택해서 진행해야 한다. 

해당 내용에 대해서도 정리한 블로그를 공유하겠다.

데이터베이스 자료형에 대해 알아보자!

 

데이터베이스 자료형에 대해 알아보자!

먹물 프로젝트를 진행하면서, 데이터베이스 설계 시 자료형을 어떤 것을 쓸지 선택하는 것이 성능, 저장소 효율성 등에 큰 영향을 끼친다는 것을 느꼈습니다. 이를 계기로 데이터베이스 자료형

keepgoingforever.tistory.com

 

그리고 FK를 설계할 때 제약 조건을 어떤 식으로 할지에 대해서도 고려해야 한다. FK 설정 시 제약 조건을 통해 부모(참조되는 테이블)와 자식(참조하는 테이블) 간의 관계를 정의할 수 있다.

  • CASCADE: 부모와 자식 데이터를 강하게 연결하고, 함께 삭제/수정이 필요한 경우.(예: 게시글 삭제 시 댓글도 함께 삭제되어야 하는 경우.)
  • SET NULL: 자식 데이터를 유지하면서 부모와의 연결만 끊고 싶은 경우.(예: 프로젝트 종료 시 관련된 사용자 데이터는 남기되 프로젝트 정보는 NULL 처리.)
  • RESTRICT: 부모 데이터의 삭제나 수정을 엄격히 제한해야 하는 경우.(예: 국가 테이블과 도시 테이블에서 국가 삭제를 방지.)

참고로, NoSQL의 경우 FK 개념이 없으므로 데이터 정합성은 애플리케이션 레벨에서 처리한다.

 

다음으로 데이터를 효율적으로 검색하기 위한 인덱스 설계에 대해 알아보겠다.

Clustered IndexNon-Clustered Index 두 가지로 인덱스 방법을 나눌 수 있다.

Clustered Index는 테이블 당 하나만 존재할 수 있고, 보통 PK가 클러스터드 인덱스로 설정되어서 데이터가 물리적으로 정렬이 되어 특정 범위의 데이터를 빠르게 검색할 때 효율적이다. 하지만 삽입, 삭제가 일어났을 때 해당 인덱스에 의해서 데이터를 물리적으로 재정렬해야 하기 때문에 비용이 발생하는 것을 항상 염두해서 인덱스를 설정해야 한다.

Non-clustered 인덱스는 실제 데이터는 정렬하지 않고 별도의 인덱스 테이블에 저장된다. 해당 인덱스는 원본 데이터의 위치를 가리키는 포인터를 포함한다. 하나의 테이블에 여러 개의 넌클러스터드 인덱스가 존재할 수 있다.

특정 컬럼을 자주 조회하거나 검색 조건으로 사용하는 경우 빠르게 검색할 수 있다.

예로,

  • 주문 내역 테이블:
    • 주문 데이터는 order_date를 기준으로 정렬되어야 하며, 날짜 범위 검색이 자주 발생한다.
    • 따라서 Clustered Index를 order_date에 설정한다.
  • 고객 주문 검색:
    • 특정 고객의 주문 내역을 자주 조회한다.
    • 따라서 Non-Clustered Index를 customer_id에 설정한다.

 

CREATE TABLE Orders (
    order_id BIGINT PRIMARY KEY,
    customer_id BIGINT,
    order_date DATE NOT NULL,
    total_price DECIMAL(10,2)
);

-- Clustered Index는 기본적으로 Primary Key(order_id)에 설정됨
-- order_date에 추가로 Clustered Index를 설정
CREATE CLUSTERED INDEX idx_order_date ON Orders (order_date);

-- Non-Clustered Index를 customer_id에 설정
CREATE NONCLUSTERED INDEX idx_customer_id ON Orders (customer_id);

이러한 이점도 주지만 무분별한 인덱스 설정은 검색 성능을 더 저하시킬 수 있다.

해당 참고 블로그를 통해서 유의해서 사용하면 좋을 것 같다!

인덱스에 대해서 알아보자!

 

인덱스에 대해서 알아보자!

이전에 [DB] PK (Primary Key) ID 길이가 성능에 영향을 미칠까? 라는 글을 쓸 때 인덱스에 대해서 간단히 알아보았다.또한, 데이터베이스 튜닝에 대하여 라는 글을 쓸 때에도 인덱스를 설정하여 DB의

keepgoingforever.tistory.com

 

3단계에 맞추어 ERD 설계에 도움이 될만한 요소들을 정리해 보았다.

이 외에도 다양하고 심화적인 요소까지 고려하여 ERD를 작성하면 초반에 더 기반이 탄탄한 프로젝트를 진행할 수 있다.

하지만 개발을 진행하면서 ERD를 수정하는 과정은 빈번히 일어날 수 있고 그때마다 잘 수정하고 대응하면 더 완전한 프로젝트로 발전할 수 있으니 항상 ERD에 관심을 기울이면서 개발하는 것이 중요하다!

그리고 참고로 erd를 설계할 때는 보통 erdcloud를 사용하여 진행하는 것 같다. 무료기도 하고 같이 공유해서 쓸 수 있어서 좋다!

(유의할 점은 처음부터 여러 명과 공유하고 싶은 경우에는 미리 팀으로 초대해서 하는 것이 좋다... ㅎㅎ erd가 복사가 안되길래.. 처음부터 같이 만드는 게 좋을 거다..!)

점점 프로젝트를 거치면서 ERD는 단순한 테이블 설계가 아니라, 데이터의 흐름을 고민하는 과정이라고 생각된다.

 

모두 ERD에 대해서 충분히 고민하고 설계하면 분명 좋은 프로젝트로 마무리할 수 있을 것이다!

 

해당 블로그가 erd 설계에 도움이 되었으면 좋겠다.

(혹시 부족한 부분이 있다면 조언도 해주시면 정말 감사할 것 같습니다!!)

 

728x90
반응형