데이터베이스 설계에서 정규화와 역정규화는 중요한 개념입니다. 이 글에서는 이 두 가지 개념의 목적과 원리를 다루고, 각 기법이 어떻게 실제 환경에서 활용되는지 설명하겠습니다. 또한, 실제 데이터베이스 성능 테스트를 통해 정규화와 역정규화의 성능 차이를 살펴보겠습니다.
💡 중복 데이터를 줄이고, 데이터의 무결성을 유지하기 위해 데이터를 체계적으로 분리하는 프로세스
각 컬럼이 원자 값을 가짐
잘못된 테이블 (1NF가 아님):
학생 ID | 이름 | 과목 |
---|---|---|
1 | 홍길동 | 수학, 영어 |
1NF로 변환:
학생 ID | 이름 | 과목 |
---|---|---|
1 | 홍길동 | 수학 |
1 | 홍길동 | 영어 |
1NF를 만족하면서 부분 함수 종속성 제거
예시: 학생이 다수의 과목을 수강할 때
잘못된 테이블 (1NF 상태):
학생 ID | 과목 | 이름 |
---|---|---|
1 | 수학 | 홍길동 |
1 | 영어 | 홍길동 |
2NF로 변환:
학생 테이블:
학생 ID | 이름 |
---|---|
1 | 홍길동 |
수강 과목 테이블:
학생 ID | 과목 |
---|---|
1 | 수학 |
1 | 영어 |
Transitive Dependency 제거
잘못된 테이블 (2NF 상태):
학생 ID | 이름 | 학과 | 학과 위치 |
---|---|---|---|
1 | 홍길동 | 컴퓨터공학과 | 2층 |
3NF로 변환:
학생 테이블:
학생 ID | 이름 | 학과 ID |
---|---|---|
1 | 홍길동 | 101 |
학과 테이블:
학과 ID | 학과 | 학과 위치 |
---|---|---|
101 | 컴퓨터공학과 | 2층 |
즉 3NF는 A->B->C인 테이블 상황을 A->B와 B->C로 분리하는 것을 의미합니다.
💡 Denormalization attempts to improve read performance at the expense of some write performance. Redundant copies of the data are written in multiple tables to avoid expensive joins.
https://github.com/donnemartin/system-design-primer#denormalization
위의 system-design-primer의 git에는 여러 대규모 시스템을 구축하는 방법론을 다루고 있습니다. 이 중 denormalization 파트를 인용하겠습니다.
Once data becomes distributed with techniques such as federation and sharding,
managing joins across data centers further increases complexity.
Denormalization might circumvent the need for such complex joins.
그렇다면 실제 조인에 들어가는 비용은 얼마나 될까요?
https://www.youtube.com/watch?v=704qQs6KoUk
배민 같은 경우 위처럼 기존에는 주문 DB에서 데이터를 저장하고 조회하는 작업을 동시에 수행하였습니다.
그런데 사용자에게 주문 내역을 보여주어야할 때, db에서 조회해야하는 정보는 무수히 많으며, 이와 관련한 테이블 또한 무수히 많았다고 합니다.
조회시 무수히 많은 테이블을 조인연산을 필요로 했고, 이는 성능 저하로 이어지게 되었다고 합니다.
배달의 민족은 읽기 전용 MongoDB를 사용해 성능 저하 문제 해결하였다고 합니다.
MongoDb를 적용한 workflow는 다음과 같습니다.
https://www.oreilly.com/library/view/mongodb-applied-design/9781449340056/ch01.html#FIG1
The seek takes well over 99% of the time spent reading a row.
When it comes to disk access, random seeks are the enemy.
The reason why this is so important in this context is because JOINs typically require random seeks.
디스크에서 읽는데 전체 걸리는 시간 중 탐색시간이 제일 오래 걸립니다.(99%)
JOIN 연산은 여러 테이블에서 데이터를 결합해야 하므로, 디스크에서 여러 위치로 이동해야 하는 random seek 이 많이 발생하게 됩니다.
random seek의 반대로 순차 접근이 있는데 순차 접근은 디스크의 데이터를 연속된 순서로 읽기 때문에, 디스크 헤드가 한 번의 이동으로 여러 데이터를 처리할 수 있어 훨씬 빠릅니다.
random seek 는 여러 위치로 헤드를 이동시키는 과정이 자주 발생하는 반면, 순차 접근은 디스크의 연속된 데이터 블록을 읽기 때문에 물리적인 헤드 이동이 거의 없거나 최소화됩니다.
결과적으로 순차 접근은 탐색 시간 및 성능 면에서 더 효율적입니다.
이러한 random seek가 많이발생 하므로 결국 오버헤드가 발생하는 경우가 존재하며,결과적으로 성능은 떨어지게 됩니다.
1:N 관계에서 정규화된 테이블과 역정규화된 테이블 간의 쿼리 성능을 비교했습니다. 데이터셋의 크기 및 테이블의 갯수 에 따라 성능 차이가 발생하는지를 측정했습니다.
SHOW PROFILES;
를 이용.위의 그래프와 도표를 통해 조인 쿼리와 합쳐진 테이블 쿼리의 성능 차이를 확인할 수 있습니다.
데이터 크기가 커질수록 조인 쿼리 시간이 점점 더 길어지는 반면, 합쳐진 테이블을 사용하는 경우 쿼리 시간이 더 짧게 유지됩니다.
이를 통해 데이터 크기와 테이블 수가 증가할 때, 역정규화된 테이블을 사용하는 것이 성능 향상에 유리하다는 것을 알 수 있습니다.
정규화는 데이터의 중복을 최소화하고 데이터 무결성을 유지하는 데 중요한 기법입니다. 하지만 조인 연산의 성능 저하 문제를 해결하기 위해, 상황에 따라 역정규화가 필요할 수 있습니다. 또한 위의 테스트 결과에 따라서 많은 테이블을 join하여 사용할 때는 역정규화가 성능 향상에 더욱 기여할 수 있음을 알 수 있습니다. 실제 데이터베이스 설계 및 데이터베이스를 선택함에 있어서, join의 성능 저하부분을 고려해야함을 어느정도는 알게 되었습니다.
이 글을 통해 정규화와 역정규화의 개념을 이해하고, 실제 시스템 설계에서의 적용 방법을 학습할 수 있었습니다.