데이터 베이스/설계 2편, 실무에서 반드시 마주치는 9가지 설계 패턴

Ch07. 동적 설계 EAV - EAV의 장단점과 주의사항

webmaster 2026. 7. 27. 22:36

EAV의 장점

  • 스키마 유연성
    • 가장 큰 장점은 테이블 구조 변경 없이 새로운 속성을 추가할 수 있다는 것이다.
    • 운영 중인 시스템에서 "ALTER TABLE"을 실행하는 것은 위험하고 시간이 오래 걸릴 수 있다.
    • EAV는 이 문제를 완전히 해결한다.
  • 희소 데이터 효율성
    • 전통적인 테이블에서 100개의 속성 컬럼이 있는데, 각 상품이 평균 10개의 속성만 사용한다면?
      • 90개의 컬럼에는 NULL이 저장되어 공간이 낭비된다.
    • EAV는 실제 값이 있는 속성만 저장하므로 희소 데이터에 효율적이다.
  • 엔티티별 다른 속성 지원
    • 같은 카테고리라도 상품마다 다른 속성을 가질 수 있다.
    • 셀러가 원하는 속성을 자유롭게 추가할 수 있다.
  • 런타임 속성 정의
    • 관리자 화면에서 새로운 속성을 정의하고 바로 사용할 수 있다.
    • 개발자의 도움 없이 운영팀이 직접 속성을 관리할 수 있다.

EAV의 단점

쿼리 복잡성

여러 속성을 조회하거나 조건으로 검색하려면 복잡한 조인이나 서브쿼리가 필요하다.

-- 전통적 테이블: 간단한 쿼리
SELECT * FROM product_phone
WHERE screen_size >= '6.0' AND ram = '12GB';

-- EAV: 복잡한 쿼리
SELECT p.* FROM product p
WHERE p.product_id IN (
    SELECT product_id FROM product_attribute
    WHERE attr_name = 'screen_size' AND attr_value >= '6.0'
)
AND p.product_id IN (
    SELECT product_id FROM product_attribute
    WHERE attr_name = 'ram' AND attr_value = '12GB'
);

 

데이터 타입 제약 부족

-- 잘못된 데이터도 입력 가능
INSERT INTO product_attribute (product_id, attr_name, attr_value) VALUES 
(1, 'price_discount', '할인중'); -- 숫자여야 하는데 문자열
  • 모든 값이 VARCHAR로 저장되므로 데이터베이스 수준의 타입 검증이 불가능하다. 숫자여야 하는 값에 문자열이 들어갈 수 있다.
  • 애플리케이션 레벨에서 검증 로직을 구현해야 한다.

참조 무결성 제약 어려움

  • 외래 키 제약조건을 걸기 어렵다.
  • 예를 들어, 색상 값이 특정 색상 테이블을 참조해야 한다면 EAV에서는 이를 데이터베이스 수준에서 강제할 수 없다.

인덱싱 제한

  • 특정 속성에 대한 효율적인 인덱스 생성이 어렵다.
  • 전통적인 테이블에서는 "CREATE INDEX idx_ram ON product_phone(ram)"으로 RAM 컬럼에 인덱스를 생성할 수 있다.
  • EAV에서는 모든 속성이 같은 컬럼에 저장되므로 특정 속성만을 위한 인덱스를 만들기 어렵다.

조인 성능 저하

  • 속성이 많아질수록 쿼리나 다중 조인의 성능이 떨어진다.

실무 팁: 데이터 타입별 컬럼 분리

EAV 패턴의 가장 큰 약점인 데이터 타입 정합성 문제를 해결하기 위해, 실무에서는 값(Value)을 저장하는 컬럼을 데이터 타입별로 분리하기도 한다. 모든 값을 하나의 `VARCHAR` 컬럼에 억지로 넣는 대신, 문자형, 숫자형, 날짜형 등으로 컬럼을 나누어 설계하는 방식이다.

DROP TABLE IF EXISTS product_attribute_typed;
CREATE TABLE product_attribute_typed (
	attribute_id BIGINT NOT NULL AUTO_INCREMENT,
    product_id BIGINT NOT NULL,
    attr_name VARCHAR(100) NOT NULL,
    val_text VARCHAR(255) NULL,
    val_int INT NULL,
    val_date DATETIME NULL,
	PRIMARY KEY (attribute_id)	
)
  • EAV 패턴의 가장 큰 약점인 데이터 타입 정합성 문제를 해결하기 위해, 실무에서는 값(Value)을 저장하는 컬럼을 데이터 타입별로 분리하기도 한다. 
    • 모든 값을 하나의 VARCHAR 컬럼에 억지로 넣는 대신, 문자형, 숫자형, 날짜형 등으로 컬럼을 나누어 설계하는 방식이다.
  • 이 예제는 단순하게 설명하기 위해 "attr_def_id" 대신에 "attr_name"을 그대로 사용했다.

데이터 저장 예시

-- 화면 패널 종류 (문자열)
INSERT INTO product_attribute_typed (product_id, attr_name, val_text)
VALUES (1, 'panel_type', 'OLED');

-- 배터리 용량 (숫자)
INSERT INTO product_attribute_typed (product_id, attr_name, val_int)
VALUES (1, 'battery_capacity', 5000);

쿼리 결과

장점 

  • 데이터 타입 보장: 숫자 컬럼에 문자가 들어가는 것을 DB 차원에서 막을 수 있다.
  • 연산 및 정렬 효율성: 숫자는 숫자로, 날짜는 날짜로 저장되므로 "ORDER BY" 정렬이나 "BETWEEN" 같은 범위 검색 시 올바르게 작동하며 인덱스도 효율적으로 탈 수 있다.
    • 예를 들어, 문자열로 저장된 "10"과 "2"를 비교하면 "10"이 더 작다고 판단될 수 있지만, 숫자형 컬럼을 따로 쓰면 이런 문제를 피할 수 있다.

물론, 하나의 로우(Row)에 사용하지 않는 나머지 타입 컬럼들은 NULL로 채워지게 된다는 점은 감수해야 한다. 하지만 데이터 무결성이 중요한 커머스 환경에서는 매우 유용한 타협안이 될 수 있다.

 

EAV 사용 시 주의사항

핵심 속성은 EAV에 넣지 말 것

-- 좋은 예: 핵심 속성은 product 테이블에
CREATE TABLE product (
    product_id BIGINT PRIMARY KEY AUTO_INCREMENT,
    category_id BIGINT NOT NULL,
    name VARCHAR(200) NOT NULL,
    price INT NOT NULL, -- 핵심 속성
    brand VARCHAR(100), -- 핵심 속성
    status VARCHAR(20), -- 핵심 속성
    created_at DATETIME NOT NULL,
    FOREIGN KEY (category_id) REFERENCES category(category_id)
);

-- 부가 속성만 EAV로
CREATE TABLE product_attribute (
    attribute_id BIGINT PRIMARY KEY AUTO_INCREMENT,
    product_id BIGINT NOT NULL,
    attr_def_id BIGINT NOT NULL,
    attr_value VARCHAR(500),
    FOREIGN KEY (product_id) REFERENCES product(product_id)
);
  • 자주 조회되고, 검색 조건으로 사용되며, 데이터 무결성이 중요한 속성은 메인테이블의 컬럼으로 정의한다.
  • 핵심 속성은 반드시 메인테이블의 컬럼으로 사용해야 한다.
  • 부가 속성만 EAV를 사용한다.

하이브리드 접근

  • 완전한 EAV 보다는 "핵심 속성 + EAV"의 하이브리드 방식을 권장한다.
  • 자주 사용되는 속성은 컬럼으로, 동적으로 변하는 속성은 EAV로 관리한다.

EAV 실무 활용 사례

쇼핑몰 상품 상세 스펙

  • 앞서 살펴본 것처럼, 오픈마켓이나 쇼핑몰에서 다양한 카테고리의 상품 스펙을 관리할 때 EAV를 사용한다.

설문조사/폼 빌더

  • 동적으로 질문을 생성하고 응답을 저장하는 시스템에서 EAV가 유용하다.
-- 설문 정의
CREATE TABLE survey (
    survey_id BIGINT PRIMARY KEY AUTO_INCREMENT,
    title VARCHAR(200) NOT NULL,
    created_at DATETIME NOT NULL
);
-- 질문 정의 (Attribute Definition)
CREATE TABLE survey_question (
    question_id BIGINT PRIMARY KEY AUTO_INCREMENT,
    survey_id BIGINT NOT NULL,
    question_text VARCHAR(500) NOT NULL,
    question_type VARCHAR(20) NOT NULL, -- TEXT, RADIO, CHECKBOX
    options VARCHAR(500),
    display_order INT,
    FOREIGN KEY (survey_id) REFERENCES survey(survey_id)
);
-- 응답 저장 (EAV)
CREATE TABLE survey_response (
    response_id BIGINT PRIMARY KEY AUTO_INCREMENT,
    survey_id BIGINT NOT NULL,
    respondent_id BIGINT,
    question_id BIGINT NOT NULL,
    answer_value VARCHAR(1000),
    created_at DATETIME NOT NULL,
    FOREIGN KEY (survey_id) REFERENCES survey(survey_id),
    FOREIGN KEY (question_id) REFERENCES survey_question(question_id)
);

 

의료 시스템의 환자 데이터

  • 환자마다 다른 검사 항목, 처방 내역, 증상 기록 등을 저장할 때 EAV가 유용하다. 의료 분야에서는 검사 항목이 수천 가지가 될 수 있고, 환자마다 받는 검사가 다르기 때문이다.

사용자 프로필 확장 속성

  • 기본 사용자 정보 외에 각 사용자마다 다른 추가 프로필 정보를 유연하게 관리할 때 사용한다.
-- 기본 사용자 정보
CREATE TABLE users (
    user_id BIGINT PRIMARY KEY AUTO_INCREMENT,
    email VARCHAR(100) NOT NULL UNIQUE,
    name VARCHAR(50) NOT NULL,
    created_at DATETIME NOT NULL
);
-- 확장 프로필 (EAV)
CREATE TABLE user_profile_attribute (
    attr_id BIGINT PRIMARY KEY AUTO_INCREMENT,
    user_id BIGINT NOT NULL,
    attr_name VARCHAR(100) NOT NULL,
    attr_value VARCHAR(500),
    FOREIGN KEY (user_id) REFERENCES users(user_id)
);
-- 사용 예
INSERT INTO user_profile_attribute (user_id, attr_name, attr_value) VALUES
(1, 'github_url', 'https://github.com/user1'),
(1, 'blog_url', 'https://blog.user1.com'),
(1, 'preferred_language', 'ko'),
(2, 'company', 'ABC Corp'),
(2, 'job_title', 'Senior Developer');

 

EAV 선택에 대한 조언- 최후의 수단 (중요)

"가급적이면 EAV를 사용하지 마라."

이 말이 모순처럼 들릴 수 있겠지만, 이것이 정답이다. EAV는 관계형 데이터베이스(RDBMS)가 제공하는 강력한 이점들을 대부분 포기하는 방식이다.

  • 데이터 무결성 포기: 데이터베이스 레벨에서 "NOT NULL", "INT", "DATE" 같은 타입 체크나 외래 키 제약조건을 걸 수 없다.
    • 잘못된 데이터가 들어가는 것을 막기 위해 애플리케이션 코드가 매우 복잡해진다.
  • SQL 복잡도 증가: 단순한 조회조차 수많은 조인(Join)과 서브쿼리가 필요하다.
    • 쿼리를 작성하기도 어렵고, 유지보수하기도 힘들다.
  • 성능 저하: 대용량 데이터에서 특정 속성으로 검색하거나 정렬할 때 인덱스를 효율적으로 태우기 어렵다.

그럼 언제 사용해야 하는가?? 기존의 전통적인 테이블 설계(컬럼 추가, 정규화, 상속 관계 등)로는 도저히 해결할 수 없는 "불확실성이 확실한" 상황에서만 사용해야 한다. 단순히 "나중에 속성이 바뀔지도 몰라"라는 막연한 걱정으로 EAV를 도입해서는 안 된다.

 

현대적인 대안: JSON Type

  • 아마도 JSON 형식을 알고 있다면, EAV 대신에 JSON 형식으로 저장하면 더 효율적일 것 같다는 생각이 들 것이다.
  • 만약 어쩔 수 없이 EAV 방식을 고려해야 한다면, 그리고 최신 데이터베이스를 사용한다면 JSON 방식을 고려해보자.
  • 최신 RDBMS는 JSON 데이터에 대한 인덱싱과 검색 기능을 제공하므로, EAV의 복잡성을 줄이면서 유연성을 확보할 수 있다.

따라서 EAV 대신 JSON 컬럼을 사용하는 것이 더 효율적이다.

CREATE TABLE product (
    product_id BIGINT PRIMARY KEY,
    name VARCHAR(200),
    attributes JSON -- 속성을 JSON으로 저장
);

 

데이터베이스 설계의 기본은 언제나 명확한 테이블과 컬럼이다. 기본에 충실하되, 정말 어쩔 수 없는 상황에서만 "EAV"라는 무기를 고려하자.