데이터 모델링

업데이트:

프로젝트 중 클린 아키텍쳐를 활용하면서 모든 엔티티들은 데이터 모델의 엔티티 개념과 일치되며, 대부분의 플로우가 엔티티를 중심으로 이루어진다는 것을 알게 되었다. 데이터 중심의 시스템 설계와 개발을 위해서는 데이터 모델링에 대한 깊은 이해가 필요할 것 같다. 이번 포스팅에서는 데이터 모델링 개념을 다시 정리해봐야겠다.

이성욱님의 책 Real Mysql 14장에 있는 내용을 정리한 포스팅입니다.

서비스의 전체적인 개발 과정 중 db와 관련된 부분은 간략하게 다음 단계를 거친다.

  1. 개념 설계
  2. 논리 설계
  3. 물리 설계
  4. DBMS구축

개념 설계, 논리 설계 단계를 논리 모델링이라 하고 물리 설계, DBMS 구축을 물리 모델링이라 한다. 비즈니스를 분석하고 데이터 집합과 집합 간 관계를 표현하는 것을 논리 모델링. 논리 모델링의 산출물을 시스템으로 표현하는 것을 물리 모델링이라고 볼 수 있다.

논리 모델링

논리 모델링은 다음과 같은 내용을 다룬다.

  • 엔터티의 범위 확정
  • 필수 속성 정의
  • 각 엔터티의 의미상 식별자를 선정
  • 각 엔터티 간의 관계를 최대한 간결히 표현

ERD를 통해 엔터티 간 관계를 표현하는데, 관계는 간결히 표현되어야 한다. ERD의 생명은 가독성이다.

모델링 용어

ERD상의 오브젝트는 논리 모델링에서 사용되는 이름과, 물리 모델링에서 사용되는 이름을 각각 가진다. 경계없이 사용해도 무방할 듯 싶다.

논리 모델 물리 모델
주제 영역(Subject Area) 주제 영역
엔터티(Entity) 테이블
속성, 어트리뷰트 칼럼
관계, 릴레이션 관계, 릴레이션
키 그룹 인덱스

주제 영역이란 관리 용이성이나 가독성을 위해 엔터티를 업무 분류별로 나눠서 그룹핑해둔 개념을 말한다. 엔터티들의 부모 개념으로 생각하면 되겠다.

레코드나 row는 모델링 요소가 아니다. 테이블은 클래스로 볼 수 있고, 레코드는 클래스에서 생성된 인스턴스로 생각하면 되겠다.

엔터티

엔터티는 객체지향 개발 언어의 클래스와 동급의 의미다. 엔터티와 테이블이 항상 1:1 매핑되는 것은 아니다. 2개 이상의 엔터티가 물리 모델링에서 통합되기도 하고, 하나의 엔터티가 여러 물리 테이블로 구현되기도 한다. 엔터티를 설계할 때는 용어의 정의가 가장 중요하다. 해당 용어가 의미하는 범위가 어디까지인지를 명시하고 걸맞는 이름을 부여해야 한다. 그래야 속성이나 식별자, 엔티티간의 관계가 명확해진다.

엔터티의 종류와 표현 방법

엔터티는 ERD에서 다음과 같이 표현된다.

엔티티명식별자 속성PK일반 속성 1일반 속성 2일반 속성 3

엔터티는 크게 키 엔터티와 메인 엔터티, 액션 엔터티로 구분할 수 있다.

  • 키 엔터티 : 데이터 중에서 가장 최상위에 존재하는 엔터티.
  • 액션 엔터티 : 키 엔터티 간의 관계로 인해 만들어지는 엔터티.
  • 메인 엔터티 : 액션 엔터티지만 중요한 역할을 하는 엔터티를 메인 엔터티라고 한다.

엔터티의 작명

엔터티의 이름은 복수형 표현을 사용하지 않고 별도의 수식어가 없는 단순 또는 복합 명사 형태를 사용한다.

  • 하나의 엔터티가 여러 엔터티로 분리되어 모델링됬을 때 엔터티의 이름에 수식어가 사용된다
  • 복수형 표현이나 “리스트”, “목록” 같은 복수를 의미하는 단어는 사용하지 않는 것이 좋다.
  • “정보”와 같은 범위가 애매한 단어를 피하는 것이 좋다.

어트리뷰트

어트리뷰트란 원자성을 가지는 최소의 데이터 보관 단위다. 하나의 엔터티 내에서 다른 어트리뷰트를 비교했을 때 독자적인 성질을 가지는 것이어야 한다.

어트리뷰트는 가공하지 않은 값이라는 의미도 가지고 있다. 상반되는 추출 칼럼은 다른 어트리뷰트로부터 계산된 값을 말한다.(코멘트 개수 등) 데이터 정규화는 보통 데이터 중복을 제거하기 위해 진행하지만, 추출 칼럼은 반대로 데이터의 중복을 만들어낸다.

어트리뷰트의 이름은 의미가 명확해야 한다. 또한 항상 최소의 데이터 단위이므로 항상 하나의 값만 가져야 한다.

어트리뷰트의 원자성

  • 하나의 어트리뷰트는 해당 업무 요건에 맞게 최소 단위의 값 하나만 가져야 한다.(ex. 주소와 일시 데이터)
  • 하나의 어트리뷰트에 복수형으로 값을 저장해서는 안된다.

물리 모델 단게에서는 성능을 위해 조금씩 위배하여 설계할 수도 있지만, 논리 모델 단계에서는 복잡도를 줄이고 가독성있게 유지하는 것이 좋다.

어트리뷰트의 작명

엔터티와 어트리뷰트에 대한 정의를 별도로 문서화하기 싫으면..:smirk: 어트리뷰트의 이름만으로 의미를 이해할 수 있게 명명해야 한다.

어트리뷰트의 이름은 한정자 + 값을 표현하는 명사로 구성하는 것이 이상적이다. (주소 -> 사무실주소, 아이디 -> 회원아이디, 로그인 -> 로그인일시 등)

팀 내에서 ERD에 사용되는 개체의 이름을 표준화한 “표준 단어”가 있다면 최대한 활용하는 것이 좋다.

식별자(Primary Key)

식별자(기본키)는 하나의 엔터티에서 개별 레코드를 식별할 수 있는 어트리뷰트의 조합을 의미한다. 일반적으로 키 엔터티는 식별자로 어트리뷰트 하나만 가지고, 메인 엔터티나 액션 엔터티는 두 개 이상의 어트리뷰티가 조합되어 식별자 역할을 할 때가 많다.

식별자는 본질 식별자(의미상 식별자)와 실질 식별자로 나뉜다.

  • 본질 식별자 : 엔터티의 레코드가 생성되는 조건을 알려주는 식별자
  • 실질 식별자 : 본질 식별자를 대체하는 식별자. 본질 식별자가 실질 식별자가 될 수도 있고, 인조 식별자(surrogate key)가 실질 식별자가 될 수도 있다.

관계(Relation)

관계는 다른 엔터티의 어트리뷰트로 참여하기도 하지만 관계 자체가 별도의 엔터티로 구현돼야 할 때도 많다.

식별 관계와 비식별 관계

  • 식별 관계 : 부모 엔터티의 식별자가 자식 엔터티의 레코드를 식별하는 데 꼭 필요하다면 식별 관계다. 부모 엔터티의 식별자는 항상 자식 엔터티의 식별자로 포함돼야 한다. 이를 식별자 상속이라고 하며 ERD에서 실선으로 표시한다.
  • 비식별 관계 : 부모 엔터티의 식별자가 없어도 자식 엔터티의 레코드가 생성될 수 있을 때 비식별 관게를 사용한다. 부모 엔터티의 식별자는 자식 엔터티의 일반 어트리뷰트로 참여한다. ERD에서 점선으로 표시한다.
게시판게시판아이디PK게시물게시물아이디PK게시판아이디PK,FK회원회원아이디PK게시물게시물아이디PK작성자아이디FK

부모 엔티티의 기본키만 자식 엔터티로 넘어가는 것은 아니다. 대체키 또한 자식 엔터티의 식별자로 상속할 수 있다.

회원회원아이디PK주민등록번호(AK)주문주문번호PK회원아이디 (FK)PK,FK 회원회원아이디PK주민등록번호(AK)주문주문번호PK주민등록번호 (FK)PK,FK

관계의 형태

계층 관계

부모와 자식 간의 직선적인 관계가 연속되는 형태

게시물게시물아이디PK게시판아이디PK,FK게시판게시판아이디PK코멘트코멘트번호PK게시판아이디PK,FK게시판아이디PK,FK

각 엔터티 간의 식별 관계가 반복되면서 부모 엔터티의 식별자를 상속받게 된다. 따라서 자식 엔터티로 갈수록 식별자를 구성하는 어트리뷰트의 수가 많아진다. 이 때 적절한 수준에서 식별자를 인조 키로 대체하는 것이 좋다. 2~4 단계 정도에서 인조 키로 대체하는 것이 일반적이다.

순환 관계

하나의 엔터티가 부모임과 동시에 자식 엔터티가 되는 재귀적인 형태다. 순환 관계는 절대 식별 관계가 될 수 없다.

부서부서코드PK상위부서코드FK

M : N 관계

일반적으로 1:1 이나 1:M 관계는 부모의 식별자가 자식 엔터티의 어트리뷰트로 참여하는 형태로 구현되나, 어트리뷰트는 하나 이상의 관계를 가지지 못하므로 N:M 관계를 어트리뷰트로 표현할 수 없다. 논리 모델에서는 M:N 관계를 추가 엔티티 없이 표현하기도 하지만 물리 모델에서는 관계 엔터티(테이블)를 추가함으로써 M:N 관계를 해소해야 한다.

sequalize와 같은 ORM 라이브러리에서 논리적으로 M:N 관계를 설정하면 실제 구현된 DB에서는 추가 관계 테이블이 생성된다.

학생학생번호PK과목과목번호PK수강학생번호PK,FK1과목번호PK,FK2

BOM 관계 (Bill Of Material)

부품을 결합해서 하나의 또 다른 부분(중간 조립 부품)을 만들고 만들어진 부품은 다른 부품의 조립 시에 사용되는 형태

학생학생번호PK수강학생번호PK,FK1과목번호PK,FK2

배타 관계

엔티티가 서로 다른 두 부모 엔터티로부터 관계를 가지고 있는데, 각 관계가 서로 배타적으로만 존재할 때도 있다. 이와 같이 하나의 엔터티에 두 개 이상의 관계가 동시에 존재할 수 없는 형태를 배타 관계라고 한다.

코멘트코멘트아이디PK게시물게시물아이디PK첨부파일게시물타입PK게시물아이디PK,FK파일아이디PK

배타 관계는 개발 단계에서 항상 UNION 또는 OUTER JOIN을 사용하는 쿼리를 만들어 내므로 좋지 않은 관계일 때가 많다. 따라서 배타 관계가 있디면, 각 부모 엔터티를 하나의 엔터티로 통합할 수 있는지를 검토하는 것이 좋다.

엔터티의 통합

처음 엔터티를 도출할 때는 수많은 엔터티가 나열됐다가 모델링이 진행될수록 줄어드는 것이 일반적이다. 엔터티를 구성하는 어트리뷰트와 관계가 비슷한 엔터티를 보게 되면, 통합의 대상이 아닌지 살펴보는 것이 좋다.

관계의 통합

관계도 엔터티와 같이 통합하는 과정을 거치는 것이 좋다. 가끔 부모와 자식 엔터티 간 관계가 여러 번 나타낼 때 하나의 관계로 통합할 수 있다.

고객고객번호PK보험계약계약번호PK계약고객FK피보험고객FK수익고객RK고객고객번호PK보험계약관계자계약번호PK,FK1고객번호PK,FK2관계자구분PK보험계약계약번호PK

이렇게 관계를 통합하면 SQL 쿼리에서 조인해야 하거나 저장해야하는 테이블이 하나 더 늘어나므로 개발하기 번거로울 수 있다. 하지만 관계를 통합하면 위 예시에서 계약 관계자가 더 늘어나더라도 모델 자체를 변경하지 않아도 되므로 유연하게 대처할 수 있다. (관계를 통합하지 않았다면 보험계약 엔터티의 어트리뷰트를 추가해주는 모델 수정이 필요하다.)

모델 정규화

정규화의 목적

  • 모델에서 중복된 데이터를 최소화하고 일반적으로 납득될 수 있는 모델로 만든다.
  • 각 어트리뷰트가 적절한 엔터티에 배치되고 각 어트리뷰트가 중복된 데이터를 갖지 않게 한다.

논리 모델링에서 진행하는 정규화는 데이터의 저장 비용을 최소화하는 역할을 담당하고, 물리 모델링에서 진행하는 반정규화는 데이터를 읽어 오는 비용을 최소화하는 역할을 한다. 두 과정 모두 필수적이다. 정규화를 수행하지 않으면 데이터의 중복이 늘어나고, 하나의 트랜잭션에서 중복된 데이터를 모두 변경해야 하므로 성능이 떨어진다.

제1정규화

모든 속성은 반드시 하나의 값을 가져야 한다.

하나의 어트리뷰트에 여러 개의 값을 저장하거나, 하나의 엔터티에서 같은 성격의 어트리뷰트가 여러 번 나열되는 것은 일반적으로 제 1정규화를 위반한 것이다. 정규화의 결과는 반복된 성격의 어트리뷰트로 이뤄진 별도의 자식 엔터티로 해결한다.

회원회원번호PK주소회원번호PK,FK주소구분PK우편번호주소회원회원번호PK친구회원들의회원번호자택주소자택우편번호자택우편번호회사주소회사우편번호친구회원번호PK,FK친구회원번호PK,FK

제 2 정규화

식별자 일부에 종속되는 어트리뷰트는 제거해야 한다.

엔터티의 식별자를 구성하는 어트리뷰트가 2개일 때, 그 엔터티의 모든 어트리뷰트가 식별자(2개 속성)에 모두 완전 종속인지를 확인하는 것이 제 2 정규화이다.

회원회원번호PK친구회원번호PK,FK친구회원번호PK,FK친구회원명

위 예제에서 친구회원명은 식별자의 어트리뷰트 중 친구회원번호에만 종속 관계를 가지고 회원번호에는 종속 관계가 없다. 친구회원명 은 회원 엔티티로 이통해야 한다.

일반적으로 제 2 정규화의 결과는 새로운 부모 엔터티가 생성된다.

이후 물리 모델링 단계에서 조회 성능의 향상을 위해 일부 어트리뷰트를 복사하는 반정규화를 많이 사용하지만, 논리 모델링 단계에서는 정규화를 실시해야 한다.

제 3 정규화

식별자 이외의 속성간에 종속 관계가 존재하면 안 된다.

회원회원번호PK회원명직업코드FK직업명직업직업코드PK직업명

위 예시의 직업명은 직업코드에 종속 관계를 가진다.

주로 코드와 관련된 어트리뷰트에서 발생한다.

물리 모델링

물리 모델링에서는 논리 모델링을 통해 나온 산출물을 RDBMS에 특성에 맞게 변환하는 작업을 수행한다. 대표적으로 다음과 같은 작업을 한다.

  • N:M 관계와 같이 RDBMS에서 구현할 수 없는 구조를 해소하는 작업
  • 프라이머리 키의 칼럼 순서 설정
  • 칼럼의 이름 부여
  • 칼럼의 데이터 타입 선정
  • 조회 성능을 위한 반정규화

프라이머리 키 선택

논리 모델링에서 선정한 식별자가 항상 물리 모델의 식별자가 되는 것은 아니다. 물리 모델에서는 프라이머리 키를 구성하는 칼럼의 순서가 중요하다.

테이블의 프라이머리 키를 복합키로 구성할 때는 너무 많은 칼럼이 프라이머리 키로 참여하지 않게 적절히 끊어줄 필요가 있다. 자식 테이블 중에서도 관계를 많이 가지는 중요 테이블에서 새로운 프라이머리 키를 갖게 해 주는 것이 좋다. 주로 시퀀스나 auto increment 와 같은 인조키를 부여하는 방법이 사용된다.

MyISAM스토리지 엔진과 같이 기본키가 클러스터링의 기준이 되지 않는 엔진에서는 기본키 수가 많아도 큰 영향은 없다. 그러나 InnoDB 엔진과 같이 기본키에 의해 클러스터링되는 엔진에서는 기본키가 길어질수록 다른 모든 보조 인덱스의 크기도 커진다.

프라이머리 키도 인덱스이므로 SELECT절에 자주 쓰이는 칼럼 위주로 순서를 배치해야 한다. 후보키가 여러 개라면 조회 조건으로 가장 많이 사용되는 것을 기본키로 선정하는 것이 좋다.

데이터 타입 선정

물리 모델링에서 칼럼의 데이터 타입은 가능한 최소 단위의 타입을 부여해야 한다. 레코드의 수가 많아질수록 한 바이트의 차이가 큰 차이를 만들어낸다.

데이터의 타입

각 테이터 타입별로 차지하는 공간이나 성격을 잘 모른다면 저장하려는 데이터 성격별로 타입을 선정하는 것이 가장 좋다.

IP주소와 같이 타입 설정이 애매한 경우(숫자형 or 문자형) 는 장단점을 잘 조율해서 선정해야 한다. 정수로 관리할 때의 장단점은 다음과 같다.

장점

  • 칼럼의 길이가 15글자에서 4바이트로 줄어듦(IPv4는 내부적으로 4개의 옥텟으로 이루어져 있다.)
  • 칼럼 길이 축소로 성능 향상이 기대됨
  • IP주소를 네트워크 클래스별로 검색하는 기능이 가능하다.

단점

  • 값을 저장하거나 조회할 때 INET_NTOA() 또는 INET_ATON() 함수의 도움이 필요함
  • 단순한 문자열 패턴 검색(LIKE)을 사용할 수 없음

칼럼의 길이

칼럼의 데이터 타입이 결정되면 데이터 타입별로 길이를 설정해야 한다. CHAR, VARCHAR, 길이를 얼마로 선정할 것인지. TINYINT, BIGINT 중 무엇을 사용할 것인지 정해야 한다.

이는 저장하는 데이터의 성격에 따라 서 설정하는 것이 가장 바람직하다.

문자집합(캐릭터 셋)

칼럼에 저장되는 문자열이 어떤 문자집합을 가지는지도 상당히 중요하다. 아시아권 언어는 한 글자당 1~2 바이트이고, utf-8은 1~3 바이트이다.

칼럼의 문자집합을 지정하지 않으면 default-character-set 시스템 설정에 지정된 문자집합이 적용된다.

MySQL 서버는 실제 칼럼에 저장된 데이터의 길이로 메모리를 할당하고 사용하는 것이 아니라 데이터 타입에 명시된 길이를 기준으로 메모리 공간을 할당한다. 해당 메모리 공간이 일정 크기를 초과하면 메모리가 아니라 디스크에서 처리하게 된다. 따라서 칼럼이 크게 설정되면 작업이 디스크에서 처리될 가능성이 높아진다. MEMORY 스토리지 엔진은 VARCHARVARBINARY와 같은 동적 칼럼을 지원하지 않으므로 CHAR와 같은 타입을 사용해야 한다.

NULL과 NOT NULL

MyISAM 스토리지 엔진

  • NULL을 저장하든 NULL을 대체하여 빈 문자열을 저장하든 사용하는 디스크 공간의 차이가 없다.

InnoDB 스토리지 엔진

  • 칼럼이 NULLABLE 이면 길이가 고정된 타입이나 가변 타입 모두 NULL이 저장되는 칼럼은 전혀 디스크 공간을 사용하지 않는다. NULL을 저장함으로써 디스크 공간 절약을 할 수 있다.

검색 조건으로 사용하지 않는 칼럼이라면 NULLABLE 칼럼을 사용해도 된다. 하지만 자주 사용된다면 칼럼을 NOT NULL로 설정하자.(디스크 공간을 차지하는 희생이 충분히 가치있다.)

도메인

데이터의 성격을 적절한 수준으로 분류해 도메인이라는 개념으로 그룹핑하고 데이터 모델링 단계에서 각 칼럼의 타입을 선정하는 것이 아니라 칼럼에 도메인을 맵핑하면, 모든 서비스나 애플리케이션에서 사용되는 데이터베이스의 칼럼 타입을 일관성 있게 통일해서 사용할 수 있다.

반정규화

모델의 정규화는 중복되는 칼럼을 제거하므로 INSERTUPDATE같은 데이터 변경 작업에 최적화된 모델을 만들어 낸다. 하지만 정규화할수록 SELECT에서 필요한 테이블의 수뿐만 아니라 GROUP BY나 쿼리 자체의 개수도 증가한다.

GROUP BYCOUNT(*)과 같이 많은 레코드를 대상으로 빠르게 조회하기 위해 미리 집계해서 테이블이나 칼럼으로 저장해두는 것을 반정규화라고 한다.

반정규화된 칼럼을 실시간 업데이트하면 데이터 변경 작업을 할 때 다른 쿼리를 필요로 하므로 부하가 커지고 lock의 경합도 많아지게 된다. 따라서 반정규화된 컬럼의 값은 최대한 모아서 배치 형태로 업데이트하거나 백그라운드 작업으로 처리하는 것이 좋다.

칼럼 복사

원본 칼럼의 값을 그대로 다른 테이블로 복사하는 반정규화는 GROUP BY, ORDER BY를 인덱스로 처리하게끔 유도할 때 유용하다. 조인을 없애는 용도로는 성능상 크게 도움되지 않는다.(데이터의 수정이 잦을수록 더 도움이 안된다.)

요약 칼럼(Summary Column)

계산의 결과로 만들어진 값을 저장하기 위해 반정규화한 칼럼. 여러 레코드로부터 최대, 최소, 카운트 등을 미리 계산해서 저장해 둔다.

해시 인덱스

해시 인덱스는 동등 비교(Equal)을 가장 빠르게 처리할 수 있는 인덱싱 방식이다. 그러나 MyISAM 이나 InnoDB는 해시 인덱스를 지원하지 않는다.

해시 인덱스를 흉내내는 T-Tree 인덱스를 만들기 위해 반정규화를 사용할 수 있다.

반정규화된 테이블

통계 테이블. ex) 게임의 이력을 이용해 랭킹을 미리 집계해 둔 테이블

서비스용으로 사용되는 테이블의 잠금과 경합이 발생하지 않게 주의해야 한다.

Reference

Real Mysql, 이성욱 14장 - 데이터 모델링

태그:

카테고리:

업데이트:

댓글남기기