비정규화 - 웹 성능을 위한 실용적인 트레이드오프
Olivia Novak
Dev Intern · Leapcell

소개
웹 애플리케이션 개발 분야에서 성능은 무엇보다 중요합니다. 사용자는 즉각적인 응답을 기대하며, 느린 로딩 시간은 참여도와 수익 감소로 이어질 수 있습니다. 데이터베이스 정규화는 데이터 중복을 줄이고 데이터 무결성을 개선하기 위한 관계형 데이터베이스 설계의 초석이지만, 절대적인 정규화를 추구하는 것은 종종 대가를 치르는데, 이는 쿼리 복잡성과 실행 시간 증가입니다. 이는 정규화된 스키마가 완전한 데이터 세트를 검색하기 위해 일반적으로 수많은 조인을 필요로 하기 때문입니다. 트래픽이 많은 웹 애플리케이션의 경우, 이러한 추가 조인은 빠르게 병목 현상이 되어 사용자 경험에 심각한 영향을 미칠 수 있습니다. 이것이 바로 비정규화가 등장하는 지점입니다. 결함으로서가 아니라, 계산된 전략적 설계 선택, 현대 웹 애플리케이션이 요구하는 응답성을 달성하기 위한 필수적인 희생으로서입니다.
상황 이해
비정규화의 미묘한 차이를 살펴보기 전에 몇 가지 기본 개념을 명확히 하겠습니다.
정규화: 데이터베이스 정규화는 데이터 중복을 최소화하고 데이터 무결성을 개선하기 위해 관계형 데이터베이스의 열과 테이블을 구성하는 프로세스입니다. 일반적으로 큰 테이블을 작고 관련된 테이블로 분할하고 그들 간의 관계를 정의하는 것을 포함합니다. 일반적인 정규 형식에는 1NF, 2NF 및 3NF가 있습니다.
비정규화: 비정규화는 쿼리 성능을 개선하기 위해 의도적으로 데이터베이스 스키마에 중복을 도입하는 프로세스입니다. 이는 종종 테이블을 결합하거나, 정규화된 설계에서 별도였을 테이블에 데이터의 복사본을 추가하는 것을 포함합니다.
조인 연산: 관계형 데이터베이스에서 JOIN 절은 열 값 간의 관련 열 값을 기반으로 하나 이상의 테이블에서 열을 결합합니다. 정규화된 스키마에서 완전한 데이터를 검색하는 데 필수적이지만, 조인은 특히 대규모 데이터 세트를 처리할 때 계산 비용이 많이 드는 연산입니다.
비정규화의 원칙
웹 애플리케이션을 위한 비정규화의 핵심 원칙은 간단합니다. 자주 액세스하는 데이터를 검색하는 데 필요한 조인 연산의 수를 줄이는 것입니다. 데이터를 미리 조인하거나 복제함으로써, 쿼리 시점에 이러한 비용이 많이 드는 연산을 수행할 필요가 없어집니다. 이러한 사전 계산 또는 사전 저장된 조인 데이터는 종종 웹 애플리케이션에서 지배적인 연산인 읽기 액세스를 크게 가속화합니다.
일반적인 시나리오를 생각해 봅시다. 각 기사에 대한 작성자 이름과 함께 블로그 홈페이지에 기사 목록을 표시하는 것입니다.
정규화된 접근 방식:
일반적으로 articles와 authors라는 두 개의 테이블이 있습니다.
-- albums 테이블 CREATE TABLE articles ( article_id INT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(255) NOT NULL, content TEXT, author_id INT NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (author_id) REFERENCES authors(author_id) ); -- artists 테이블 CREATE TABLE authors ( author_id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(255) NOT NULL, email VARCHAR(255) UNIQUE );
기사 제목과 작성자 이름을 표시하려면 다음과 같은 쿼리를 실행해야 합니다.
SELECT a.title, au.name FROM articles a JOIN authors au ON a.author_id = au.author_id WHERE a.created_at >= CURDATE() - INTERVAL 7 DAY ORDER BY a.created_at DESC;
몇 개의 기사에 대해서는 이 조인이 미미합니다. 하지만 수천 개의 기사와 수백 명의 작성자가 있는 인기 있는 블로그를 상상해 보세요. 자주 방문하는 페이지의 데이터를 가져옵니다. 각 요청에는 조인이 포함되며, 이는 성능 병목 현상이 될 수 있습니다.
비정규화된 접근 방식:
articles 테이블에 author_name을 직접 추가하여 중복을 도입할 수 있습니다.
CREATE TABLE articles ( article_id INT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(255) NOT NULL, content TEXT, author_id INT NOT NULL, author_name VARCHAR(255) NOT NULL, -- 비정규화된 필드 created_at DATETIME DEFAULT CURRENT_TIMESTAMP -- 무결성 및 향후 업데이트를 위해 author_id가 여전히 존재하지만, -- 전략에 따라 외래 키 제약 조건이 여전히 바람직할 수 있습니다. );
이제 기사 제목과 작성자 이름을 가져오는 것은 단일 테이블에서 간단한 읽기입니다.
SELECT title, author_name FROM articles WHERE created_at >= CURDATE() - INTERVAL 7 DAY ORDER BY created_at DESC;
이 단일 테이블 쿼리는 특히 부하가 많은 환경에서 조인 연산보다 훨씬 빠릅니다. 웹 애플리케이션은 모든 요청에 대한 조인 오버헤드를 피합니다.
애플리케이션 시나리오 및 구현
비정규화는 다음과 같은 시나리오에서 가장 효과적입니다.
- 읽기 중심 워크로드: 웹 애플리케이션은 종종 읽기 대 쓰기 비율이 읽기 쪽으로 크게 치우쳐 있습니다. 비정규화는 이러한 빈번한 읽기 연산을 최적화합니다.
- 복잡한 보고 또는 분석: 여러 테이블에서 데이터를 집계하는 것은 종종 복잡하고 느린 조인을 포함합니다. 사전 계산 및 저장된 집계를 비정규화하면 보고서 속도를 크게 높일 수 있습니다.
- 여러 테이블이 필요한 자주 액세스되는 데이터: 특정 데이터 조합이 일관되게 요청되는 경우 비정규화는 상당한 성능 향상을 제공할 수 있습니다.
일반적인 비정규화 전략:
-
열 복제: 위
author_name예와 같이 가장 간단한 형태입니다. 관련 테이블에서 열을 기본 액세스 테이블로 복사합니다. -
요약/집계 테이블: 보고를 위해, 사전 계산된 합계, 평균 또는 개수를 저장하는 테이블을 생성하여 비싼
GROUP BY및JOIN연산을 최소화할 수 있습니다. 예를 들어,daily_sales_summary테이블은 개별order_items에서 매번 다시 계산하는 것을 피하기 위해 각 제품별 일일 총 판매량을 저장할 수 있습니다. -
수직 분할 (종종 비정규화와 결합): 엄밀히 말하면 비정규화는 아니지만 종종 함께 사용됩니다. 많은 열을 가진 단일 테이블을 갖는 대신, 액세스 패턴에 따라 테이블을 수직으로 분할합니다. 자주 액세스되는 열은 함께 유지되고, 덜 자주 액세스되거나 더 큰 열은 별도의 테이블에 유지합니다. 그런 다음 자주 조인되는 열을 기본 "핫" 테이블로 비정규화할 수 있습니다.
데이터 일관성 관리:
비정규화의 가장 큰 과제는 데이터 일관성을 유지하는 것입니다. 비정규화된 필드가 원래 테이블에서 업데이트되면 중복된 복사본도 업데이트해야 합니다. 이는 다양한 메커니즘을 통해 처리할 수 있습니다.
-
애플리케이션 로직: 애플리케이션 코드는 원본 데이터가 변경될 때마다 모든 중복 복사본을 업데이트할 책임이 있습니다. 이는 세심하고 규율 있는 개발을 필요로 합니다.
-
데이터베이스 트리거: 데이터베이스 트리거는 원본 데이터가 변경될 때 비정규화된 필드를 자동으로 업데이트할 수 있습니다. 이렇게 하면 애플리케이션에서 일관성 로직을 오프로드하지만 데이터베이스 스키마에 복잡성을 더합니다.
-- authors.name이 변경될 때 articles의 author_name을 업데이트하는 예제 트리거 DELIMITER $$ CREATE TRIGGER update_article_author_name AFTER UPDATE ON authors FOR EACH ROW BEGIN IF OLD.name <> NEW.name THEN UPDATE articles SET author_name = NEW.name WHERE author_id = NEW.author_id; END IF; END$$ DELIMITER ; -
배치 업데이트/예약 작업: 덜 중요하거나 자주 변경되지 않는 데이터의 경우, 비정규화된 필드에 대한 업데이트는 주기적으로 (예: 야간) 실행되는 배치 작업으로 처리할 수 있습니다.
-
구체화된 뷰 (일부 데이터베이스 시스템): 일부 고급 데이터베이스 시스템은 물리적으로 저장된 쿼리의 사전 계산된 결과를 제공하는 구체화된 뷰를 제공합니다. 수동으로 또는 자동으로 새로 고칠 수 있어 강력한 비정규화 메커니즘을 제공합니다.
결론
비정규화는 피해야 할 안티 패턴이 아니라, 신중하게 적용하면 강력한 최적화 기법입니다. 읽기 속도가 중요한 고성능 웹 애플리케이션의 경우, 데이터 중복을 통제적으로 도입하면 쿼리 실행 시간을 크게 줄이고 사용자 경험을 개선할 수 있습니다. 이는 이론적인 데이터 무결성을 희생하여 실질적인 성능 향상을 얻는 실용적인 트레이드오프입니다. 핵심은 전략적 적용, 일관성 영향 이해, 중복 데이터 효과적으로 관리하기 위한 강력한 메커니즘 구현에 있습니다. 비정규화를 신중하게 채택함으로써 개발자는 기능적일 뿐만 아니라 탁월하게 빠르고 반응성이 뛰어난 웹 애플리케이션을 구축할 수 있습니다.