엔티티-속성-값 스키마의 매혹적인 함정
Takashi Yamamoto
Infrastructure Engineer · Leapcell

소개
끊임없이 발전하는 소프트웨어 개발 환경에서 유연하고 적응 가능한 데이터 모델의 필요성은 매우 중요합니다. 우리는 종종 데이터 구조를 미리 완전히 알지 못하거나 새 속성을 자주 추가해야 하는 시나리오에 직면하게 되는데, 이는 기존의 관계형 스키마 디자인이 경직되어 느껴지게 만듭니다.
이러한 유연성에 대한 추구는 때때로 개발자를 겉보기에 유망한 경로로 이끌기도 합니다. 바로 엔티티-속성-값(EAV) 스키마입니다. 겉보기에는 EAV가 동적 데이터에 대한 만능 해결책처럼 보이며, 스키마 변경 없이 무한한 확장성을 약속합니다. 그러나 이러한 초기 매력은 종종 우아해 보이는 솔루션을 데이터베이스 설계 악몽으로 만들 수 있는 수많은 복잡성과 성능 병목 현상을 가립니다. 이 글은 EAV 모델을 깊이 파고들어, 겉보기의 유연성이 왜 그렇게 기만적일 수 있는지 탐구하고, 그 구현과 관련된 종종 숨겨진 비용을 밝힙니다.
EAV의 매력과 심연
문제를 파헤치기 전에 EAV 스키마가 무엇을 포함하는지에 대한 명확한 이해를 확립해 봅시다.
핵심 용어
- 엔티티 (E): 시스템의 주요 개체 또는 레코드를 나타냅니다. 예를 들어, "제품" 또는 "사용자"입니다. 관계형 테이블에서 엔티티는 일반적으로 Entities 테이블의 행에 해당합니다.
- 속성 (A): 엔티티와 관련된 특성 또는 속성입니다. 예를 들어, 제품의 "색상", "크기", "무게"입니다. EAV 모델에서 속성은 종종 Attributes 테이블의 행이나 단순히 텍스트 값으로 저장됩니다.
- 값 (V): 특정 엔티티에 대한 특정 속성과 관련된 실제 데이터입니다. 예를 들어, "제품" 엔티티의 "색상" 속성에 대한 "빨간색"입니다. 값은 엔티티와 속성 모두에 연결된 Values 테이블에 저장됩니다.
EAV 작동 방식
전통적인 관계형 데이터베이스에서 엔티티의 각 속성은 일반적으로 테이블의 열이 됩니다.
전통적 스키마 (예: Products 테이블):
product_id | name | price | color | weight_kg |
|---|---|---|---|---|
| 1 | Laptop X | 1200.00 | Silver | 2.5 |
| 2 | Mouse Y | 25.00 | Black | 0.1 |
EAV 스키마에서는 이 구조가 일련의 테이블로 분해되며, 일반적으로 다음을 포함합니다.
Entities테이블: 기본 엔티티 정보 (예:product_id,product_name)를 저장합니다.Attributes테이블: 가능한 속성을 정의합니다 (예:attribute_id,attribute_name,data_type).Values테이블 (또는EntityAttributeValue테이블): 엔티티, 속성 및 해당 값을 연결합니다. 이 테이블이 EAV의 핵심입니다.
EAV 스키마 예:
Products 테이블 ( 엔티티):
product_id | name |
|---|---|
| 1 | Laptop X |
| 2 | Mouse Y |
Attributes 테이블:
attribute_id | attribute_name | data_type |
|---|---|---|
| 101 | price | DECIMAL |
| 102 | color | VARCHAR |
| 103 | weight_kg | DECIMAL |
Product_Attribute_Values 테이블 (값):
product_id | attribute_id | value_text | value_decimal |
|---|---|---|---|
| 1 | 101 | NULL | 1200.00 |
| 1 | 102 | Silver | NULL |
| 1 | 103 | NULL | 2.5 |
| 2 | 101 | NULL | 25.00 |
| 2 | 102 | Black | NULL |
| 2 | 103 | NULL | 0.1 |
value_text 및 value_decimal 열을 주목하세요. 일반적인 EAV 패턴은 여러 value_TYPE 열 (예: value_int, value_date)을 사용하여 다른 데이터 유형을 저장합니다. 단일 value 열은 유형 강제 변환을 강제하거나 유형 무결성을 희생하게 되기 때문입니다.
문제 현실
EAV 모델은 매우 유연해 보이지만, 구현 시 몇 가지 심각한 문제가 빠르게 드러납니다.
-
데이터 유형 관리 및 무결성: 관계형 스키마에서 각 열은 데이터베이스에서 시행되는 정의된 데이터 유형을 가집니다. EAV에서는 값이 종종 일반 문자열 (
value_text)로 저장되거나 여러 유형별 열에 저장되어 다음과 같은 결과를 초래합니다.- 데이터베이스 수준 유형 강제 적용 손실: 모든 것에
value_text를 사용하면 데이터베이스는 "price"가 숫자이고 "color"가 텍스트임을 강제할 수 없어 데이터 유효성 검사 로직을 애플리케이션 계층으로 옮겨야 합니다. - 유형 변환을 위한 복잡한 쿼리: 여러
value_TYPE열을 사용하는 경우, 올바른 값을 검색하기 위해CASE문이나COALESCE함수 (예:COALESCE(value_text, CAST(value_decimal AS VARCHAR)))를 사용하여 특정 속성을 쿼리해야 하므로 쿼리가 복잡해집니다.
예시 (가격 검색):
-- EAV 쿼리로 제품 가격 가져오기 SELECT p.name, pav.value_decimal AS price FROM Products p JOIN Product_Attribute_Values pav ON p.product_id = pav.product_id JOIN Attributes a ON pav.attribute_id = a.attribute_id WHERE a.attribute_name = 'price' AND p.product_id = 1;기존 스키마의 간단한
SELECT name, price FROM Products WHERE product_id = 1;과 비교해 보세요. - 데이터베이스 수준 유형 강제 적용 손실: 모든 것에
-
참조 무결성 및 제약 조건: "color" 속성이 특정 사전 정의된 값 (예: 'Red', 'Green', 'Blue')만 가질 수 있도록 어떻게 강제할 수 있습니까? EAV에서는 이것이 극도로 어려워집니다.
value열에 대한 외래 키는 비현실적이므로, 고유성, null 허용 여부, 외래 키 관계와 같은 일반적인 관계형 데이터베이스 제약 조건은 불가능하거나 애플리케이션 계층에서 수고스럽게 강제해야 합니다. 이는 데이터 불일치의 위험을 크게 증가시킵니다. -
쿼리 및 성능: 모든 속성을 가진 단일 엔티티를 검색하려면 잠재적으로 각 속성마다 여러 개의
JOIN작업이 필요한데, 이는 매우 비효율적일 수 있습니다. 속성으로 필터링하거나 정렬해야 하는 경우 복잡성이 증가합니다.예시 (모든 속성 검색):
-- EAV 쿼리로 모든 속성을 가진 제품 가져오기 (피벗) SELECT p.name, MAX(CASE WHEN a.attribute_name = 'price' THEN pav.value_decimal END) AS price, MAX(CASE WHEN a.attribute_name = 'color' THEN pav.value_text END) AS color, MAX(CASE WHEN a.attribute_name = 'weight_kg' THEN pav.value_decimal END) AS weight_kg FROM Products p JOIN Product_Attribute_Values pav ON p.product_id = pav.product_id JOIN Attributes a ON pav.attribute_id = a.attribute_id WHERE p.product_id = 1 GROUP BY p.product_id, p.name;이는 여러 조인 및 집계 (암시적 피벗)를 포함합니다. 속성 또는 엔티티 수가 많을수록 이는 주요 성능 병목 현상이 됩니다.
attribute_id및product_id의 인덱스는 중요하지만, 오버헤드를 어느 정도 완화할 수 있을 뿐입니다. -
보고 및 분석: EAV 모델에서는 속성 전체에 걸쳐 데이터를 집계하는 복잡한 분석 쿼리가 매우 어렵습니다. 가격 합산, 무게 평균, 특정 색상 항목 수 계산이 필요한 보고서를 생성하는 것은 동적 SQL이나 최적화가 매우 어려운 중첩된 하위 쿼리를 자주 요구하는 번거로운 작업이 됩니다.
-
스키마 진화 대 데이터 모델 경직성: EAV는
Attributes테이블에 행을 추가하기만 하면 "유연한 스키마 진화"를 약속하지만, 이는 다른 종류의 경직성을 도입합니다. 바로 데이터를 쿼리하고 상호 작용하는 스키마입니다. 새로운 속성이 추가될 때마다 데이터를 구조적인 열 중심 방식으로 표시하려면 애플리케이션 코드, 보고서 쿼리 또는 뷰 정의를 수정해야 하는 경우가 많습니다. "스키마"는 데이터베이스 정의 언어 (DDL)에서 애플리케이션의 쿼리 로직으로 이동합니다.
EAV를 고려할 수 있는 경우 (및 대안)
단점에도 불구하고, EAV (또는 유사한 희소 데이터 모델)를 극도의 주의를 기울여 고려할 수 있는 틈새 시나리오가 있습니다.
- 진정으로 희소하고 예측 불가능한 데이터: 엔티티가 수백 또는 수천 개의 잠재적인 속성을 가질 수 있지만, 특정 엔티티에는 몇 개만 정의되어 있고 속성 세트가 끊임없이 변경되는 경우 (예: 의료 진단, 연구 실험).
- 외부 통합 (CMS/전자상거래 사용자 정의 필드): 많은 콘텐츠 관리 시스템 (CMS) 및 전자상거래 플랫폼은 사용자 정의 필드를 위해 EAV와 유사한 구조를 사용합니다. 이는 일반적으로 사용자가 개발자 개입 없이 새 속성을 정의할 필요성 때문입니다. 이러한 경우 플랫폼이 복잡성을 처리합니다.
고려할 대안:
-
JSON/JSONB 열 (NoSQL 하이브리드): 최신 관계형 데이터베이스 (PostgreSQL, MySQL 5.7+, SQL Server 2016+)는 기본 JSON 데이터 유형을 제공합니다. 이는 반구조화된 데이터에 대해 종종 더 나은 접근 방식이며, 핵심 엔티티를 관계형 구조로 유지하면서 단일 열 내에 동적 속성을 저장할 수 있습니다.
예시 (JSONB 사용):
-- 동적 속성을 위해 JSONB가 있는 Products 테이블 CREATE TABLE Products ( product_id SERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL, base_price DECIMAL(10, 2), properties JSONB ); INSERT INTO Products (name, base_price, properties) VALUES ('Laptop X', 1200.00, '{"color": "Silver", "weight_kg": 2.5, "brand": "TechCo"}'), ('Mouse Y', 25.00, '{"color": "Black", "weight_kg": 0.1, "DPI": 1600}'); -- JSONB 속성 쿼리 SELECT name, base_price, properties->>'color' AS color, -- ->> 텍스트 추출 (properties->'weight_kg')::DECIMAL AS weight_kg -- -> JSON 추출, 유형으로 캐스팅 FROM Products WHERE (properties->>'color') = 'Black';JSONB는 동적 필드의 경우 기본 EAV보다 더 나은 인덱싱 기능과 쿼리 성능을 제공하며, 표준 열에 주요 고정 속성을 유지합니다.
-
잘 설계된 관계형 스키마와 확장 테이블: 속성 그룹이 동적이지만 완전히 임의적이지 않은 시나리오의 경우, 외래 키로 연결된 특수 "확장" 테이블을 사용할 수 있습니다. 예를 들어,
Products,Product_Specifications,Product_Variants가 있습니다. 이는 강력한 유형과 참조 무결성을 유지합니다. -
스키마 마이그레이션 도구: 스키마 마이그레이션 도구 (예: Flyway, Liquibase, ActiveRecord Migrations)의 힘을 받아들이십시오. 전통적인 스키마에 열을 추가하는 것은 대부분의 최신 데이터베이스에서 잘 이해되고 종종 비파괴적인 작업입니다. 특히 온라인 DDL 변경의 경우, 마이그레이션 실행의 "오버헤드"는 EAV의 지속적인 성능 및 유지 관리 비용보다 훨씬 적은 경우가 많습니다.
결론
엔티티-속성-값 (EAV) 스키마는 타의 추종을 불허하는 유연성을 제공하는 것처럼 보이지만, 초기 매력이 심각한 복잡성으로 빠르게 바뀌는 디자인 패턴의 고전적인 예입니다. 데이터 유형 관리, 무결성 강제 적용, 효율적인 쿼리 수행 및 보고서 생성의 고유한 어려움은 종종 유망한 솔루션을 지속적인 데이터베이스 디자인 악몽으로 변모시킵니다. 그 사용이 정당화될 수 있는 틈새 사례가 있지만, JSON 네이티브 유형 또는 신중하게 설계된 확장 테이블과 같은 최신 데이터베이스 기능은 훨씬 더 강력하고 성능이 뛰어난 대안을 제공하여 개발자가 관계형 데이터베이스의 기본 강점을 희생하지 않고 유연성을 달성할 수 있도록 합니다. EAV의 무한한 확장성의 기만적인 매력보다는 명확성, 무결성 및 성능을 선택하십시오.

