복잡한 분석 속도 향상을 위한 머티리얼라이즈드 뷰 활용
Lukas Schneider
DevOps Engineer · Leapcell

소개
데이터의 세계에서 우리는 방대한 양의 정보에서 통찰력을 추출해야 하는 시나리오를 자주 접하게 됩니다. 이는 종종 대형 테이블에 걸쳐 합계, 평균, 개수 등과 같은 집계를 계산하는 복잡한 분석 쿼리를 실행하는 것을 포함합니다. 이러한 쿼리는 강력하지만 특히 반복적으로 실행될 때 엄청나게 리소스를 많이 사용하고 시간이 많이 걸릴 수 있습니다. 매분 새로고침되는 대시보드를 상상해 보세요. 매번 동일한 무거운 집계 쿼리를 실행합니다. 성능 병목 현상이 빠르게 나타나 애플리케이션 응답이 느려지고 데이터베이스 리소스가 비효율적으로 사용됩니다.
이것이 미리 계산된 결과를 캐싱하는 개념이 매우 중요해지는 지점입니다. 이러한 복잡한 집계의 결과를 저장함으로써 거의 즉시 검색할 수 있으며, 쿼리 성능과 전반적인 사용자 경험을 극적으로 향상시킬 수 있습니다.
이 글에서는 머티리얼라이즈드 뷰가 복잡한 집계 쿼리 결과를 캐싱하는 훌륭한 솔루션 역할을 하여 데이터베이스 효율성과 분석 기능을 크게 향상시키는 방법을 자세히 살펴봅니다.
기본 개념 이해
머티리얼라이즈드 뷰의 구체적인 내용을 살펴보기 전에, 논의의 기반이 되는 몇 가지 핵심 개념을 간략하게 정의해 보겠습니다.
집계 쿼리: 이것은 행 집합에 대한 계산을 수행하고 단일 값을 반환하는 SQL 쿼리입니다. 일반적인 집계 함수에는 COUNT()
, SUM()
, AVG()
, MIN()
, MAX()
가 있으며, 데이터를 범주화하기 위해 종종 GROUP BY
절과 함께 사용됩니다.
캐싱: 나중에 해당 데이터에 대한 요청을 더 빠르게 처리할 수 있도록 데이터를 임시 저장 영역에 저장하는 프로세스입니다. 우리 맥락에서는 비싼 쿼리 결과를 저장하는 것을 의미합니다.
뷰 (논리적 뷰): SQL 쿼리 결과 집합을 기반으로 하는 가상 테이블입니다. 뷰는 실제 테이블과 마찬가지로 행과 열을 포함합니다. 뷰의 필드는 데이터베이스의 하나 이상의 실제 테이블 필드입니다. 그러나 뷰 자체는 데이터를 저장하지 않습니다. 대신 액세스될 때마다 기본 쿼리를 실행합니다.
머티리얼라이즈드 뷰 (미리 계산된 뷰): 표준 뷰와 달리 머티리얼라이즈드 뷰는 정의 쿼리의 미리 계산된 결과를 실제 테이블로 저장합니다. 머티리얼라이즈드 뷰를 쿼리할 때 원래의 복잡한 쿼리를 다시 실행하는 대신 저장된 미리 계산된 결과를 쿼리하는 것입니다. 이것이 집계 쿼리에 대한 성능 이점의 핵심 차이점입니다.
머티리얼라이즈드 뷰의 원리
복잡한 집계 쿼리에 머티리얼라이즈드 뷰를 사용하는 기본 원리는 미리 계산하고 저장하는 것입니다. 집계된 데이터가 필요할 때마다 대규모 기본 테이블에서 계산 비용이 많이 드는 GROUP BY
쿼리를 실행하는 대신, 한 번 (또는 주기적으로) 실행하여 그 결과를 머티리얼라이즈드 뷰에 저장합니다. 이후 쿼리는 미리 계산된 뷰에서 단순히 선택하며, 이는 일반 테이블처럼 작동하여 응답 시간을 훨씬 빠르게 만듭니다.
작동 방식
- 정의: SQL 쿼리를 사용해 머티리얼라이즈드 뷰를 정의합니다. 종종
JOIN
작업, 집계 함수 및GROUP BY
절이 포함됩니다. - 생성/채우기: 머티리얼라이즈드 뷰가 처음 생성될 때 정의 쿼리가 실행되고 그 결과가 전용 물리적 테이블에 저장됩니다.
- 쿼리: 사용자가 또는 애플리케이션이 머티리얼라이즈드 뷰를 쿼리할 때, 데이터베이스는 원래의 복잡한 쿼리 실행을 건너뛰고 물리적 저장소에 있는 미리 계산된 데이터에 직접 액세스합니다.
- 새로고침: 기본 테이블이 변경될 수 있으므로 머티리얼라이즈드 뷰의 데이터가 최신이 아닐 수 있습니다. 따라서 머티리얼라이즈드 뷰는 주기적으로 새로고침하여 최신 데이터를 반영해야 합니다. 이는 수동으로, 예약에 따라, 또는 때로는 데이터베이스 시스템에 의해 자동으로 수행될 수 있습니다 (예: Oracle의 빠른 새로고침).
실제 구현
실제 예제를 살펴봅시다. 전자 상거래 플랫폼에 orders
및 order_items
테이블이 있다고 가정해 보겠습니다. 시간에 따른 제품 카테고리별 총 매출을 자주 분석해야 합니다.
기본 테이블:
-- orders 테이블 CREATE TABLE orders ( order_id INT PRIMARY KEY, customer_id INT, order_date DATE, total_amount DECIMAL(10, 2) ); -- product_categories 테이블 CREATE TABLE product_categories ( category_id INT PRIMARY KEY, category_name VARCHAR(100) ); -- products 테이블 CREATE TABLE products ( product_id INT PRIMARY KEY, product_name VARCHAR(255), category_id INT, FOREIGN KEY (category_id) REFERENCES product_categories(category_id) ); -- order_items 테이블 CREATE TABLE order_items ( order_item_id INT PRIMARY KEY, order_id INT, product_id INT, quantity INT, price_per_unit DECIMAL(10, 2), FOREIGN KEY (order_id) REFERENCES orders(order_id), FOREIGN KEY (product_id) REFERENCES products(product_id) ); -- 샘플 데이터 삽입 INSERT INTO product_categories (category_id, category_name) VALUES (1, 'Electronics'), (2, 'Books'), (3, 'Apparel'); INSERT INTO products (product_id, product_name, category_id) VALUES (101, 'Laptop', 1), (102, 'Smartphone', 1), (201, 'SQL Guide', 2), (202, 'NoSQL Basics', 2), (301, 'T-Shirt', 3), (302, 'Jeans', 3); INSERT INTO orders (order_id, customer_id, order_date, total_amount) VALUES (1, 1001, '2023-01-05', 1200.00), (2, 1002, '2023-01-06', 50.00), (3, 1001, '2023-01-07', 80.00), (4, 1003, '2023-02-10', 95.00), (5, 1004, '2023-02-15', 1500.00); INSERT INTO order_items (order_item_id, order_id, product_id, quantity, price_per_unit) VALUES (1, 1, 101, 1, 1200.00), (2, 2, 201, 1, 50.00), (3, 3, 301, 2, 40.00), (4, 4, 302, 1, 95.00), (5, 5, 102, 1, 700.00), (6, 5, 202, 2, 400.00); -- Total 1500 (700 + 800)
비싼 집계 쿼리:
월별 제품 카테고리별 총 매출을 얻으려면:
SELECT pc.category_name, DATE_TRUNC('month', o.order_date) AS sales_month, SUM(oi.quantity * oi.price_per_unit) AS total_sales FROM orders o JOIN order_items oi ON o.order_id = oi.order_id JOIN products p ON oi.product_id = p.product_id JOIN product_categories pc ON p.category_id = pc.category_id GROUP BY pc.category_name, DATE_TRUNC('month', o.order_date) ORDER BY sales_month, pc.category_name;
이 쿼리는 여러 조인과 집계를 포함하며, 대규모 데이터셋에서는 매우 느릴 수 있습니다.
머티리얼라이즈드 뷰 생성 (PostgreSQL 구문):
CREATE MATERIALIZED VIEW monthly_category_sales AS SELECT pc.category_name, DATE_TRUNC('month', o.order_date) AS sales_month, SUM(oi.quantity * oi.price_per_unit) AS total_sales FROM orders o JOIN order_items oi ON o.order_id = oi.order_id JOIN products p ON oi.product_id = p.product_id JOIN product_categories pc ON p.category_id = pc.category_id GROUP BY pc.category_name, DATE_TRUNC('month', o.order_date);
이제 복잡한 쿼리를 직접 실행하는 대신 머티리얼라이즈드 뷰를 쿼리할 수 있습니다:
SELECT * FROM monthly_category_sales WHERE sales_month = '2023-01-01' ORDER BY total_sales DESC;
이 쿼리는 미리 계산된 테이블에서 단순히 선택하는 것이므로 훨씬 빠릅니다.
머티리얼라이즈드 뷰 새로고침:
새 주문이 들어오거나 기존 주문이 업데이트되면 monthly_category_sales
뷰가 최신이 아닐 수 있습니다. 새로고침해야 합니다:
REFRESH MATERIALIZED VIEW monthly_category_sales;
이 명령은 정의 쿼리를 다시 실행하고 최신 데이터로 머티리얼라이즈드 뷰를 업데이트합니다. 매우 큰 머티리얼라이즈드 뷰의 경우 이 새로고침 작업이 여전히 시간이 많이 걸릴 수 있습니다. Oracle과 같은 일부 데이터베이스 시스템은 마지막 새로고침 이후의 변경 사항만 처리하여 업데이트 프로세스를 훨씬 빠르게 만드는 '빠른 새로고침' 기능을 제공합니다. PostgreSQL은 임의 쿼리에 대한 네이티브 '빠른 새로고침'을 제공하지 않지만 사용자는 종종 사용자 지정 로직이나 도구를 통해 증분 업데이트를 구현합니다.
적용 시나리오
머티리얼라이즈드 뷰는 다음과 같은 경우에 특히 적합합니다:
- 보고 및 대시보드: 실시간 정밀도가 필요하지 않지만 높은 성능의 이점을 누릴 수 있는 집계 측정값을 표시하는 운영 대시보드.
- 데이터 웨어하우징: 분석 처리 (OLAP) 시스템에서 머티리얼라이즈드 뷰는 집계 사실에 대한 쿼리를 크게 가속화합니다.
- 복잡한 ETL 프로세스: 분석 저장소에 로드하기 전에 데이터를 미리 집계하면 후속 쿼리가 단순화될 수 있습니다.
- 집계 데이터 노출 API: 기본 트랜잭션 데이터베이스에 부담을 주지 않고 집계 데이터에 대한 빠른 엔드포인트를 제공합니다.
- 기계 학습 기능 엔지니어링: 과거 데이터를 기반으로 하는 기능 (예: 누적 평균, 시간 창에 대한 합계)을 미리 계산합니다.
결론
머티리얼라이즈드 뷰는 특히 복잡하고 자주 액세스되는 집계 쿼리를 다룰 때 데이터베이스 성능을 최적화하는 강력하고 종종 필수적인 도구입니다. 쿼리 결과를 미리 계산하고 저장함으로써 느리고 리소스 집약적인 작업을 빠른 데이터 검색으로 변환하여 애플리케이션 응답성과 사용자 경험을 극적으로 향상시킵니다.
새로고침 전략과 잠재적인 데이터 최신성 부족에 대한 신중한 고려가 필요하지만, 분석 및 보고 맥락에서의 장점은 부인할 수 없으며, 효율적인 데이터 처리를 위한 초석이 됩니다.