[SQL AntiPattern] 1부 논리적 데이터베이스 설계 안티패턴

September 1, 2023 - 15 minute read -
book sql anti pattern database sql

SQL 을 효과적으로 활용하기 위해 SQL AntiPattern 의 내용을 정리한다.
1부에서는 데이터베이스 테이블과 컬럼, 관계를 계획하는 방법에 대해 알아본다.


2장 무단횡단: 다중 값 속성 저장

다대다 관계를 위해 교차 테이블 생성을 피하기 위해 쉼표(,)로 구분된 목록을 사용한다.
이러한 안티패턴을 책에서는 ‘무단횡단’ 또는 ‘교차로’ 로 표현했다.

안티패턴: 쉼표로 구분된 목록에 저장

product 테이블의 account_id 컬럼을 VARCHAR 로 선언하고 쉼표로 구분해 나열하는 방법 (ex. ‘12,34’)

  • 특정 계정에 대한 상품을 찾기 위해서는 패턴 매칭 필요
    • ex) REGEXP, LIKE
  • 계정을 조회하기 위해서는 어려운 조인, 많은 비용 발생
    • 인덱스 활용 불가
    • 두 테이블의 카테시안 곱 생성하여 평가
  • 제품 별 계정 정보를 집계하기 위해서는 COUNT, SUM 같은 집계 쿼리 사용 불가
    • , 의 개수를 세는 등 특별한 방법 필요
  • 제품에 대한 계정 변경이 어려움
    • concat, replace 등의 함수를 사용해야 함 (정렬은 불가능)
    • 특정 처리를 위해 많은 코드 필요
  • 유효하지 않은 계정에 대한 검증이 어려움
  • 문자열인 경우 구분자 문자를 포함하는 경우 처리가 모호함
  • 목록 길이 제한을 정하는 것이 모호함 (각 항목의 길이가 다르면 들어갈 수 있는 항목의 개수가 달라짐)

사용이 합당한 경우

  • 반정규화를 적용하여 성능을 향상하기 위함
    • 정규화가 우선되어야 하므로 보수적으로 결정 필요
  • 목록 안의 개별 값을 분리할 필요가 없는 경우

해법: 교차 테이블 생성

contact mapping erd
contact mapping erd (출처: SQL AntiPattern)

account_idproduct 테이블이 아닌 별도의 테이블에 저장
이렇듯 어떤 테이블이 FK 로 두 테이블을 참조할 때 교차 테이블(조인 테이블, 다대다 테이블, 매핑 테이블) 이라고 함

  • productcontact 테이블을 통해 account 쉽게 조회 가능
  • 제품과 계정에 대한 복잡한 집계 쿼리 가능
  • 특정 제품에 대한 계정 변경 쉬움
  • FK 를 사용하여 유효한 계정 검증 가능, 참조 정합성 유지
  • 구분자 필요하지 않음, 항목 수 제한 없음
  • 인덱스를 활용하여 성능 향상
  • 각 항목에 속성 추가 가능

3장 순진한 트리: 계층구조 저장 및 조회

답글을 달 수 있고 답글에 대한 답글을 달 수 있다고 가정
데이터가 재귀적 관계를 가지고 트리나 계층 구조를 가질 수 있음
각 항목은 노드라고 부르고 최상위 노드는 뿌리(root) 가장 아래 노드는(leaf)라고 부름

안티패턴: 항상 부모에 의존

adjacency list comment table
adjacency list comment table (출처: SQL AntiPattern)
adjacency list comment erd
adjacency list comment erd (출처: SQL AntiPattern)
adjacency comment tree sample
adjacency comment tree sample (출처: SQL AntiPattern)

이처럼 같은 테이블 안의 다른 글을 참조하는 설계를 인접 목록(adjacency list) 라고 한다.

  • 단계가 깊어질 수록 컬럼을 추가하는 방식으로 후손을 포함하게 됨
    • 계속 본인 테이블의 JOIN 문을 추가해야 함
    • COUNT 같은 집계 수치를 계산하기 어려워짐
    • 다른 방법으로 관련 데이터를 모두 조회하여 애플리케이션에서 트리구조를 만들어줄 수 있음
  • 노드를 삭제하기 위해서는 자손을 찾아 가장 아래 단계부터 차례로 삭제 필요
    • ON DELETE CASCADE 로 자동화 가능함
    • 트리에서 고아 노드 관리가 필요함

사용이 합당한 경우

  • 계층적 데이터 작업이 많지 않은 경우
    • 주어진 노드의 부모나 자식을 바로 얻을 수 있음
    • 새로운 노드 추가가 쉬움

해결책: 대안 트리 모델 사용

  • 인적 목록 모델 이외에 다른 모델 사용 (처음에는 복잡해보일 수 있음)
    • 경로 열거(Path Enumeration)
    • 중첩 집합(Nested Sets)
    • 클로저 테이블(Closure Table)

경로 열거(Path Enumeration)

  • 일련의 조상을 각 노드의 속성으로 저장
    • ex) 디렉터리 구조. /usr/local/lib 에서 usrlocal 의 부모
  • comment 테이블에서 parent_id 컬럼 대신, 긴 VARCHAR 타입의 path 컬럼 정의
  • LIKE 구문을 통해 자손과 후손에 대해 조회 가능
  • 다른 노드를 수정하지 않아도 종단이 아닌 중간 노드 삽입 가능
    • 부모 경로를 복사하고 새 노드의 아이디를 조회하여 path 변경
  • breadcrumb 을 사용자 인터페이스에 보여줄 때 좋음
    • breadcrumb: 위치를 쉽게 추적, 헨젤과 그레텔의 빵 부스러기에서 유래
  • 무단횡단과 비슷한 단점 존재
    • 데이터 검증 불가
    • 문자열 컬럼의 제한으로 트리의 깊이 제한이 존재

중첩 집합(Nested Sets)

nested sets comment tree sample
nested sets comment tree sample (출처: SQL AntiPattern)
  • 부모 대신 자손의 집합에 대한 정보 저장
    • 트리의 각 노드를 두 개의 수로 부호화 가능
    • comment_id 와 상관 없음
    • ex) nsleft 수는 자식 노드의 nsleft 보다 작고, nsright 는 자식의 nsright 보다 큼
  • 노드 숫자 사이로 조상 및 자손 조회 가능
  • 자식을 가진 노드를 삭제해도 그 자손이 자동으로 삭제된 노드 부모의 자손이 됨
    • 노드를 삭제하여 숫자 간격이 생겨도 트리구조에 문제가 되지 않음
  • 트리 수정없이 조회를 많이 하는 경우 적합
  • 단점
    • 새로운 노드를 추가하는 경우 모든 노드의 값을 다시 계산해야 함
      • 트리에 노드를 삽입하는 경우가 많다면 중첩 집합은 적합하지 않음
    • 직접 적인 부모 찾는 쿼리처럼 일부 쿼리가 복잡해질 수 있음
      • 대상의 조상 중 부모와 자식 노드 사이에 노드가 없는 경우를 찾아야 함

클로저 테이블(Closure Table)

closure table comment tree sample
closure table comment tree sample (출처: SQL AntiPattern)
  • 계층구조를 저장하는 단순한 방법
    • 부모-자식 관계 뿐만 아니라 트리의 모든 경로 저장
  • 트리의 정보를 comment 테이블이 아닌 트리 구조를 저장하는 새로운 테이블 생성
    • ex) tree_path 라는 테이블 생성
  • 조상이나 자손을 조회하는 쿼리가 직관적임
  • 새로운 종말 노드를 추가하려면 자기 자신을 참조하는 행 추가
  • 융통성있는 모델이지만 많은 저장 공간을 필요로 함
  • 다양한 속성을 추가해 테이블 개선 가능
    • ex) path_length 를 추가하여 트리의 깊이 조회 가능
모델 테이블 자식 조회 트리 조회 삽입 삭제 참조 정합성
인접 목록 1 쉽다 어렵다 쉽다 쉽다 가능
재귀적 쿼리 1 쉽다 쉽다 쉽다 쉽다 가능
경로 열거 1 쉽다 쉽다 쉽다 쉽다 불가능
중첩 집합 1 어렵다 쉽다 어렵다 어렵다 불가능
클로저 테이블 2 쉽다 쉽다 쉽다 쉽다 가능

4장 아이디가 필요해: 중복행 방지

  • 아무런 의미도 가지지 않는 인위적인 값을 PK 로 사용하는 형태를 가상키(pseudokey) 또는 대체키(surrogate key) 라 함
    • 대부분의 DBMS 에서는 가상키 값이 유일하게 할당되는 것을 보장하기 위한 메커니즘 제공
      • ex) AUTO_INCREMENT, GENERATOR, IDENTITY, ROWID, SEQUENCE, SERIAL
  • PK 는 테이블 내의 유일함을 보장
    • 각 행에 접근하는 논리적 메커니즘
    • 중복 행이 저장되는 것을 방지
    • PK 관계를 생성할 때 FK 로부터 참조

안티패턴: 만능키

모든 테이블에 다음과 같은 특성의 PK 칼럼 추가

  • 칼럼 이름은 id
  • 데이터 타입은 32 비트 또는 64비트 정수
  • 유일한 값은 자동 생성

특징

  • 중복 키 생성
    • id 컬럼을 정의하고 테이블에 UNIQUE 제약조건이 설정된 컬럼을 추가
    • ex) id 가 있지만 bug_id 키 추가
  • 중복 행 허용
    • 교차 테이블에서는 복합키를 통해 유일한 값을 보장해야 함
    • id 칼럼을 사용하는 경우 두 칼럼에 제약조건이 적용되지 않음
      • UNIQUE 제약조건을 걸어야 한다면 id 칼럼은 불필요
  • 모호한 키의 의미
    • id 라는 의미는 일반적이기 때문에 아무런 의미를 가지지 못함
      • 어떤 테이블의 id 인지 구분이 어려움
    • bug_id, account_id 같은 이름이 가독성이 좋음
  • USING 사용 불가능
    • id 를 사용하면 이름이 다르기 때문에 불가능
    • 두 테이블에서 컬럼 이름이 같다면 간략한 문법 사용 가능
      • ex) SELECT * FROM bug JOIN bug_product USING (bug_id);
  • 어려운 복합키
    • id 를 사용하지 않으면 복합키 필요
    • 복합 PK 를 참조하려면 복합 FK 가 되어야 함
      • 쿼리가 복잡해짐

사용이 합당한 경우

  • id 가상키 관례를 따르는 객체-관계 프레임워크를 사용하는 경우
    • ex) CoC (Convention over Configuration)
  • 지나치게 긴 자연키를 대체하기 위함
    • ex) 파일 경로 - 긴 문자열을 키로 한다면 많은 인덱스 유지 비용 발생

해법: 상황에 맞추기

정해진 관례를 무조건 따르지 않고 상황에 맞게 선택

  • 있는 그대로 말하기
    • 의미있는 PK 이름 사용 (ex. bug 테이블의 PK 는 bug_id)
    • 본질을 더 잘 표현 하는 경우, FK 를 PK 이름과 다르게 설정 (ex. bugreported_by)
  • 관례에서 벗어나기
    • 사용하는 프레임워크의 설정을 변경
    • 의미 있는 컬럼 이름 사용하는 것이 중요
  • 자연키와 복합키 포용
    • 자연키로 적당했던 컬럼이 중복이 허용되도록 변경될 수도 있음
    • 복합키가 적절한 경우에 복합키 사용
      • 참조하는 FK 도 복합키가 되어야 하므로 주의
      • 중복된 칼럼 값을 얻을 때 조인을 안해도 되는 장점이 생김

5장 키가 없는 엔트리: 데이터베이스 아키텍처 단순화

다음과 같은 상황으로 인해 참조 정합성 제약조건(또는 FK)을 사용하지 말라는 경우가 있음

  • 데이터 업데이트 시 제약조건과 충돌
  • 참조 정합성 제약조건 또는 FK 를 지원하지 않는 데이터베이스 설계
  • FK 에 자동 생성되는 인덱스로 인해 성능에 영향을 받음
  • FK 선언을 위해 문법을 찾아봐야 함

안티패턴: 제약조건 무시

외래 키 제약조건을 생략하면 다른 방식으로 정합성 유지가 필요해짐

  • 무결점 코드
    • 행을 추가, 삭제할 때마다 값이 존재여부 확인 필요
    • 높은 동시성(concurrency)과 확장적응성(scalability)이 필요한 환경에서 제대로 동작하지 않음
  • 오류 확인
    • 손상된 데이터를 찾기위해 자주 확인 필요
    • 오류를 발견해도 어떤 값으로 맞춰야 할 지 바로잡기 어려움
  • “내 잘못이 아냐”
    • 데이터베이스를 건드리는 모든 코드가 완벽하지 않음
    • 코드를 수정해도 모든 경우에 대해 문제가 없는지 확신이 어려움
  • 진퇴양난 업데이트
    • FK 제약조건을 위반하지 않기 위해 여러 컬럼 실행 필요
    • 자식 행이 참조하는 컬럼을 UPDATE 하는 경우 처리가 어려워짐
      • 자식 행 업데이트 전 부모 행 업데이트 불가, 부모 행 업데이트 전 자식행 업데이트 불가

사용이 합당한 경우

  • FK 제약조건을 지원하지 않는 데이터베이스
    • 품질 제어 스크립트 같은 것으로 보완 필요

해법: 제약조건 선언하기

  • FK 사용하여 처음부터 잘못된 데이터가 입력되지 않도록 설정
    • 불필요한 코드 작성할 필요 없음
    • 모든 코드가 동일한 제약조건을 따르는 것을 확신 가능

여러 테이블 변경 지원

단계적 업데이트(cascading update) 기능 지원

  • 부모 행을 업데이트 또는 삭제하는 경우 자식 행을 알아서 처리 (진퇴양난 문제 해결)
  • ON UPDATE , ON DELETE 선언 방식에 따라 결과 제어 가능
    • CASCADE : 다른 행에서 대상 행을 참조하고 있으면 함께 변경/삭제 됨
    • RESTRICT: 다른 행에서 대상 행을 참조하고 있으면 변경/삭제되지 않고 오류 발생
    • NO ACTION: MYSQL 에서는 RESTRICT 와 동일
    • SET NULL: 대상 행에서 대상 행을 참조하고 있으면 값을 NULL 로 변경

오버헤드

약간의 오버헤드가 있을 수 있지만, FK 가 더 효율적

  • 데이터 확인을 위한 SELECT 쿼리 불필요
  • 여러 테이블 변경을 위해 테이블 잠금 불필요
  • 고아 데이터를 정정하기 위한 품질 제어 스크립트 불필요


6장 엔티티-속성-값: 가변 속성 지원

객체지향 프로그래밍 모델에서 데이터 타입을 상속하는 것과 같은 방법으로 관계를 가질 수 있다.

bug issue feature request table erd
bug issue feature request table erd (출처: SQL AntiPattern)

버그 데이터베이스로 예를 들어,
BugFeatureRequest 는 베이스 타입인 Issue 속성을 공통으로 가지고 각자 다음 속성들을 갖는다.

Bug: 제품의 버전, 중요도, 영향도 FeatureRequest: 예산을 지원하는 스폰서

안티패턴: 범용 속성 테이블 사용

별도 테이블을 생성해 속성을 행으로 저장하는 방식

issue attribute table erd
issue attribute table erd (출처: SQL AntiPattern)
  • 엔티티 (Entity)
    • 속성하나의 엔티티에 대해 하나의 행을 가지는 부모 테이블에 대한 FK
  • 속성 (Attribute)
    • 일반 테이블에서의 컬럼 역할
    • 속성이 하나씩 들어감
  • 값 (Value)
    • 속성에 대한 값을 가짐

이 설계는 엔티티-속성-값(Entity-Attribute-Value) 또는 EAV, 오픈 스키마(open schema), 스키마리스(schemaless), 이름-값(name-value pairs) 으로 불리기도 함

  • 장점
    • 두 테이블 모두 적은 컬럼을 가짐
    • 새로운 속성을 지원하기 위해 컬럼을 추가할 필요가 없음
    • 특정 속성이 필요 없는 경우 NULL 을 채워도 되지 않음
  • 속성 조회
    • 문자열로 속성 이름을 지정하여 정보를 조회해야 함
    • 일반 조회 쿼리보다 더 복잡하고 명확하지 않음
  • 데이터 정합성
    • 필수 속성(NOT NULL) 사용 불가
    • 데이터 타입 사용 불가
      • 타입마다 컬럼을 선언하여 사용할 수도 있지만 쿼리가 더 복잡해짐
    • 참조 정합성 강제 불가
    • 속성 이름 강제 불가
  • 행을 재구성하기
    • 일반적인 테이블에 저장된 것처럼 하나의 이슈를 조회하려면 각 속성에 대해 조인필요
    • 속성 개수가 늘어나면 조인 회수도 늘어나고 쿼리 비용도 지수적으로 증가

사용이 합당한 경우

  • 다루기 어려워지므로 명심해서 사용해야 함
  • 비관계형 기술을 사용하는 경우
    • Berkeley DB, Cassandra, CouchDB, Hadoop, MongoDB, Redis…

해법: 서브타입 모델링

단일 테이블 상속 (Single Table Inheritance)

  • 모든 타입을 하나의 테이블에 저장하고, 각 타입에 있는 모든 속성을 별도의 칼럼으로 저장
  • 서브타입을 나타내기 위한 속성 필요 (ex. issue_type)
  • 해당 속성이 적용되지 않는 객체의 경우 NULL 로 채움
  • 새로운 객체 타입이 생기면 해당 타입의 속성도 수용하기 위해 컬럼 추가 필요
  • 적용하기 좋은 경우
    • 서브타입 개수가 적은 경우
    • 특정 타입에만 속하는 속성의 개수 적은 경우
    • 단일 테이블 데이터베이스 접근 패턴을 사용하는 경우

구체 테이블 상속 (Concrete Table Inheritance)

  • 적용되지 않는 속성이 없도록 강제 가능
  • 단일 테이블 상속처럼 부가적인 서브타입을 나타내는 속성이 필요 없음
  • 공통 속성을 인지하기 어렵고, 공통 속성이 추가되면 모든 서브타입 테이블 변경이 필요
  • 모든 서브타입을 조회할 필요가 없는 경우에 적합
    • 서브타입이 별도 테이블에 저장된 경우, 모든 객체를 보기가 복잡

클래스 테이블 상속 (Class Table Inheritance)

  • 객체지향 클래스인 것처럼 생각하여 상속을 흉내
    • 공통인 속성을 포함하는 베이스 타입을 위한 테이블 생성
  • 서브타입을 나타내기 위한 속성이 필요 없음
  • 모든 서브타입에 대한 조회가 많고 공통 컬럼을 자주 참조하는 경우에 적합

반구조적 데이터 (Semistructured Data)

  • 데이터의 속성 이름과 값을 XML 또는 JSON 형태로 저장
  • 새로운 속성을 언제든 저장할 수 있기 때문에 확장이 쉬움
  • 다른 속성 집합을 가질 수 있어서 각 행마다 서브 타입을 가질 수 있음
  • 데이터베이스에서 특정 속성에 대해 지원하지 않는다면 조회가 어렵고, 복호화 코드 필요
  • 완전한 유연성이 필요한 경우 적합

사후 처리

SELECT issue_id, attribute_name, attribute_value
FROM issue_attribute
WHERE issue_id = 1234;
  • EAV 설계를 사용할 수 밖에 없는 경우
  • 단일 행으로 조회하는 것이 아닌, 관련된 속성을 모두 조회하여 애플리케이션 코드에서 처리


7장 다형성 연관: 여러 부모 참조

polymorphism table erd
polymorphism table erd (출처: SQL AntiPattern)

하나의 댓글(comment) 테이블에 대해 버그(bug) 와 기능요청(feature_request) 에 대한 댓글을 저장하고 싶다. 하지만 여러 개의 부모 테이블을 참조하는 FK 를 생성은 불가능하다.
서브 타입이 아닌 관련 되지 않은 경우에도 이러한 문제는 동일하다.

안티패턴: 이중 목적의 FK 사용

이와 같은 경우, 다형성 연관(polymorphic Associations) 또는 난잡한 연관 (promiscuous associations) 이라고도 불리는 해법을 활용한다.

다형성 연관 정의

  • 현재 행이 참조하는 부모 테이블 이름을 저장하는 컬럼 추가 (ex. issue_type, parent_table)
  • 여러 부모 테이블들의 PK 값을 저장할 수 있는 컬럼 필요 (ex. issue_id, parent_id)
  • 여러 테이블에 대해 참조하기 때문에 FK 선언 불가
  • 조인할 때, 부모 테이블 이름을 정확하게 명시해야 함
    • 모두 다른 테이블과 연관되어 있는 경우, 테이블 조인이 불가능
    • 외부 조인을 하는 쿼리를 활용하면 매칭되지 않는 필드는 NULL 로 됨

사용이 합당한 경우

  • 객체-관계 프로그래밍 프레임워크를 사용하는 경우 (ex. Hibernate)

해법: 관계 단순화

관계의 방향성을 거꾸로 하는 역 참조 방법으로 해결

  • 교차 테이블 생성
    • 각 부모에 대해 교차 테이블을 생성하여 여러 개의 FK 사용
      • ex) bug_comment, feature_request_comment
      • 자식 테이블에서 부모 테이블의 타입을 저장하는 컬럼이 필요하지 않음
    • 데이터 정합성 강제 가능
    • 신호등 설치 - 허용하고 싶지 않은 연관이 생길 수 있는 문제 발생
      • 자식 테이블의 특정 행이 같은 부모 테이블의 여러 행과 연관되지 않도록 UNIQUE 제약조건 추가
      • 특정 행이 여러 부모 테이블에 대해 참조되는 것은 방지 불가 (어플리케이션 코드의 책임)
    • 양쪽 다 보기
      • 특정 부모 테이블에 대한 데이터는 교차 테이블을 이용해 간단하게 조회 가능
      • 참조 정합성에 의존하여 조회 가능
    • 차선 통합 - 여러 부모 테이블의 결과를 하나의 테이블처럼 보여줘야 하는 경우
      • UNION 을 통해 결과를 묶을 수 있음
      • COALESCE0() 함수를 사용하여 존재하는 한쪽 부모 테이블의 필드만 나열하여 묶을 수 있음
  • 공통 수퍼테이블 생성
    • 부모 테이블이 상속할 베이스 테이블 생성하여 문제 해결
      • ex) issue 공통 테이블 생성, commentissue 을 참조
    • 부모 테이블 이름을 저장하는 컬럼이 필요하지 않음
    • FK 제약조건이 직접 연결되어 있지 않아도 정합성을 강제하면서 조인도 가능


8장 다중 컬럼 속성: 다중 값 속성 저장

2장 무단횡단 안티패턴과 동일한 목표
여러 개의 값을 저장하고 싶은 경우
ex) 전화번호 - 보조 휴대폰, 사무실, 팩스 번호 등

안티패턴 : 여러 개의 칼럼 생성

  • 각 컬럼에 하나의 값만 저장하기 위해 여러 개의 칼럼을 생성
    • ex) tag1, tag2, tag3
  • 값 검색
    • 관련된 컬럼들에 대해 조회가 필요하다면 OR 또는 IN 조건을 사용해야 함
    • 여러 값에 대해서도 조회가 필요하다면 WHERE 절이 길어짐
  • 값 추가와 삭제 - 어느 컬럼이 비어있는지 확인하기 위한 조회 필요
    • 동시성 문제가 발생될 수 있음 (충돌 또는 덮어쓰기)
    • 복잡한 SQL 을 이용하면 한번에 해결도 가능
      • NULLIF 함수를 이용하여 동일한 대상 값 삭제 가능
      • COALESCE 함수를 이용하여 빈 칼럼에 값 추가 가능
  • 유일성 보장 불가
  • 값의 수 증가 처리 - 테이블 변경 필요
    • 테이블 구조를 변경하려면 잠금 설정하고, 클라이언트 접근 차단 과정 필요
    • 예전 테이블에서 모든 데이터를 새로운 테이블로 복사하고, 예전 테이블 삭제하는 과정은 많은 시간 소요
    • 컬럼을 추가하면 관련된 모든 SQL 문을 확인해 지원되도록 수정 필요

사용이 합당한 경우

  • 속성의 개수가 고정되고 선택의 위치나 순서가 중요한 경우
  • 각 속성의 사용처가 다른 경우 (논리적으로 다른 속성)

해법: 종속 테이블 생성

  • 종속 테이블 생성하고 FK 정의
  • 주어진 태그에 대한 검색이 직관적
  • 행 추가 삭제도 단순해짐
  • 중복이 허용되지 않도록 제약조건을 추가하여 유일성 보장
  • 제한되지 않은 컬럼 개수


9장 메타데이터 트리블: 확장 적응성 지원

데이터 양이 늘어나면 쿼리 성능 저하됨
크기가 늘어나도 쿼리 성능을 향상 시킬 수 있도록 데이터베이스 구성

안티패턴 : 테이블 또는 칼럼 복제

  • 많은 행을 가진 큰 테이블을 작은 테이블로 분리
    • 작은 테이블의 이름을 테이블의 속성 중 하나의 값을 기준으로 지정
  • 하나의 칼럼을 여러 개의 칼럼으로 분리
    • 칼럼 이름은 다른 속성의 값을 기준으로 지정

위와 같이 두 가지의 형태의 안티패턴이 존재
이렇게 구성하게 되면 테이블 수나 칼럼 수가 계속 증가하게 됨

  • 테이블이 우글우글
    • 데이터를 분리해 별도의 테이블에 넣으려면 어떤 테이블로 보낼지 정책 필요
    • 나눠지는 정책이나 값이 변경되면 애플리케이션 에러 발생
    • 새로운 데이터 값이 들어오면 새로운 메타데이터 객체가 필요
  • 데이터 정합성 관리
    • 조건이 올바르지 않는 데이터가 존재하지 않아야 함
    • 테이블 생성 시, CHECK 제약 조건을 통해 제한 가능
  • 데이터 동기화
    • 데이터를 변경하는 경우, 한 테이블에서 삭제하고 다른 테이블에 삽입해야할 수 있음
  • 유일성 보장
    • 유일성을 보장하기 위해서는 PK 값 생성만을 위한 새로운 테이블 정의 필요
  • 여러 테이블에 걸쳐 조회
    • 여러 테이블에 걸쳐 조회해야 하는 경우, UNION 으로 묶어야 함
  • 메타데이터 동기화
    • 테이블 컬럼을 추가하기 위해서는 모든 테이블에 컬럼 추가 필요
    • 와일드카드(*) 사용이 어렵고 이름 지정 필요
  • 참조 정합성 관리
    • 다른 테이블에서 해당 테이블에 대해 FK 선언 불가
    • JOIN 되는 경우, UNION 으로 묶어서 조회 필요
  • 메타데이터 트리블 칼럼 식별하기
    • 컬럼도 동일하게 메타데이터 트리블이 발생

사용이 합당한 경우

  • 현재 데이터와 오래된 데이터를 함께 조회할 필요가 없는 경우
    • 오래된 데이터를 다른 위치로 옮기고 해당 테이블에서 삭제

해법: 파티션과 정규화

  • 수평 분할(horizontal partitioning) 사용
    • 규칙을 정해 행을 여러 파티션으로 분리
      • 물리적으로 분리되어 있지만, 하나의 테이블처럼 사용 가능
    • 별도 스토리지로 분리 가능
    • 행이 잘못된 테이블로 들어갈 문제가 없음
    • 물리적인 테이블 개수를 직접 지정할 수 있음
  • 수직 분할(vertical partitioning) 사용
    • 크기가 큰 컬럼이나 거의 사용되지 않은 컬럼이 있는 경우 유리
      • 와일드카드(*)를 사용하면 해당 컬럼도 모두 조회하게 되므로 성능 저하 발생될 수 있음
      • ex) BLOB, TEXT
    • 행이 고정 크기인 경우 조회 성능이 좋으므로 가변 길이 컬럼을 별도 테이블로 저장하면 성능 향상
      • ex) VARCHAR
  • 메타데이터 트리블 컬럼 고치기
    • 메타데이터 트리블 컬럼에 대해 종속 테이블 생성
    • 함께 조회하는 경우가 드문 경우 사용
      • 함께 조회하는 경우가 많다면 오히려 성능 저하 발생

관련 코드는 깃허브 참고

출처

  • SQL AntiPatterns