Postgres 복합 뷰와 Redis 애플리케이션 캐싱 중 선택하기
Min-jun Kim
Dev Intern · Leapcell

소개
고성능 애플리케이션을 추구하는 과정에서 개발자들은 자주 접근하는 데이터를 빠르고 효율적으로 제공해야 하는 과제에 끊임없이 직면합니다. 두 가지 강력한 기법이 자주 사용되는데, 바로 PostgreSQL 복합 뷰와 Redis 애플리케이션 레벨 캐싱입니다. 둘 다 쿼리 지연 시간을 줄이고 데이터베이스 부하를 낮추는 것을 목표로 하지만, 기술 스택의 다른 계층에서 작동하며 각기 다른 장점을 제공합니다. 언제 하나를 사용하고 다른 하나를 사용할지, 또는 둘을 어떻게 조합할지를 이해하는 것은 확장 가능하고 반응성이 뛰어난 시스템을 구축하는 데 매우 중요합니다. 이 글에서는 각 접근 방식의 핵심 개념, 구현 세부 정보 및 실제 시나리오를 탐구하여 아키텍처 결정을 안내할 것입니다.
핵심 개념 설명
구현의 복잡성에 들어가기 전에, 논의할 주요 용어를 간략하게 정의해 보겠습니다.
PostgreSQL 복합 뷰: PostgreSQL의 복합 뷰는 쿼리 결과를 미리 계산하여 물리적 테이블로 저장하는 데이터베이스 객체입니다. 일반 뷰와 달리, 일반 뷰는 본질적으로 액세스할 때마다 결과가 계산되는 저장된 쿼리이지만, 복합 뷰는 실제 데이터를 저장합니다. 이 사전 계산은 후속 읽기를 크게 가속화합니다. 그러나 복합 뷰의 데이터는 기본 테이블이 변경되어도 자동으로 업데이트되지 않습니다. 명시적으로 새로고침해야 합니다.
Redis 애플리케이션 레벨 캐시: Redis(Remote Dictionary Server)는 종종 캐시로 사용되는 오픈 소스 인메모리 데이터 저장소입니다. 이름에서 알 수 있듯이 애플리케이션 레벨 캐시는 애플리케이션 코드에서 직접 관리됩니다. 데이터가 요청되면 애플리케이션은 먼저 Redis를 확인합니다. 데이터가 발견되면( "캐시 히트"), 즉시 반환됩니다. 그렇지 않으면( "캐시 미스"), 애플리케이션은 기본 데이터 소스(예: PostgreSQL)에서 데이터를 가져와서 향후 요청을 위해 Redis에 저장한 다음 반환합니다.
Postgres 복합 뷰: 원리, 구현 및 사용 사례
원리
복합 뷰는 사전 계산의 원리에 기반합니다. 조인, 집계 또는 비용이 많이 드는 계산을 포함하는 복잡한 쿼리가 한 번 실행되고 결과가 저장됩니다. 그런 다음 복합 뷰에 대한 후속 쿼리는 간단한 테이블 스캔이 되어 읽기 성능을 크게 향상시킵니다. 대가는 데이터의 최신성입니다. 뷰의 데이터는 마지막 새로고침 시점을 기준으로 기본 테이블의 상태만 반영합니다.
구현
복합 뷰를 만드는 것은 간단합니다.
CREATE MATERIALIZED VIEW daily_sales_summary AS SELECT DATE(order_timestamp) AS sale_date, SUM(total_amount) AS total_revenue, COUNT(DISTINCT customer_id) AS unique_customers FROM orders WHERE order_timestamp >= CURRENT_DATE - INTERVAL '30 days' GROUP BY DATE(order_timestamp) ORDER BY sale_date DESC;
사용하려면 일반 테이블처럼 쿼리하면 됩니다.
SELECT * FROM daily_sales_summary WHERE sale_date = '2023-10-26';
뷰를 새로고침하려면 명시적 명령이 필요합니다. 자주 변경되는 기본 테이블의 경우 빈번한 새로고침이 필요할 수 있습니다.
REFRESH MATERIALIZED VIEW daily_sales_summary;
대규모 뷰의 경우 CONCURRENTLY를 사용하면 잠금을 최소화하면서 새로고침 중에 읽기가 가능합니다.
REFRESH MATERIALIZED VIEW CONCURRENTLY daily_sales_summary;
참고: CONCURRENTLY는 복합 뷰의 하나 이상의 열(또는 열 집합)에 UNIQUE 인덱스가 필요합니다.
애플리케이션 시나리오
- 복잡한 보고: 대규모 데이터 세트에 대한 무거운 집계 또는 조인을 포함하는 보고서를 생성할 때, 최신 데이터가 즉시 필요하지 않은 경우. 예를 들어, 일간, 주간 또는 월간 판매 보고서입니다.
 - 대시보드: 약간의 데이터 지연을 허용할 수 있는 주요 지표를 표시하는 비즈니스 인텔리전스 대시보드입니다.
 - 데이터 웨어하우징 / OLAP 유형 쿼리: 운영 데이터베이스에 직접적으로 비용이 많이 드는 쿼리를 실행하지 않고 분석 목적으로 집계된 데이터 패턴에 빠르게 액세스해야 할 때.
 - API 응답 안정화: API 엔드포인트가 자주 변경되지 않는 복잡한 데이터 구조를 생성하는 경우, 복합 뷰가 데이터 소스 역할을 하여 일관되고 빠른 응답을 보장할 수 있습니다.
 
Redis 애플리케이션 레벨 캐시: 원리, 구현 및 사용 사례
원리
Redis는 "빛의 속도" 액세스라는 원리로 작동합니다. RAM에 데이터를 저장함으로써 매우 낮은 지연 시간을 제공합니다. 애플리케이션 로직은 무엇을 캐시할지, 얼마나 오래(TTL - Time To Live) 캐시할지, 어떻게 무효화할지를 결정합니다. 이를 통해 개발자는 캐싱 전략을 세밀하게 제어할 수 있습니다.
구현
Redis를 사용하려면 애플리케이션 코드에서 Redis 클라이언트 라이브러리와 상호 작용해야 합니다. 다음은 redis-py를 사용한 Python 예시입니다.
import redis import json # Redis에 연결 r = redis.Redis(host='localhost', port=6379, db=0) def get_product_details(product_id): cache_key = f"product:{product_id}" # 캐시에서 가져오기 시도 cached_data = r.get(cache_key) if cached_data: print(f"Cache hit for {cache_key}") return json.loads(cached_data) # 캐시 미스 - DB에서 가져오기 print(f"Cache miss for {cache_key}. Fetching from DB...") # DB 쿼리 시뮬레이션 db_data = fetch_from_database(product_id) # PostgreSQL에 연결한다고 가정 if db_data: # TTL(예: 3600초 = 1시간)로 캐시에 저장 r.setex(cache_key, 3600, json.dumps(db_data)) return db_data def fetch_from_database(product_id): # 이것이 실제 데이터베이스 쿼리 로직일 것입니다 # 시연을 위한 모의 데이터 if product_id == 123: return {"id": 123, "name": "Fancy Gadget", "price": 99.99, "stock": 150} return None # 사용 예시 product_info = get_product_details(123) print(product_info) # 두 번째 호출은 캐시를 히트합니다 product_info_cached = get_product_details(123) print(product_info_cached)
애플리케이션 시나리오
- 자주 액세스되는 개별 레코드/객체: 사용자 프로필, 제품 세부 정보, 자주 읽지만 드물게 업데이트되는 구성 설정.
 - 실시간 데이터: 가능한 가장 최신 데이터가 필요하고 기본 데이터가 자주 변경되는 경우. 수동 무효화 또는 짧은 TTL을 사용하여 최신성을 관리할 수 있습니다.
 - 세션 관리: 웹 애플리케이션을 위한 사용자 세션 데이터 저장.
 - 리더보드/카운터: Redis의 원자적 연산과 데이터 구조는 고처리량 리더보드, 실시간 분석 및 카운터에 탁월합니다.
 - 마이크로서비스 통신: 서비스 간의 비용이 많이 드는 API 호출 결과 캐싱.
 
무기 선택: 무엇을 언제 사용할 것인가
복합 뷰와 Redis 캐싱 사이의 선택은 종종 몇 가지 주요 요인에 달려 있습니다.
1. 데이터 최신성 요구 사항: * 복합 뷰: 약간의 데이터 지연을 허용합니다. 시간별 또는 일별 새로고침이 허용되는 보고서 및 대시보드에 적합합니다. * Redis 캐시: 짧은 TTL 또는 사전 예방적 무효화를 통해 매우 최신 데이터를 제공할 수 있습니다. 실시간 정확성이 가장 중요한 사용자 대면 데이터에 가장 적합합니다.
2. 사전 계산의 복잡성: * 복합 뷰: 반복적으로 실행하기에 비용이 많이 드는 복잡한 SQL 쿼리(조인, 집계, 윈도우 함수)를 처리하는 데 탁월합니다. 데이터베이스 엔진은 이를 위해 최적화되어 있습니다. * Redis 캐시: 일반적으로 더 간단한 key-value 쌍 또는 구조화된 데이터(JSON, 해시)를 저장합니다. 객체가 복잡할 수 있지만, 해당 객체의 계산은 일반적으로 캐싱 전에 애플리케이션에서 또는 데이터베이스 쿼리 중에 발생합니다.
3. 데이터 볼륨 및 액세스 패턴: * 복합 뷰: 분석적인 특성을 가진 다양한 쿼리에서 대규모 집계 데이터 세트가 소비되는 시나리오에 가장 적합합니다. * Redis 캐시: 개별 "핫" 항목을 매우 빠르게 제공하는 데 이상적입니다. 높은 트래픽을 받는 개별 레코드에 대한 읽기를 확장하는 데 좋습니다.
4. 운영 오버헤드 및 제어: * 복합 뷰: 데이터베이스에서 관리합니다. 새로고침 예약 및 동시 새로고침 기술은 데이터베이스 관리가 필요합니다. 데이터 일관성은 데이터베이스에서 처리됩니다. * Redis 캐시: 애플리케이션 계층에서 관리됩니다. 제거 정책, TTL 및 무효화 로직에 대한 세밀한 제어를 제공합니다. 개발자가 캐싱 로직을 구현해야 합니다.
5. 데이터 저장 위치: * 복합 뷰: 데이터는 PostgreSQL 데이터베이스 내에 있습니다. * Redis 캐시: 데이터는 별도의 인메모리 저장소에 있으며 기본 데이터베이스의 부하를 분산합니다.
두 가지 특정 예제를 살펴 보겠습니다.
시나리오 A: 일별 판매 대시보드 구축. 대시보드는 지난 30일간의 총 수익, 평균 주문 가치 및 가장 많이 팔린 제품을 보여줍니다. 이 데이터는 몇 시간마다 한 번씩 업데이트되어야 합니다.
- 솔루션: PostgreSQL 복합 뷰가 완벽하게 적합합니다. 기본 쿼리는 매우 복잡할 가능성이 높으며(판매 및 제품 테이블에 대한 집계, 조인), 시간별 새로고침이 허용됩니다. 대시보드는 복합 뷰를 쿼리하여 운영 테이블에 최소한의 부하를 줍니다.
 
시나리오 B: 트래픽이 많은 소셜 미디어 플랫폼에서 사용자 프로필 표시. 사용자가 다른 사용자의 프로필을 방문할 때마다 해당 프로필 정보(사용자 이름, 아바타, 소개)가 가져와집니다. 프로필 업데이트는 드물지만 거의 즉시 반영되어야 합니다.
- 솔루션: Redis 애플리케이션 레벨 캐시. 개별 사용자 프로필은 자주 액세스되는 핫 항목입니다. 합리적으로 짧은 TTL(예: 몇 분)로 Redis에 캐시하거나 업데이트 시 즉시 무효화하면 속도와 최신성 모두 보장됩니다. 애플리케이션 로직은 캐시 미스 시 데이터베이스에서 가져오고 Redis에 업데이트를 푸시하는 것을 처리합니다.
 
하이브리드 접근 방식:
둘 다 사용하는 것도 일반적입니다! 일별 판매 대시보드(시나리오 A)를 상상해 보세요. 복합 뷰가 집계 데이터를 제공하는 동안, 현재 시간의 실시간 판매를 보여주는 대시보드의 작고 자주 업데이트되는 부분은 Redis에 캐시되거나 직접 Redis에 업데이트되는 데이터를 먼저 애플리케이션에서 계산하여 이를 파워로 삼을 수 있습니다.
결론
PostgreSQL 복합 뷰와 Redis 애플리케이션 레벨 캐시 모두 애플리케이션 성능을 최적화하는 데 귀중한 도구이지만, 서로 다른 문제를 해결하고 다른 계층에서 작동합니다. 복합 뷰는 기본 데이터베이스에서 무거운 쿼리 실행을 오프로드하면서, 약간의 데이터 지연을 허용하는 분석 목적을 위한 복잡한 집계 데이터를 사전 계산하는 데 탁월합니다. 반면 Redis는 애플리케이션 레벨에서 캐싱 전략에 대한 세밀한 제어를 제공하며, 자주 변경되는 특정 데이터 포인트에 대한 매우 빠른 액세스를 제공합니다. 그들의 고유한 강점과 사용 사례를 이해함으로써 개발자는 직무에 맞는 올바른 도구를 신중하게 선택하거나 심지어 결합하여 고성능 및 확장 가능한 시스템을 구축할 수 있습니다.

