저자: 이춘식 / LG CNS 데이터아키텍처팀
출처: 데이터베이스 설계와 구축(개정판) Chapter 8. 성능 데이터 모델링 중에서
일반적으로는 정규화가 잘 되어 있으면 입력, 수정, 삭제의 성능이 향상되고, 반정규화를 많이 할수록 조회 성능이 향상된다. 그러나 꼭 반정규화만이 조회 성능을 향상시킨다는 고정관념을 깨야 한다. 정규화를 해야만 성능을 향상시키는 경우도 많이 있기 때문이다.
두 개의 엔티티타입이 통합되어 반정규화된 경우
다음 데이터모델을 살펴보자. 경매에 대한 간단한 사례로, 매각 물건이 있으면 해당 매각 물건은 특정한 날짜에 지정된 여러 장소에서 매각을 하고, 매각된 내용에 대해 통계 데이터를 관리하는 데이터모델이다. 이 업무에서는 매각 일자에 따라 매각 시간과 매각 장소가 결정된다. 매각 일자는 대략적으로 5000건이 있고, 일자별 매각 물건은 100만 건으로 가정하자.
<그림-성능이 저하된 반정규화 사례-엔티티타입 반정규화>
위의 모델에서 만약 매각장소가 ‘서울 7호’에서 매각된 총매각금액, 총유찰금액을 산출하는 조회용 SQL 문장을 작성하면 다음과 같이 작성된다.
SELECT B.총매각금액 , B.총유찰금액
FROM (SELECT DISTINCT 매각일자
FROM 일자별매각물건
WHERE 매각장소 = "서울 7호") A, <== 100만 건의 데이터를 읽어 DISTINCT함
매각일자별 매각내역 B
WHERE A.매각일자 = B.매각일자
즉 대량으로 존재하는 데이터에서 조인 조건이 되는 대상을 찾기 위해 인라인뷰를 사용함으로써 성능이 저하되는 사례다.
복합 식별자 중에서 일반 속성이 주식별자 속성 중 일부에만 종속 관계를 가지고 있으므로 2차 정규화 대상이 된다. 2차 정규화를 적용하면 다음과 같은 모델이 된다.
<그림-성능이 저하된 반정규화 사례-정규화를 통한 성능 향상>
2차 정규화를 적용하여 매각일자가 PK가 되고, 매각시간과 매각장소가 일반 속성이 되었다. 정규화를 적용함으로써 매각일자를 PK로 사용하는 매각 일자별 매각내역과도 관계가 연결될 수 있어 업무 흐름에 따른 정확한 데이터모델링 표기도 가능하게 되었고, 드라이빙된 테이블이 5000건의 매각기일 테이블이 되어 성능도 향상되었음을 알 수 있다.
만약 위의 모델에서 ‘서울 7호’에서 매각된 총매각금액, 총유찰금액을 산출하는 조회용 SQL 문장을 작성하면 다음과 같이 작성된다.
SELECT B.총매각금액 , B.총유찰금액
FROM 매각기일 A,
매각일자별매각내역 B
WHERE A.매각장소 = "서울 7호" <== 5천건의 데이터를 읽음
AND A.매각일자 = B.매각일자;
매각기일 테이블이 정규화되었고, 따라서 드라이빙되는 대상 테이블의 데이터가 5천 건이므로 빠른 조회 성능이 나온다.
최대 성능 구현을 위한 효율적인 데이터 모델링 기법 세미나
두 개의 속성을 나열하여 반정규화한 경우
계층형 데이터베이스를 많이 사용했던 과거 데이터모델링의 습관에 따라 관계형 데이터베이스에서도 이와 같이 데이터모델링을 한 경우가 많이 나타난다. 다음 사례에서 보면 모델이라고 하는 테이블에는 업무적으로 필요한 8개의 인덱스가 이미 생성되어 있는 상태다. 데이터는 30만 건이고, 온라인 환경의 데이터베이스라고 가정하자. 유형기능분류코드에 따라 데이터를 조회하는 경우가 많이 나타나 인덱스를 생성하려고 하니 무려 유형기능분류코드 각각에 대해 인덱스를 생성해야 하므로 9개나 되는 인덱스를 추가 생성해야 한다.
<그림-성능이 저하된 반정규화 사례-속성이 중복된 경우>
한 테이블에 인덱스가 많아지면 조회 성능은 향상되지만, 데이터 입력, 수정, 삭제에 대한 성능은 저하된다. 그래서 일반 업무 처리(온라인성 업무)에서는 인덱스 수를 가급적 7~8개가 넘지 않도록 하는 것이 좋다.
그런데 위의 모델은 다른 필요한 인덱스 이외에 유형기능분류코드 속성에 해당하는 인덱스를 9개나 추가적으로 생성해야 하므로 실전 프로젝트에서는 어쩔 수 없이 인덱스를 생성하지 않거나 A유형기능분류코드1 하나만 인덱스를 생성하는 경우가 발생한다. 이에 따라 A유형기능분류코드1, A유형기능분류코드2, A유형기능분류코드3...을 이용하는 SQL의 성능이 저하되어 나타나는 경우가 많다.
만약 각 유형 코드별로 조건을 부여하여 모델코드와 모델명을 조회하는 SQL 문장을 작성한다면 다음과 같이 작성된다.
SELECT 모델코드, 모델명
FROM 모델
WHERE ( A유형기능분류코드1 = "01" )
OR ( B유형기능분류코드2 = "02" )
OR ( C유형기능분류코드3 = "07" )
OR ( D유형기능분류코드4 = "01" )
OR ( E유형기능분류코드5 = "02" )
OR ( F유형기능분류코드6 = "07" )
OR ( G유형기능분류코드7 = "03" )
OR ( H유형기능분류코드8 = "09" )
OR ( I유형기능분류코드9 = "09" )
각 유형별로 모두 인덱스가 걸려 있어야 인덱스에 의해 데이터를 찾을 수 있다.
위 모델은 다음과 같이 정규화를 적용해야 한다.
중복속성에 대한 분리가 1차 정규화의 정의임을 고려하면 모델 테이블은 1차정규화의 대상이 된다. 로우단위의 대상도 1차 정규화 대상이 되지만, 컬럼 단위로 중복되는 경우도 1차 정규화 대상이 된다. 따라서 모델에 대해 1차 정규화를 적용하며 다음과 같이 분리될 수 있다.
<그림-성능이 저하된 반정규화 사례-속성이 중복된 경우>
하나의 테이블에 데이터 9개가 반복적으로 나열되어 있을 때는 인덱스 생성이 어려웠지만, 정규화를 통해 분리한 이후에는 인덱스 추가 생성이 0개가 되었고, 분리된 테이블 모델기능분류코드에서 PK 인덱스를 생성하여 이용함으로써 성능이 향상될 수 있다.
만약 각 유형코드별로 조건을 부여하여 모델코드와 모델명을 조회하는 SQL 문장을 작성한다면 다음과 같이 작성된다.
SELECT A.모델코드, A.모델명
FROM 모델 A, 모델기능분류코드 B
WHERE ( B.유형코드 = "A" AND B.기능분류코드 = "01" AND A.모델코드 = B.모델코드 )
OR ( B.유형코드 = "B" AND B.기능분류코드 = "02" AND A.모델코드 = B.모델코드 )
OR ( B.유형코드 = "C" AND B.기능분류코드 = "07" AND A.모델코드 = B.모델코드 )
OR ( B.유형코드 = "D" AND B.기능분류코드 = "01" AND A.모델코드 = B.모델코드 )
OR ( B.유형코드 = "E" AND B.기능분류코드 = "02" AND A.모델코드 = B.모델코드 )
OR ( B.유형코드 = "F" AND B.기능분류코드 = "07" AND A.모델코드 = B.모델코드 )
OR ( B.유형코드 = "G" AND B.기능분류코드 = "03" AND A.모델코드 = B.모델코드 )
OR ( B.유형코드 = "H" AND B.기능분류코드 = "09" AND A.모델코드 = B.모델코드 )
OR ( B.유형코드 = "I" AND B.기능분류코드 = "09" AND A.모델코드 = B.모델코드 )
위 SQL 구문은 유형코드+기능분류코드+모델코드에 인덱스가 걸려 있으므로 인덱스를 통해 데이터를 조회하여 성능이 향상된다.
실전 프로젝트에서도 많은 데이터모델이 컬럼 단위에서 중복된 경우가 발견된다. 파급 효과를 계산하지 않고 무조건 컬럼 단위로 COL1, COL2, COL3... 식으로 데이터모델링을 하다가는 모델을 이용하는 개발자에게 원성을 들을 준비를 해야 한다.
그러므로 데이터모델링을 전개할 때 기본적으로 정규화를 적용하도록 해야 한다. 일단 정규화를 적용한 데이터모델을 만들고 난 이후에 업무적으로 발생시키는 트랜잭션의 성격, 분산 환경 등의 조건에 따라 반정규화를 적용하도록 해야 한다.