사전 EAV
안티패턴

EAV

gabury1

데이터베이스에서 속성을 열이 아니라 행으로 담는 설계입니다. 속성이 늘어도 테이블을 고치지 않아도 됩니다. 그 편함의 값은 조회와 제약 쪽에서 치릅니다.

쉽고 빠른 이해

이 설계는 회원·상품처럼 자꾸 속성이 느는 테이블을 다룹니다. 예를 들어 회원 속성이 하나 늘어도 테이블에 열을 더하지 않고 행 하나만 추가합니다.

속성이 하나 늘 때마다 테이블 구조를 고치는 부담을 없애려고 이렇게 만듭니다. 구조를 고치는 대신 값을 하나 끼워 넣는 일로 바꾸는 것입니다.

이렇게 돕니다.

  1. 무엇에 대한 이야기인지 · 속성 이름 · 값을 세 칸에 나눠 담습니다
  2. 새 속성이 필요하면 테이블 구조를 안 바꾸고 행만 하나 더합니다
  3. 값은 대개 문자열 하나로 뭉뚱그려서 어떤 속성이든 같은 칸에 들어갑니다

대가는 조회와 제약 쪽에서 치릅니다. 값을 다시 열로 모으려면 조인이 늘고, 값 칸 하나로는 타입 검사와 제약을 걸 수 없어 인덱스와 무결성 점검이 무뎌집니다.

상세

관습적인 설계는 관심 있는 파라미터(데이터베이스 문헌에서 속성을 이렇게도 부릅니다)마다 테이블에 열을 따로 둡니다. Nadkarni 외는 자기들 논문에서 관습적 설계를 그렇게 정의합니다. 스프레드시트나 플랫파일 데이터베이스를 쓰는 사람 대부분이 본능적으로 잡는 모양입니다. 다뤄야 할 데이터의 종류가 늘면 테이블 수도 같이 늘어야 합니다.

EAV(Entity-Attribute-Value, 엔티티-속성-값)는 그 반대편입니다. 세 글자는 각각 Entity Attribute Value, 곧 엔티티·속성·값을 가리킵니다. 엔티티 칸에 무엇이 들어가는지는 자리마다 다릅니다. 전자 환자 기록 시스템에서 엔티티는 대개 환자 이벤트입니다. 환자 ID 에 그 사건이 언제 일어났고 시작했고 끝났고 기록됐는지를 적는 타임스탬프 여럿이 붙은 것입니다. 웹 쿠키에서는 엔티티가 쿠키 자신입니다. 쿠키 하나 안의 속성-값 쌍은 전부 그 쿠키 하나에 대한 것임이 이미 자명해서 엔티티 칸에 매번 같은 값을 반복해 적을 필요가 없어 칸을 통째로 생략할 수 있습니다. 개념적으로는 세 칸짜리 테이블 하나입니다. 속성-값 쌍 하나가 행 하나입니다. 이론적으로는 데이터베이스에 기록되는 사실 대부분을 단일 EAV 테이블에 담을 수 있습니다. 아래 표의 EAV/CR(EAV with Classes and Relationships, 클래스와 관계를 갖춘 EAV)은 기본 EAV 표현에 객체 모델링과 객체 간 관계 관리를 더한 변형입니다. 데이터베이스 문헌에서는 엔티티를 객체라고도 부릅니다.

관습적 설계 EAV EAV/CR
칸 파라미터마다 열 하나 엔티티 · 속성 · 값 세 칸 객체 ID · 속성 ID · 값 + 별도 메타데이터 테이블
새 데이터 종류가 늘면 테이블·열을 늘려야 한다 행만 늘어난다 행만 늘어난다
데이터 타입 열마다 고정 안 가린다 속성 정의 시 타입을 같이 정하고, 타입마다 테이블을 따로 둔다
값 칸의 형태 열의 타입 그대로 대개 문자열 하나로 뭉뚱그린다 강한 타이핑. 다만 실제 전자 환자 기록 시스템은 반대로 문자열로 뭉뚱그리기도 한다

이 표현은 희소 행렬을 공간 효율로 저장하는 방법과 닮았습니다. 비어 있지 않은 값만 저장한다는 점에서 그렇습니다. 그래서 EAV 테이블은 흔히 long and skinny 라고 묘사됩니다. long 은 행 수를, skinny 는 열이 몇 개 없다는 것을 가리킵니다.

EAV 는 우선 데이터베이스의 물리 스키마를 단순하게 만드는 수단입니다. 단순화가 이득이 될 때 쓰라고 논문은 적습니다. 물리 저장이 어떻게 되어 있든 사용자는 데이터를 관습적으로 구조화된 것으로, 곧 테이블과 열로 나뉜 것으로 자연스럽게 받아들입니다. 화면에 그려 보이거나 데이터를 분석하는 외부 프로그램도 마찬가지로 속성마다 열 하나인 데이터를 기대합니다. 사용자와 그런 프로그램이 기대하는 이 모양, 곧 데이터가 실제로 어떻게 저장됐는지와 무관하게 사람이 인식하는 구조가 논리 스키마입니다.

끌리는 이유

속성이 하나 늘 때마다 테이블을 고쳐야 합니다. Mark Wong 은 EAV 로 옮기기 전의 불편을 한 줄로 적었습니다. 속성이 추가될 때마다 ALTER TABLE 이 유발된다는 것입니다. 회원 정보에 EAV 를 적용하며 얻기로 한 것도 같은 자리였습니다. 회원 속성을 넣거나 뺄 때 비싼 ALTER TABLE 을 피하는 것입니다.

Dinu 와 Nadkarni 는 같은 대비를 표 하나로 보였습니다. Id 가 3 인 환자에게 Test3 라는 새 속성을 더한다고 합시다. 관습적 설계 테이블에서는 새 열을 더해야 하고, 그것은 데이터베이스 스키마를 고치는 일입니다. EAV 모델에서는 행을 하나 새로 만들면 끝납니다.

새 속성을 더할 때 값이 없는 속성
관습적 설계 열을 더한다. 스키마를 고친다 값 없음 항목을 만들어 둔다
EAV 행을 하나 넣는다 아무 행도 만들지 않는다

Nadkarni 외는 EAV 설계의 이점을 넷으로 정리했습니다.

유연성입니다. 엔티티당 속성 수에 임의의 제한이 없습니다. 스키마를 다시 설계하지 않고도 파라미터 수가 데이터베이스와 함께 늘 수 있습니다. 전자 환자 기록 시스템에서 이것이 중요하다고 적습니다. 모든 임상 전문 분야를 통틀면 한 환자에게 수천 개의 파라미터가 적용될 수 있기 때문입니다. 관습적 설계라면 그 정보를 계속 늘어나는 테이블 목록에 나눠 담아야 합니다. 벤더가 정한 테이블당 열 수 제한이 있어서입니다.

희소한 데이터를 공간 효율로 저장합니다. 전자 환자 기록 시스템에서 수천 개 파라미터가 적용 가능해도 전형적인 환자에게 실제로 기록되는 것은 수십 개뿐입니다. EAV 설계에서는 값이 널인 속성을 위해 공간을 예약할 필요가 없습니다.

부분적으로 자기 서술적인(값마다 속성 이름이 같은 행에 함께 실려 있어 별도 스키마 없이도 그 값이 무엇에 대한 것인지 알 수 있는) 단순한 물리 데이터 형식입니다. 쿠키와 Windows 레지스트리에서 이것이 중요하다고 적습니다.

객체 단위 질의(웹 브라우저로 조회할 때처럼 한 번에 여럿을 훑지 않고 객체 하나씩 조회하는 질의)입니다. 아주 복잡한 논리 스키마를 상대로 하는 객체 단위 질의는 관습적 설계보다 EAV 로 구현하기가 훨씬 수월합니다.

같은 논문은 EAV 설계가 스키마가 복잡하고 계속 바뀌는 데이터베이스에 잠재적으로 매력적이라고 적습니다. 빠르게 발전하는 과학 영역의 지식을 반영하느라 스키마가 계속 도는 자리입니다. 관습적 설계를 쓰면 스키마를 고칠 때마다 테이블과 그것을 다루는 코드와 사용자 인터페이스까지 계속 다시 설계해야 합니다. EAV 설계는 스키마를 단순하게 만들어서 그런 변경의 여파로부터 상대적인 절연을 제공할 수도 있다고 적습니다.

어디까지 잘 도는지도 같은 논문에 있습니다. 웹 브라우저를 통한 조회처럼 한 번에 한 객체를 꺼내는 경우에는 데이터 양이 충분히 적어서 차이가 눈에 띄지 않는다고 적습니다.

Bill Karwin 이 2013년 발표 소개문에 적은 요구사항도 같은 종류입니다. 사용자가 필요할 때 새 필드를 선언할 수 있는 데이터베이스. 제품마다 속성이 제각각인 전자상거래 카탈로그. 커스텀 데이터를 위한 확장을 지원하는 콘텐츠 관리 플랫폼. 사용자 커스터마이징을 받아내는 확장 가능하고 유연한 스키마를 설계하는 일은 흔한 요구사항이라고 그는 적었습니다.

예시

EntityAttributeValue

Karwin 이 EAV FAIL 에 실은 최소 형태입니다.

SQL
CREATE TABLE EntityAttributeValue (
entity VARCHAR(20) NOT NULL,
attribute VARCHAR(20) NOT NULL,
value VARCHAR(1000) NOT NULL,
PRIMARY KEY (entity, attribute)
);
INSERT INTO EntityAttributeValue (entity, attribute, value)
VALUES
('New Cuyama', 'Population', '562'),
('New Cuyama', 'Ft. above sea level', '2150'),
('New Cuyama', 'Established', '1951');

SELECT SUM(value) FROM EntityAttributeValue
WHERE entity = 'New Cuyama';

세 칸이 전부입니다. entity 는 무엇에 대한 이야기인지, attribute 는 속성 이름, value 는 값입니다. 기본 키는 entity 와 attribute 두 칸입니다. 값 칸은 VARCHAR(1000) 하나로 모든 속성의 값을 받습니다.

마지막 조회가 이 설계의 성질을 드러냅니다. 인구 562, 해발 2150피트, 설립 연도 1951 을 SUM() 으로 더합니다. 세 값이 마침 숫자라고 해서 그것들을 SUM() 으로 합치는 것이 뜻이 되지는 않습니다. 그런데 서로 다른 속성이 같은 열에 저장되어 있으면 그것들을 그런 식으로 호환되는 것처럼 다루고 싶어집니다. Karwin 은 정규화된 데이터베이스의 중요한 성질을 이 반례로 보인다고 적었습니다. 속성의 논리적 타입마다 별도의 열에 속한다는 성질입니다.

같은 글이 대조로 올린 것이 이쪽입니다.

SQL
CREATE TABLE Cities (
  city_id SERIAL PRIMARY KEY,
  city_name VARCHAR(100) NOT NULL,
  population INT UNSIGNED NOT NULL,
  feet_altitude SMALLINT UNSIGNED NOT NULL,
  year_established SMALLINT UNSIGNED NOT NULL
);

속성마다 열이 따로 있고 열마다 타입이 다릅니다. 속성을 가리키는 것은 문자열이 아니라 열 이름입니다. 데이터베이스를 설계하는 제대로 된 방법은 서로 다른 속성을 서로 다른 열에 두는 것이라고 Karwin 은 적었습니다.

member · field · member_field

실제 서비스에 적용된 형태입니다. Mark Wong 은 PGConf NYC 2014 발표에서 몇 해 전에 회원 정보에 EAV 데이터 모델을 적용하는 대규모 스키마 리팩터링을 했다고 밝혔습니다. 모델은 세 테이블로 이뤄집니다.

자리 테이블 담는 것
엔티티 member 모든 회원이 반드시 가져야 하는 속성. 이메일 주소 같은 것
속성 field 사용자가 정의하는 커스텀 속성. 선호하는 데이터베이스 관리 시스템 같은 것
값 member_field field 테이블에 정의된 커스텀 속성의 값

세 테이블이 물리는 방식은 앞서 본 EAV 세 칸 그대로입니다. member_field 한 행이 어느 회원의 어느 속성 값인지를 가리키려면 member 와 field 양쪽을 참조해야 합니다.

erDiagram
    member ||--o{ member_field : "이 회원의 값들"
    field ||--o{ member_field : "이 속성으로 정의된 값들"
    member {
        string email "모든 회원이 반드시 갖는 속성"
    }
    field {
        string name "사용자가 정의하는 커스텀 속성"
    }
    member_field {
        string value "field 에 정의된 커스텀 속성의 값"
    }

같은 발표는 EAV 로 가기 전에 알고 있던 대가도 적어 뒀습니다. 데이터를 다르게 질의해야 한다는 것, 그리고 데이터 타입 검사를 테이블 여러 개로 하든 열 여러 개로 하든 따로 해야 한다는 것입니다. 이 사례는 후자를 골랐습니다.

경계

클래스란 관습적 설계에서 테이블 하나에 대응하는, 엔티티가 속하는 종류입니다. 자동차 데이터베이스라면 차대번호·브랜드·색상·엔진 종류를 열로 갖는 Cars 테이블이 자동차라는 클래스이고, 그 테이블의 행 하나하나가 자동차 한 대라는 엔티티입니다.

클래스 한둘의 속성 목록이 유동적이면 EAV 인가. 아닙니다. 하이브리드 성격을 띠는 클래스란 속성 일부는 관습적으로 열에 담기고 나머지 일부만 희소하거나 휘발성(목록이 바뀌기 쉬운 성질)이라 행으로 담아야 하는 클래스를 말합니다. Dinu 와 Nadkarni 는 시스템에서 그런 클래스가 한두 개뿐이라면 EAV 설계가 값을 못 할 수 있다고 적습니다. 그 자리에서는 특수 목적의 행 모델링(클래스 하나의 속성을 열이 아니라 행으로 담는 것) 테이블 하나를 주 클래스 테이블에 다대일로 붙이게 됩니다. 여러 클래스가 하이브리드 성격을 보일 때라야 EAV 설계가 그런 특수 목적 테이블의 증식을 막습니다.

행 모델링을 고르는 기준도 같은 논문에 있습니다. 행 모델링은 희소성과 휘발성이라는 쌍둥이 문제를 다룹니다. 전형적인 고객은 언제든 살 수 있는 상품 중 아주 적은 비율만 삽니다. 그리고 상품 목록은 바뀌기 쉽습니다. 둘 중 하나라도 있으면 행 모델링을 써야 하고, 둘 다 없으면 부적절합니다. 그때는 관습적 설계를 씁니다. EAV 테이블 설계는 그 행 모델링을 일반화한 것입니다. 클래스 하나가 아니라 희소성과 휘발성의 영향을 받는 모든 사실을 데이터베이스 전체에 걸쳐 하나의 테이블 또는 한 벌의 테이블에 저장합니다.

두 갈림길을 이으면 이렇습니다.

flowchart TD
    A["클래스의 속성이 희소하거나 휘발성인가"] -->|"둘 다 없음"| B["관습적 설계"]
    A -->|"하나라도 있음"| C["행 모델링이 필요한 클래스"]
    C --> D["하이브리드 클래스가 몇 개인가"]
    D -->|"한둘"| E["특수 목적 행 모델링 테이블<br/>주 클래스 테이블에 다대일로 붙임"]
    D -->|"여럿"| F["EAV 설계<br/>특수 목적 테이블의 증식을 막음"]

Nadkarni 외는 실서비스 EAV 데이터베이스 대부분이 말이 되는 자리에서는 관습적 설계도 같이 쓴다고 적습니다. 스키마가 이질적이라는 뜻입니다. 그래서 주어진 데이터 종류마다 관습적 설계와 EAV 표현 중 무엇을 고를지 아는 것이 중요하다고 적습니다. 스키마가 비교적 정적이거나 단순한 경우, 예컨대 재고나 회계 같은 업무 애플리케이션 데이터베이스에서는 EAV 설계의 오버헤드가 그 이점을 넘습니다. 위키피디아도 같은 선을 긋습니다. 관습적 설계가 다루기 힘들다고 미리 알려진 하위 시스템에만 적용하라는 것입니다. 임상 데이터 영역이 그런 자리입니다. 아니면 시스템이 자라는 동안 상당한 유지보수 문제를 일으킨다는 것이 드러난 하위 시스템입니다.

한 열에 담는 대안

PostgreSQL 의 hstore 모듈은 키/값 쌍의 집합을 하나의 PostgreSQL 값 안에 저장하는 hstore 데이터 타입을 구현합니다. 공식 문서는 쓸모 있는 상황을 이렇게 적습니다. 속성이 많지만 거의 들여다보지 않는 행, 또는 반정형 데이터입니다. 키와 값은 단순히 텍스트 문자열입니다. hstore 는 @> ? ?& ?| 연산자에 대해 GiST(Generalized Search Tree, 일반화 검색 트리) 와 GIN(Generalized Inverted Index, 일반화된 역색인) 인덱스를 지원합니다.

SQL
CREATE INDEX hidx ON testhstore USING GIST (h);

CREATE INDEX hidx ON testhstore USING GIN (h);

jsonb 쪽도 같은 자리에 섭니다. GIN 인덱스는 많은 수의 jsonb 문서 안에 나타나는 키나 키/값 쌍을 효율적으로 찾는 데 쓸 수 있습니다.

SQL
CREATE INDEX idxgin ON api USING GIN (jdoc);

위키피디아는 PostgreSQL 9.4 부터 들어온 JSONB 컬럼이 질의와 인덱싱과 조인이 가능하며, 전통적인 EAV 테이블 설계 대비 천 배 이상의 성능 개선을 가능하게 한다고 적습니다.

옮겨도 남는 것이 있습니다. Mark Wong 은 hstore 쪽 대가를 셋으로 적었습니다. 엄격한 타입이 없어 전부 문자열입니다. 참조 무결성 제약이 없어서 hstore 의 키와 테이블 열 사이에 외래 키를 만들 수 없습니다. 상위 레벨 데이터베이스 연결 라이브러리에서 네이티브 지원은 제각각일 수 있습니다.

서브타입으로 나누는 대안

Karwin 이 든 대안은 이름만 걸어 둡니다. 책의 해법 절은 서브타입을 모델링하는 것이고, 2013년 발표에서 개발 생산성과 데이터 무결성과 저장 효율과 질의 성능과 확장 용이성을 놓고 견준 대안은 Class Table Inheritance, Serialized BLOB, Inverted Indexing 셋입니다.

실패

값 칸 하나가 모든 타입을 받는 데서 시작합니다.

행 모델링 테이블에서 값 열의 데이터 타입은 그 열이 기록하는 사실의 성질로 미리 정해집니다. EAV 테이블은 다릅니다. 특정 행에 있는 값의 개념적 데이터 타입이 그 행의 속성에 달려 있습니다. 그래서 실서비스 시스템에서 EAV 테이블에 데이터를 직접 입력하게 허용하는 것은 재앙을 부르는 방법이라고 위키피디아는 적습니다. 데이터베이스 엔진 자체가 견고한 입력 검증을 수행할 수 없기 때문입니다.

모든 값을 문자열로 밀어 넣으면 단순하지만 확장되지 않는 구조가 됩니다. 값으로 무언가를 하려면 데이터 타입 상호 변환이 끊임없이 필요합니다. 그리고 EAV 테이블의 값 열에 건 인덱스는 사실상 쓸모가 없습니다. Dinu 와 Nadkarni 도 같은 자리를 짚습니다. 역사적으로 HELP 시스템(Homer Warner 그룹이 만든 임상 데이터 저장소) 초기판이 그랬듯 EAV 값을 전부 최소공통분모 데이터 타입인 문자열로 저장하면 값에 효과적인 인덱싱을 할 수 없습니다. 본질적으로 숫자나 날짜인 데이터를 문자열로 저장한 인덱스는 최적화된 범위 검색을 허용하지 않고, 숫자에 비교 연산자를 지정한 질의는 데이터를 즉석에서 숫자로 변환해야 하기 때문입니다.

제약도 못 겁니다. 제약은 보통 테이블의 개별 열을 기준으로 정의됩니다. 속성마다 열 하나인 관습적 설계에서는 그것이 잘 동작합니다. EAV 테이블에서는 그렇지 않습니다. 같은 열이 특성이 서로 다른 수천 개의 속성을 받고, 새 속성이 계속 시스템에 추가됩니다. 그래서 메타데이터로 정의한 제약을 트리거로 해석하는 것만이 유일하게 실행 가능한 대안이 됩니다. 위키피디아는 이것을 맞바꿈으로 정리합니다. EAV 시스템은 데이터의 물리 구조와 논리 구조의 단순함을 메타데이터의 복잡함과 맞바꿉니다. 그 메타데이터가 관습적 설계에서 데이터베이스 제약과 참조 무결성이 하던 역할을 맡습니다.

정규형도 못 지킵니다. Karwin 은 EAV FAIL 의 댓글에서 근거를 적었습니다. 1NF(First Normal Form, 제1정규형)의 간략한 정의는 테이블이 관계를 충실히 표현하고 반복 그룹이 없다는 것입니다. 충실히 표현한다는 기준 하나는 각 열과 행의 교차점이 적절한 타입의 값을 정확히 하나 갖는다는 것입니다. EAV 테이블에서는 그 적절한 타입이 속성 칸(Karwin 은 이 댓글에서 그 칸을 attribute_id 라고 부르는데, 위 예시의 attribute 와 같은 칸입니다)에 무엇이 들었는지에 따라 행마다 달라집니다. 큰 varchar 를 일종의 범용 타입으로 쓸 수는 있지만 허용되는 값의 도메인은 속성마다 다릅니다. 제대로 된 관계에서는 속성이 모든 행에 적용되는 하나의 타입 또는 도메인을 가져야 합니다. 그래서 EAV 테이블은 제대로 된 관계가 아니고 1NF 의 기준을 만족하지 못합니다. 값의 의미도 그 행이 어떤 속성을 나타내는가에 따라 달라집니다. 1968 은 인구일 때와 고도일 때와 설립 연도일 때 각각 다른 것을 뜻합니다. 속성 칸은 EAV 테이블 키의 일부이지 키 전체가 아닙니다. 그래서 2NF(Second Normal Form, 제2정규형)도 위반합니다.

조회는 조인 수로 값을 치릅니다. 피벗(속성마다 흩어진 값을 한 행으로 모아 여러 열짜리 출력으로 바꾸는 것)에서 데이터베이스가 실제로 하는 연산은 연속된 full outer join 입니다. 속성마다 나온 데이터 조각은 열 하나 폭이면서 행 수는 제각각인데, 이런 조각들을 나란히 조인해 여러 열짜리 출력 하나를 만듭니다.

flowchart TD
    A["속성 A 조각<br/>행 수 제각각"] --> J1(("full outer join"))
    B["속성 B 조각<br/>행 수 제각각"] --> J1
    J1 --> M["중간 결과"]
    M --> J2(("full outer join"))
    C["속성 C 조각<br/>행 수 제각각"] --> J2
    J2 --> R["여러 열짜리 출력"]

full outer join 은 개별 환자의 임상 이벤트에서 속성 값 하나 이상이 빠져 있는 상황을 받아냅니다. 대부분의 고급 데이터베이스 벤더가 SQL(Structured Query Language, 구조화 질의 언어) 의 FULL OUTER JOIN 문을 지원하지만, 실제로는 같은 결과를 내면서 잠재적으로 더 효율적인 여러 대안이 쓰일 수 있습니다.

수치도 있습니다. Dinu 와 Nadkarni 의 벤치마킹 연구에서 속성 중심의 임시 질의(ad hoc query, 미리 정해 두지 않고 필요할 때 즉석에서 짜는 질의)는 EAV 시스템에서 관습적 설계보다 3배에서 12배 느리게 돌았습니다. 느려지는 정도는 질의 복잡도의 함수였고, 여기서 복잡도는 결합한 속성의 수입니다. 그 자리에서 따를 수 있는 전략은 둘입니다. 하나는 단순한 SQL 문을 여럿 만들어 임시 테이블을 만들고, 집합 교집합이 필요한 질의라면 행 수가 가장 적은 테이블부터 조인하는 휴리스틱으로 합친 뒤 최종 결과 테이블이 나오면 지우는 것입니다. 다른 하나는 원하는 데이터 전부를 한 번에 렌더링하려는 거대한 SQL 문 하나를 만드는 것입니다. 같은 연구에서 첫 번째 접근이 두 번째를 앞질렀습니다. DBMS 엔진이 발전했음에도 조인이 많이 들어간 문장은 질의 옵티마이저에 불필요한 일을 만들 수 있기 때문입니다.

한 번에 한 객체만 꺼낼 때는 차이가 안 보이지만, 한 번에 여러 객체를 대량으로 꺼내는 일괄 조회에서는 관습적 설계보다 덜 효율적입니다. Mark Wong 도 같은 발표에서 EAV 를 실제로 써 본 뒤 겪은 것을 두 줄로 적었습니다. EAV 모델에서 데이터를 꺼내는 일 자체가 비효율적이고, 수백만 행을 피벗하는 자리부터 성능 문제가 시작된다는 것입니다.

언제부터 못 견디는지를 모으면 이렇습니다.

조건 무슨 일이 나나
값을 전부 문자열로 담았다 값 열의 인덱스가 사실상 쓸모없어진다. 범위 검색이 최적화되지 않는다
속성마다 다른 제약을 걸어야 한다 열 단위 제약을 못 건다. 메타데이터를 트리거로 해석하는 길만 남는다
속성 여러 개를 한 행으로 되돌려야 한다 피벗이 연속된 full outer join 이 된다
속성 여러 개를 조합한 조건으로 찾아야 한다 관습적 설계 대비 3배에서 12배 느리게 돈다
한 번에 여러 객체를 대량으로 꺼낸다 관습적 설계보다 덜 효율적이다
수백만 행을 피벗한다 성능 문제가 시작된다

값을 치르는 자리가 하나 더 있습니다. 관습적 설계가 자동으로 해주는 많은 일을 하려면 상당한 사전 프로그래밍이 필요하다고 Nadkarni 외는 적습니다. 다만 그런 프로그래밍은 한 번만 하면 되고, 범용 EAV 도구를 쓸 수 있게 되면 이 한계가 없어질 수도 있다고 덧붙입니다.

Karwin 도 EAV 로 돌아가는 시스템을 만드는 것이 가능하다는 점은 인정했습니다. 다만 모르타르 없이 벽돌집을 짓는 것과 같다고 적었습니다. 충분히 조심하면 세워 둘 수는 있지만 거기에 기대지는 말라는 것입니다.

여담

EAV 라는 이름은 안티패턴으로 붙은 것이 아닙니다. 임상 데이터베이스 쪽에서 왔습니다. 일반적인 지식 표현 수단으로서의 EAV 는 속성-값 쌍 개념에서 비롯됐고, 그것이 처음 도입된 것은 LISP 에서였다고 위키피디아는 적습니다. EAV 를 채용한 첫 의무기록 시스템 셋은 Clement MacDonald 가 이끈 Regenstrief 전자 의무기록, William Stead 와 Ed Hammond 의 TMR, Homer Warner 그룹이 LDS 병원에서 만든 HELP 인데, 셋 다 1970년대에 E.F. Codd 의 관계형 데이터베이스 모델에 기반한 상용 시스템이 나오기 전에 개발됐습니다. 이 이름에 안티패턴이라는 이름표를 붙인 Bill Karwin 은 2013년 Percona Live MySQL Conference & Expo 발표에서 EAV 를 Inner-Platform Effect 라는 안티패턴의 한 사례로도 분류했습니다. 이미 열과 데이터 타입과 제약으로 속성을 제공하고 있는 RDBMS(Relational Database Management System, 관계형 데이터베이스 관리 시스템) 아키텍처 위에 속성 관리 시스템을 다시 모델링하는 것이라는 뜻입니다.

관련 항목

갈래의 이웃

안티패턴 · 패턴 · 문제 · 오해통념

이것이 딸린 데이터베이스 범주

데이터베이스 · 관계형 데이터베이스 · RDBMS · 스키마 · 정규화

정합성과 조회 성능이 걸리는 개념

1NF · 2NF · 참조 무결성 · 외래 키 · 인덱스 · 트리거 · 피벗 · full outer join · 질의 옵티마이저 · 조인

대안

hstore · JSONB · GiST · GIN · Class Table Inheritance · Serialized BLOB · Inverted Indexing

사례에서 언급하는 언어와 제품

SQL · MySQL · PostgreSQL · LISP

여러 절에서 같이 쓰이는 개념

반정형 데이터 · 메타데이터 · 타임스탬프 · 쿠키 · 희소 행렬 · 검색 · 성능 · 개발

다른 이름: entity-attribute-value · 엔티티-속성-값 · EAV/CR · open schema