본문 바로가기
Dev/DataBase

『DataBase』 설계를 위해 고민한 내용들

by Day-T 2025. 10. 9.

 

설계의 중요성

 

최근에 회사에서 애플리케이션 뒷단 관련하여 모든 권한을 위임받았다.

 

데이터 마이그레이션 업무와 논리적 설계 관련하여 꽤나 괜찮게 수행했고, 웹 애플리케이션을 A-Z까지 설계/개발/운영한 경험을 가진 구성원이 없기 때문에 조금 빠르게 모든 권한을 위임받았다.

 

처음 합류했을 때, 조직 구성원들이 비개발자 95%로 이루어져 있었다. 이로 인해 설계에 대한 일정 투자를 설득하기가 쉽지 않았는데, 프로토타입 제품을 출시하면서 설계 없이 개발한 제품을 한번 경험해 보니 만들고자 하는 제품에 대해 투자를 시도하는 문화가 생겼다. 급하게 만들기보다는 충분한 일정과 리소스를 투자한다는 말이다.

 

데이터베이스 설계도 이와 똑같다. '일단 만들고 보자'는 생각으로 주먹구구식으로 만들다 보면, 처음에는 빠르게 개발되는 것처럼 느껴질 수 있다. 하지만 제품이 커지고 데이터가 복잡해질수록 문제는 눈덩이처럼 불어난다. 좋은 표현으로는 '기술 부채'라고 하며, 나는 역병이 돈다고 표현한다.

 

데이터베이스 스키마, 즉 테이블의 구조는 바로잡는 데 많은 리소스가 소요된다. 설계를 꽤 괜찮게 하려면 눈앞의 기능 구현에만 집중하기보다는 제품을 크게 보는 관점이 필요하다.

 

나쁜 설계는 다음과 같은 문제를 일으킨다.

 

데이터 중복, 수정 이상, 삽입 이상, 삭제 이상

 

위와 같은 '이상 현상'들을 피하기 위해 애플리케이션 레벨에서 방어 로직을 보통 구현하는데, 스프린트 4번 정도 뛰면 시스템 전체가 비대하고 복잡해진다. 데이터는 뒤죽박죽이 되고, 시스템은 느려지며, 작은 변화에도 전체가 휘청거린다.

 

시공 계획을 세우는 것처럼 데이터베이스 설계도 3단계의 과정을 거친다.

 

1단계: 개념적 설계 (Conceptual Design)

핵심 데이터가 무엇인지 정의하는 것.

개발자뿐만 아니라 기획자, 현업 담당자 등 비개발자와 소통하는 것이 주된 목적이다

 

2단계: 논리적 설계 (Logical Design)

관계형 데이터베이스의 원리에 맞게 구체적인 구조로 다듬는 단계다.

특정 RDBMS(MySQL, Oracle 등)에 종속되지 않는, 순수한 논리적인 데이터 구조를 만든다.

 

3단계: 물리적 설계 (Physical Design)

우리가 사용할 특정 RDBMS의 특성에 맞게 최적화하여 구체적인 명세를 정하는 마지막 단계다.

 

개념, 논리, 물리 각각의 설계 모델에 따라서 사용하는 용어가 다르다.

비개발자와 소통할 때 엑셀로 비유하면 편하다.

구분 개념 모델 논리 모델 물리 모델 엑셀 비유 데이터 예시
저장 구조 엔티티 릴레이션 테이블 시트 직원
세부 항목 속성 속성 열, 컬럼 직원의 이름, 주소
데이터 단위 인스턴스 튜플 '세대교체' 직원

 

각 모델링 단계마다 용어가 다른 이유는 각 단계가 가진 고유한 목표와 바라보는 관점이 다르기 때문이다. 데이터베이스를 설계하는 과정은 마치 집을 짓는 과정과 같다. 건축주와 이야기하는 단계, 설계도를 그리는 단계, 실제 시공하는 단계의 언어가 모두 다른 것과 같은 이치다.

 

 

 

개념적 모델링

개념적 모델링은 개발자, 기획자, 현업 담당자 등 모두가 함께 모여 "우리가 만들려는 제품은 이런 모습입니다"라고 합의하는 과정이다. 모두가 이해할 수 있어야 한다. 최대한 그림과 통일된 용어로 소통하며 우리가 만드는 것이 무엇인지를 명확히 정의하는 단계다.

 

모든 설계는 요구사항 분석에서 시작한다.

 

요구사항 분석을 통해 개념적 모델링의 가장 기본적인 구성 요소는 '엔티티(Entity)'를 찾아야 한다.

엔티티는 "데이터를 저장하고 관리해야 할 대상"을 의미한다.

 

엔티티는 다음과 같은 특징을 가진다.

  1. 엔티티는 업무 프로세스에 의해 이용되어야 한다.
  2. 식별 가능해야 한다.
  3. 두 개 이상의 정보를 가진다.
  4. 인스턴스(Instance)의 집합이다.
  5. 다른 엔티티와 관계를 맺는다.

 

설계를 할 때 종종 엔티티로 분류할까 속성으로 추가할까 고민이 되는 경우가 있는데 다음 3가지 개념으로 접근하면 분류 기준을 정의할 수 있다.

 

유형 엔티티

물리적인 형태를 가지고 있어 눈으로 보거나 만질 수 있는 실체를 표현하는 엔티티다.

  • 사원, 학생, 고객, 교수 (사람)
  • 상품, 자재, 물품, 차량, 건물 (사물)

 

개념 엔티티

물리적인 형태는 없지만, 업무적으로 관리해야 할 개념이나 아이디어를 표현하는 엔티티다.

업무 규칙이나 제도, 분류 기준 등을 표현한다.

  • 부서, 조직, 팀 (조직 구조)
  • 계좌, 보험상품 (금융 상품 및 장부)

 

사건 엔티티

업무 프로세스가 진행됨에 따라 발생하는 특정 행위나 사건을 표현하는 엔티티다.

업무 수행에 따라 발생하는 행위를 기록한다.

  • 주문, 계약, 청구, 매출 (판매 및 계약 관련 사건)
  • 결제, 입금, 출금, 미납 (재무 관련 사건)

 

결국 엔티티를 어떻게 분류하고 구성하는지 살펴보면, 만들려는 시스템의 최종 목적과 정체성을 엿볼 수 있다.

 

 

 

논리적 모델링 - 키

데이터베이스는 각 데이터 행(Row)을 유일하게 식별할 수 있는 장치가 필요하고 키(Key)를 통해 해당 역할을 수행한다. 키는 단순히 데이터를 찾는 용도로만 쓰이지는 않고 데이터가 중복되거나 잘못 입력되는 것을 막아주는 무결성 제약조건의 역할도 수행한다.

 

기본 키 (Primary Key - PK)

  1. NULL 값을 가질 수 없다. (NOT NULL)
  2. 반드시 유일해야 한다. (UNIQUE)
  3. 값이 변하지 않아야 한다 (불변성)

 

외래 키 (Foreign Key - FK)

  1. 테이블 간의 관계를 연결하는 역할을 한다.
  2. 다른 테이블의 기본 키를 참조하는 것이다.

 

기본 키를 선택하는 과정에서 우리는 두 가지 후보, 자연 키와 대리 키를 만나게 된다.

 

자연 키란 이름 그대로, 비즈니스 로직 안에서 자연스럽게 발생하는 데이터를 기본 키로 사용하는 것을 말한다. 예를 들어, 대한민국 국민이라는 데이터가 있다면 '주민등록번호'가 자연 키가 될 수 있고, 도서 데이터에서는 'ISBN' 코드가 자연 키가 될 수 있다.

 

자연 키의 장점은 명확하지만, '변경 가능성'이라는 단점을 가지고 있다. 현대 데이터베이스 설계의 중요한 원칙 중 하나는 "기본 키는 영원히 변하지 않아야 한다"는 명제를 위반한다.

 

만약 자연 키를 기본 키로 사용한다가 변경이 필요하다면 시스템과 연동하는 모든 지점을 찾아 데이터 동기화 로직을 추가로 개발해야만 한다. 비즈니스 로직(자연 키)을 PK로 사용한 대가로, 시스템 전체의 복잡도가 기하급수적으로 증가하는 것이다. 이처럼 자연 키는 '변경'이라는 시한폭탄을 품고 있다. 데이터베이스 내부의 정합성을 깨뜨릴 뿐만 아니라, 데이터의 역사성을 왜곡하고, 외부 시스템과의 연동까지 마비시킬 수 있는 매우 위험한 설계 방식이다.

 

자연 키가 가진 '변경 가능성'이라는 약점을 해결하기 위해 등장한 개념이 바로 대리 키 개념이다. 대리 키의 핵심 아이디어는 간단하다. "비즈니스 로직과 완전히 무관한, 오직 데이터를 식별하기 위한 용도로만 존재하는 임의의 값을 기본 키로 사용하는 것이다."

 

대리 키는 어떻게 자연 키의 문제를 해결하는가 하면, 다음과 같다.

  1. 영원히 변하지 않는 키
  2. 손쉬운 변경
  3. 비즈니스 로직의 유연성 확보

 

그렇다면 자연 키는 이제 쓸모없는 것일까? 아니다. PK를 대리 키에게 넘겨주고, 본연의 역할에 집중하면 된다. 즉, 비즈니스적으로 고유해야 하는 모든 칼럼에 UNIQUE 제약조건을 설정하는 것이다.

 

현대적인 데이터베이스 설계는 대리 키-PK, 자연 키-UNIQUE 방식이 사실상 표준이다.

그냥 위처럼 설계해서 웬만하면 손해는 없기 때문에 외우자.

 

과거. 즉, 그 시절 데이터베이스 설계는 애플리케이션보다 데이터 그 자체의 독립성에 더 큰 중점을 두었다. 데이터베이스가 시스템의 중심이었고, 여러 다른 애플리케이션을 이 중앙 데이터베이스에 직접 연결하여 데이터를 사용하는 경우가 많았다. SQL 클라이언트로 직접 데이터를 조회하고 관리하는 일이 많았기 때문에 이런 직관성은 큰 장점이었다. 또한 지금이야 저장 공간 비용이 저렴해졌지만, 과거에는 디스크 비용이 비쌌다. 자연 키를 사용하면 별도의 ID 칼럼을 추가할 필요가 없으므로, 아주 약간이라도 저장 공간을 아낄 수 있었다. 이런 원칙에 따라 비즈니스적으로 의미 있는 식별자(자연 키)를 기본 키로 삼는 것이 '올바른' 설계라고 여겨졌다.

 

하지만 애플리케이션의 복잡성이 기하급수적으로 증가하면서 상황은 완전히 달라졌다. 이제 시스템의 중심은 데이터베이스가 아닌 애플리케이션으로 이동했다.

  1. 비즈니스 요구사항의 폭발적인 변화 속도
  2. ORM 기술의 등장과 패러다임 전환
  3. 분산 시스템과 마이크로서비스 아키텍처(MSA)

 

정리하자면, 과거의 설계가 데이터의 의미와 직관성을 중시했다면, 현대의 설계는 비즈니스 변화에 대한 유연성과 시스템의 안정성을 최우선으로 고려한다. 대리 키는 비즈니스 로직이라는 변화무쌍한 세계로부터 안전장치 역할을 한다. 이 안전장치 덕분에 우리는 비즈니스 로직은 비즈니스 로직대로, 데이터는 데이터대로 각자의 역할에 충실한 시스템을 만들 수 있다. 이것이 바로 현대적인 설계가 '느슨한 결합(Loose Coupling)'을 추구하며 대리 키를 압도적으로 선호하는 이유다.

 

 

 

논리적 모델링 - 참여도와 일대다 관계

관계를 어떻게 설정하느냐에 따라 데이터 모델의 유연성과 확장성이 결정된다.

 

관계형 데이터베이스의 관계를 처음 배울 때 가장 흔하게 하는 오해 중 하나는 관계에 '방향'이 있다고 생각하는 것이다. 특히 개발자의 경우 객체 지향 프로그래밍(OOP)의 참조 개념에 익숙하다 보니, 한쪽에서 다른 쪽으로만 접근할 수 있는 단방향 관계라고 생각하기 쉽다. 결론부터 말하자면, 관계형 데이터베이스의 관계(Relationship)에는 방향이 없다. 외래 키는 두 테이블을 연결하는 제약조건일 뿐, 데이터 조회 방향을 제한하지 않는다.

 

결과적으로 두 테이블이 관계를 맺으려면 FK를 둘 중 한 곳에 두면 된다. 그럼 조인을 통해서 두 테이블을 서로 참조할 수 있다.

 

논리적 모델링에서의 관계 역시 개념적 모델링에서와 마찬가지로 카디널리티와 참여도라는 두 가지 핵심 속성을 가진다. 이 두 가지를 어떻게 조합하느냐에 따라 관계의 성격이 결정된다.

 

참여도

필수적 관계 : 관계를 맺는 상대방 엔티티에 반드시 대응되는 행이 있어야 한다.

선택적 관계 : 상대방 엔티티에 대응되는 행이 없어도 된다.

 

논리적 모델링에서 참여도는 외래 키(FK) 칼럼의 NULL 허용 여부로 구현된다. 어려운 개념은 아니다.

선택적 참여는 데이터베이스에서 자연스럽게 구현되지만 필수적 참여는 어떻게 강제할 수 있을까? 결론부터 말하면, 외래 키와 같은 기본 제약조건만으로는 강제할 수 없다. 이러한 종류의 비즈니스 규칙은 보통 애플리케이션 계층에서 로직으로 해결한다.

 

관계형 데이터베이스는 외래 키를 사용해서 관계를 표현한다. 그렇다면 일대다 또는 그 반대인 다대일 관계에서 외래 키는 어디에 위치해야 할까? 결론부터 말하면, 외래 키는 항상 '다' 쪽에 존재해야 한다. 

 

일대다 또는 다대일 관계는 실무에서 가장 흔하게 사용되는 관계이다.

관계를 표현하기 위한 외래 키는 반드시 '다' 쪽에 위치해야 한다. '일' 쪽에 외래 키를 두면 제1 정규형 위반, 데이터 검색 및 수정의 어려움, 확장성 부재 등 심각한 문제가 발생한다.

 

'다' 쪽에 외래 키를 두면 데이터의 원자성을 준수하면서 확장성 있는 구조를 만들 수 있다.

 

 

 

논리적 모델링 - 일대일, 다대다 관계

일대일 관계는 하나의 행이 다른 테이블의 단 하나의 행과만 관계를 맺는 경우다. 일대다 관계와 비교해서 상대적으로 흔하지 않다. 왜냐하면 일대일 관계는 그냥 하나의 테이블로 합쳐버리는 것이 더 효율적인 경우가 많기 때문이다. 

 

성능 최적화를 위한 분리하는 경우가 있다는데, 내 경우에는 보안 강화를 위해 분리하는 경우가 더 일반적이라고 생각한다.

예를 들어 주민등록번호, 여권번호, 은행 계좌 정보와 같은 개인 정보 데이터는 별도의 테이블로 분리한다.

 

논리적으로는 강한 연관성이 있더라도, 비즈니스 역할과 시스템의 구조적 설계에 따라 테이블을 분리할 수 있다. 이때 일대일 관계를 데이터베이스에서 강제하려면, 외래 키 칼럼에 유니크 제약조건을 추가해야 한다. 

 

일대일 관계에서는 외래 키를 주 테이블 또는 보조 테이블 어느 곳에나 둘 수 있지만, 보조 테이블에 외래 키를 두는 것을 권장한다. 보조 테이블에 외래 키를 두고 UNIQUE 제약조건으로 일대일 관계를 설정한 경우, 비즈니스 요구사항 변경으로 일대다 관계가 필요할 때 UNIQUE 제약조건만 제거하면 쉽게 일대다 관계로 확장할 수 있다.

 

다대다 관계의 해결책은 중간에 연결 테이블을 두어, 기존의 다대다 관계를 두 개의 일대다 관계로 풀어내는 것이다. 연결 테이블은 양쪽 테이블의 기본 키를 각각 외래 키로 가지며, 두 테이블을 연결하는 다리 역할을 한다. 연결 테이블은 단순히 관계를 이어주는 기술적 역할을 넘어 관계 자체에 대한 추가적인 속성을 저장하는 역할을 한다. 이론적으로는 속성 없는 다대다 관계가 존재하지만, 실무에서는 대부분 숨겨진 속성이 발견되므로 다대다 관계를 발견하면 그 관계 자체에 어떤 정보가 포함될지 고민하는 것이 중요하다.

 

 

 

논리적 모델링 - 식별 관계, 비식별 관계

지금까지 우리는 외래 키를 사용해서 테이블 간의 관계를 맺어왔다. 그런데 이 외래 키를 어떻게 사용하느냐에 따 라 관계는 크게 다음 두 종류로 나뉜다.

 

식별 관계

비식별 관계

 

구분하는 기준은 단 하나다.

(식별 관계) 부모 테이블의 기본 키를 물려받아 자식 테이블의 기본키로 사용하는가?

(비식별 관계) 부모 테이블의 기본 키를 단순히 일반 칼럼으로 사용하는가?

 

현대적인 애플리케이션 개발과 데이터베이스 설계에서는 비식별 관계를 사용하는 것이 사실상 표준이다.

 

과거에는 식별 관계를 선호하는 경향이 있었다.

 

'부모 없이는 자식이 존재할 수 없다'는 논리적 관계를 데이터 구조에 직접적으로 표현할 수 있다는 점을 높이 평가했다. 지금과 달리 저장 공간이 비쌌던 시절에는 별도의 ID 칼럼을 만드는 것을 낭비라고 생각하는 경향이 있었고, 특정 시나리오에서 별도의 인덱스 없이도 PK 인덱스를 바로 활용할 수 있다는 점이 장점으로 여겨졌다. 하지만 이러한 장점들은 소프트웨어의 복잡성이 기하급수적으로 증가하고, 변화의 속도가 빨라진 현대에 와서는 그 의미가 대부분 퇴색되었다.

 

소프트웨어 요구사항은 항상 변한다. 식별 관계의 강한 결합은 변화에 대응하는 것을 어렵게 만든다. 반면, 비식별 관계의 느슨한 결합은 PK의 변경 없이 관계나 비즈니스 규칙을 수정할 수 있게 하여 변화에 대응할 수 있도록 한다.

 

JPA/Hibernate ORM 등 현대 애플리케이션 개발의 표준이 된 ORM 기술들은 엔티티(테이블)가 독립적인 단일 식별자(PK)를 갖는 것을 기본으로 설계되었다. 반면, 복합키를 사용하는 식별 관계는 ORM에서 다루기 매우 번거롭고 추가적인 코드를 요구한다. 비즈니스와 무관한 대리 키를 사용하는 비식별 관계는 이러한 외부 변화로부터 데이터 구조를 안전하게 보호하는 '방화벽' 역할을 한다.

 

 

 

정규화

정규화가 왜 필요할까?

 

'하나의 칸에는 하나의 정보만'

'하나의 주제는 하나의 묶음으로'

'반복되는 정보는 한 곳에서 관리'

 

정규화라는 용어는 어떤 대상을 가장 단순하고 표준적인 형태로 변환한 것을 의미한다.

정규화를 이해하기 위한 가장 핵심적인 기초 개념은 '함수 종속성'이다. 함수 종속성이란, 테이블에서 칼럼의 값들이 다른 칼럼의 값을 유일하게 결정하는 관계를 의미한다. 쉽게 말해, X 값이 Y 값을 유일하게 결정한다는 뜻이다.

 

제1 정규형(1NF)

테이블의 모든 칼럼이 원자적(Atomic)인 값만을 가져야 한다.

 

제2 정규형(2NF)

1. 제1 정규형을 만족해야 한다.

2. 테이블의 모든 칼럼이 기본 키에 대해 완전 함수 종속이어야 한다. 즉, 부분 함수 종속이 없어야 한다.

 

부분 함수 종속을 제거하는 방법은 간단하다. 종속 관계에 맞게 테이블을 분리하면 된다.

 

제3 정규형(3NF)

1. 제2 정규형을 만족해야 한다.

2. 이행적 함수 종속이 존재하지 않아야 한다.

 

이행적 함수 종속을 제거하는 방법 역시 테이블을 분리하는 것이다.

 

BCNF(Boyce-Codd Normal Form)는 제3 정규형을 조금 더 강화한 버전으로, '강한 제3 정규형'이라고도 불린다. 실무에서는 제3 정규형까지 만족하는 설계를 목표로 하는 경우가 많지만, BCNF를 이해하면 더 깊이 있는 데이터베이스 설계가 가능하다.

 

제3 정규형은 기본 키가 아닌 칼럼들 사이의 종속 관계(이행적 종속)만 제거했다. 반면 BCNF는 더 엄격해서 기본 키가 아니더라도 어떤 칼럼이 다른 칼럼을 결정한다면, 그 결정자는 반드시 후보 키여야 한다는 규칙이다. 대부분의 경우 제3 정규형을 만족하면 BCNF도 만족한다.


참고

https://www.inflearn.com/course/%EA%B9%80%EC%98%81%ED%95%9C-%EC%8B%A4%EC%A0%84-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EC%84%A4%EA%B3%841%ED%8E%B8

 

김영한의 실전 데이터베이스 - 설계 1편, 현대적 데이터 모델링 완전 정복| 김영한 - 인프런 강의

현재 평점 5.0점 수강생 1,340명인 강의를 만나보세요. 데이터베이스 설계를 개념적, 논리적, 물리적 3단계로 체계적으로 배우고, 실무 중심 쇼핑몰 프로젝트로 역량을 강화합니다. 현대적 설계 기

www.inflearn.com