Postgres マテリアライズドビューか Redis アプリケーションキャッシュか:選択肢の検討
Min-jun Kim
Dev Intern · Leapcell

はじめに
高パフォーマンスなアプリケーションを追求する上で、開発者は常に頻繁にアクセスされるデータを迅速かつ効率的に提供するという課題に直面しています。ここでよく活用されるのが、PostgreSQL のマテリアライズドビューと Redis のアプリケーションレベルキャッシュという 2 つの強力な手法です。どちらもクエリの遅延とデータベースの負荷を軽減することを目的としていますが、テクノロジースタックの異なるレイヤーで動作し、それぞれ独自の利点を提供します。どちらを使用するか、あるいは両者をどのように組み合わせるかを理解することは、スケーラブルで応答性の高いシステムを構築するために不可欠です。この記事では、各アプローチのコアコンセプト、実装の詳細、および実践的なシナリオを検討し、アーキテクチャの意思決定をガイドします。
コアコンセプトの説明
その応用技術の複雑さを掘り下げる前に、議論する主要な用語を簡単に定義しましょう。
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 は、マテリアライズドビューの少なくとも 1 つの列(または列のセット)に UNIQUE インデックスが必要です。
アプリケーションシナリオ
- 複雑なレポート: 大規模データセットに対する集計や結合を伴う重いレポートを生成する場合で、最新のデータが即座に必要ない場合。たとえば、日次、週次、または月次の売上レポート。
 - ダッシュボード: わずかなデータ遅延を許容できる主要な指標を表示するビジネスインテリジェンスダッシュボード。
 - データウェアハウス / OLAP ライクなクエリ: 運用データベースに直接コストのかかるクエリを実行することなく、分析目的で集計されたデータパターンに高速にアクセスする場合。
 - API レスポンスの安定化: API エンドポイントが頻繁に変更されない複雑なデータ構造を生成する場合、マテリアライズドビューをデータソースとして使用し、一貫性のある高速なレスポンスを保証できます。
 
Redis アプリケーションレベルキャッシュ:原則、実装、およびユースケース
原則
Redis は「光速」アクセスの原則に基づいて機能します。データを RAM に保存することで、例外的に低い遅延を提供します。アプリケーションロジックは、何をキャッシュするか、どのくらいの期間(TTL - Time To Live)、どのように無効化するかを決定します。これにより、開発者はキャッシング戦略を細かく制御できます。
実装
Redis の使用には、アプリケーションコード内の Redis クライアントライブラリとの対話が含まれます。以下は redis-py を使用した Python の例です。
import redis import json # Connect to Redis r = redis.Redis(host='localhost', port=6379, db=0) def get_product_details(product_id): cache_key = f"product:{product_id}" # Try to get from cache cached_data = r.get(cache_key) if cached_data: print(f"Cache hit for {cache_key}") return json.loads(cached_data) # Cache miss - fetch from database print(f"Cache miss for {cache_key}. Fetching from DB...") # Simulate DB query db_data = fetch_from_database(product_id) # Imagine this talks to Postgres if db_data: # Store in cache with a TTL (e.g., 3600 seconds = 1 hour) r.setex(cache_key, 3600, json.dumps(db_data)) return db_data def fetch_from_database(product_id): # This would be your actual database query logic # For demonstration, a mock data if product_id == 123: return {"id": 123, "name": "Fancy Gadget", "price": 99.99, "stock": 150} return None # Example usage product_info = get_product_details(123) print(product_info) # Second call will hit the cache product_info_cached = get_product_details(123) print(product_info_cached)
アプリケーションシナリオ
- 頻繁にアクセスされる個々のレコード/オブジェクト: ユーザープロファイル、商品詳細、設定など、頻繁に読み取られるが更新はまれな設定。
 - リアルタイムデータ: 可能な限り最新のデータが必要で、基になるデータが頻繁に変更される場合。手動無効化または短い TTL で鮮度を管理できます。
 - セッション管理: Web アプリケーションのユーザーセッションデータを保存します。
 - リーダーボード/カウンター: Redis のアトミック操作とデータ構造は、高スループットのリーダーボード、リアルタイム分析、カウンターに最適です。
 - マイクロサービス間通信: サービス間の高コストな API 呼び出しの結果をキャッシュします。
 
武器の選択:どちらを使用するか
マテリアライズドビューと Redis キャッシュの選択は、多くの場合、いくつかの主要な要因に帰着します。
1. データ鮮度の要件: * マテリアライズドビュー: ある程度のデータ鮮度の遅延を許容します。レポートやダッシュボードで、1 時間または 1 日ごとの更新が許容される場合に適しています。 * Redis キャッシュ: 特に短い TTL またはプロアクティブな無効化により、非常に鮮度の高いデータを提供できます。リアルタイムの精度が最優先されるユーザー向けデータに最適です。
2. 事前計算の複雑さ: * マテリアライズドビュー: 繰り返し実行するとコストのかかる複雑な SQL クエリ(結合、集計、ウィンドウ関数)の処理に優れています。データベースエンジンはこのために最適化されています。 * Redis キャッシュ: 通常、単純なキーと値のペアまたは構造化データ(JSON、ハッシュ)を格納します。オブジェクトは複雑になる可能性がありますが、そのオブジェクトの 計算 は通常、アプリケーション内またはキャッシュされる 前 のデータベースクエリ中に発生します。
3. データ量とアクセスパターン: * マテリアライズドビュー: 多くの異なるクエリ、特に分析的な性質を持つクエリによって消費される大規模な集計データセットに最適です。 * Redis キャッシュ: 特定の個々の「ホット」アイテムを非常に迅速に提供するのに理想的です。高トラフィックの個々のレコードの読み取りをスケーリングするのに適しています。
4. 運用上のオーバーヘッドと制御: * マテリアライズドビュー: データベースによって管理されます。リフレッシュスケジュールと並行リフレッシュ技術には、データベース管理が必要です。データの一貫性はデータベースによって処理されます。 * Redis キャッシュ: アプリケーションレイヤーによって管理されます。エビクションポリシー、TTL、無効化ロジックを細かく制御できます。開発者はキャッシングロジックを実装する必要があります。
5. データストレージの場所: * マテリアライズドビュー: データは PostgreSQL データベース内に存在します。 * Redis キャッシュ: データは別のインメモリストアに存在し、プライマリデータベースの負荷をオフロードします。
2 つの具体的な例を考えてみましょう。
シナリオ A: 日次売上ダッシュボードの構築。 ダッシュボードには、過去 30 日間の総収益、平均注文値、およびトップセラー商品が表示されます。このデータは数時間ごとに更新する必要があります。 * ソリューション: Postgres マテリアライズドビューが最適です。基になるクエリは複雑(売上および商品テーブルの集計、結合)であり、1 時間ごとの更新で十分です。ダッシュボードはマテリアライズドビューをクエリし、運用テーブルへの負荷を最小限に抑えます。
シナリオ B: 高トラフィックのソーシャルメディアプラットフォームでのユーザープロフィールの表示。 ユーザーが他のユーザーのプロフィールを訪問するたびに、そのプロフィール情報(ユーザー名、アバター、自己紹介)が取得されます。プロフィールの更新はまれですが、ほぼ即座に反映される必要があります。 * ソリューション: Redis アプリケーションレベルキャッシュ。個々のユーザープロフィールは頻繁にアクセスされるホットアイテムです。これらを比較的短い TTL(例: 数分)で Redis にキャッシュするか、更新時に即座に無効化することで、速度と鮮度の両方を保証します。アプリケーションロジックは、キャッシュミス時にデータベースから取得し、Redis への更新をプッシュすることを処理します。
ハイブリッドアプローチ:
両方を使用することも一般的です!たとえば、日次売上ダッシュボード(シナリオ A)を考えてみましょう。マテリアライズドビューが集計データをサービスする一方で、現在の時間 の リアルタイム 売上を表示する、より小さく頻繁に更新されるダッシュボードの一部は、アプリケーションで計算されてから Redis にキャッシュされるか、さらには Redis に直接更新されるデータによって強化される可能性があります。
結論
PostgreSQL のマテリアライズドビューと Redis のアプリケーションレベルキャッシュは、どちらもアプリケーションパフォーマンスを最適化するための貴重なツールですが、異なる課題に対処し、異なるレイヤーで動作します。マテリアライズドビューは、ある程度のデータ鮮度の遅延が許容される分析目的のために、複雑な集計データを事前計算するのに優れており、プライマリデータベースからの重いクエリ実行をオフロードします。一方、Redis は、アプリケーションレベルでキャッシング戦略を細かく制御しながら、頻繁に変更される特定のデータポイントへの超高速アクセスを提供します。それぞれの長所とユースケースを理解することで、開発者は適切なツールを賢く選択するか、あるいはそれらを組み合わせて、非常にパフォーマンスが高くスケーラブルなシステムを構築できます。

