データベース●●●●○

マテリアライズドビュー

Materialized View ・ まてりあらいずどびゅー

重い集計クエリの結果を実体として保存し、読み取りを速くする代わりに鮮度を管理する仕組み。

✦ 30秒で試す — 航海読むのは3ミリ秒。でも、その裏の重さは消えていないREFRESH 1回にかかる時間は?予想してみる

概要

マテリアライズドビュー(実体化ビュー)は、SQL クエリの結果をその場で計算するのではなく、あらかじめ計算してテーブルのような「実体」としてディスクに保存しておく仕組みです。「月別売上サマリ」「ユーザーごとの投稿数ランキング」のような、複数テーブルの結合と集計を伴う重いクエリの結果を事前に作り置きしておき、参照時にはその作り置きを読むだけにする — いわばデータベース内蔵のキャッシュです。

日常的には管理画面やダッシュボード、レポート機能の高速化で登場します。元データが数億行あっても、集計済みの結果が数千行なら読み取りは一瞬です。その代わり、元データが更新されても作り置きは自動では変わらないため、「いつ・どうやって作り直すか(リフレッシュ)」と「どこまでの古さを許容するか(鮮度)」の管理がこの仕組みの中心的なテーマになります。

なぜ生まれたか

RDBMS には古くから「ビュー」という仕組みがあります。ビューは複雑なクエリに名前を付けたもので、参照するたびに元のクエリがその場で実行されます。定義の再利用や権限の切り分けには便利ですが、性能は1ミリも改善しません。ビューの裏側が「5つのテーブルを結合して全行を集計する」クエリなら、ダッシュボードを開くたびにその全計算が走ります。

データ量が増え、BIやレポーティングのように「同じ重い集計を何度も参照する」利用が広がると、毎回の再計算は明らかな無駄になりました。参照のたびに答えが変わるわけでもない集計を、なぜ毎回ゼロから計算するのか — この無駄を省くため、「クエリの結果そのものを実体として保存し、再計算は明示的なタイミングだけにする」マテリアライズドビューが生まれました。Oracle が「スナップショット」の名前で実装し、のちにマテリアライズドビューと呼び替えたのが代表的な系譜です。SQL Server は元テーブルの更新に合わせて自動で保守される「インデックス付きビュー」として、PostgreSQL は 9.3(2013年)で手動リフレッシュ型のマテリアライズドビューとして取り入れました。一方で MySQL には今も標準の機能としては存在せず、集計結果を普通のテーブルに書き出し、定期ジョブやトリガーで作り直す形で代用します。どの形でも、計算のタイミングを「読むとき」から「書くとき・更新するとき」へ移す、という発想の転換は共通です。

詳細

通常のビューとの違い — 計算するタイミングが逆

両者の違いは「クエリがいつ実行されるか」に尽きます。通常のビューは参照のたびに元テーブルへクエリが走るため、常に最新である代わりに毎回重い。マテリアライズドビューはリフレッシュ時にだけクエリが走り、参照は保存済み結果を読むだけなので速い代わりに、前回リフレッシュ以降の変更は反映されません。

通常のビューマテリアライズドビュー参照クエリビュー定義 名前だけ元テーブル群数億行参照のたびに重い集計が走る常に最新・毎回遅い参照クエリ保存済みの集計結果数千行の実体元テーブル群数億行読むだけ・速いリフレッシュ時のみ再集計速い・鮮度はリフレッシュ次第
通常のビューとマテリアライズドビューの比較 — 重い計算がいつ走るか

実体を持つということは、テーブルと同じようにインデックスを張れるということでもあります。集計結果にさらに検索条件を掛けるダッシュボードでは、マテリアライズドビュー + インデックスの組み合わせが強力に効きます。オプティマイザが元テーブルへのクエリを自動でマテリアライズドビュー参照に書き換えてくれる機能(クエリリライト)を持つデータベースもあり、その場合はアプリ側のクエリを変えずに恩恵を受けられます。効いているかどうかは実行計画で確認できます。

リフレッシュ戦略 — 鮮度と負荷のトレードオフ

リフレッシュの設計が、この仕組みの成否を分けます。もっとも単純なのは全体再計算(PostgreSQL の REFRESH MATERIALIZED VIEW)で、確実ですが元クエリの全コストが毎回かかります。夜間バッチや1時間おきの定期実行と組み合わせるのが典型で、「最大1時間古いデータが見える」ことを仕様として許容する判断とセットになります。なお PostgreSQL の素朴な全体リフレッシュは、実行中そのビューへの参照をブロックします。参照を止めずに入れ替えるのが REFRESH MATERIALIZED VIEW CONCURRENTLY で、新しい結果を別に計算してから差分だけを書き換えます。ただし差分を突き合わせるために、ビューの全行を一意に識別できるユニークインデックス(WHERE 句のない、列だけのもの)が必要で、差分の計算のぶん全体の処理はむしろ重くなります。初回の作成直後のように中身がまだ空の状態でも使えません。

より進んだ方式が増分リフレッシュ(incremental refresh / incremental view maintenance)です。元テーブルの変更差分だけを使ってビューを部分更新するもので、Oracle のマテリアライズドビューログを使った fast refresh が代表例です。全体再計算に比べ圧倒的に軽い一方、対応できるクエリの形(結合や集計の種類)に制約があります。さらにこの発想を推し進め、変更ストリームから集計結果を常時更新し続ける仕組みはストリーム処理の領域と地続きで、Materialize のような「常に最新のマテリアライズドビュー」を掲げる専用エンジンも登場しています。

どの方式でも本質は同じトレードオフです。鮮度を上げるほどリフレッシュの頻度・仕組みのコストが上がり、下げるほど「古いデータを見せるリスク」が上がる。これはキャッシュの無効化問題と同型であり、「このデータはどれだけ古くてよいか」を業務要件として明確にすることが設計の出発点になります。

鮮度と読み取りの速さの取引を確かめてください。左の元テーブルに注文を書き込むと、閲覧者が2秒ごとに開いている2つのダッシュボードの値の変わり方が違います。リフレッシュを周期にして注文を流し込むと、MV の古さが鋸歯の形になります。

⚡ 体験: 集計を作り置きする
注文
リフレッシュ
orders(元テーブル)120万行(集計していない)+ 本+ 家電+ 食品通常のビュー読み取り 1,200 ms本40家電25食品55閲覧のたびに orders を全件集計するマテリアライズドビュー読み取り 3 ms本40家電25食品55すべて反映済みREFRESH0510秒MV の古さ(最も古い未反映の注文から・直近30秒)許せる古さ
注文の書き込みリフレッシュ本当の値(MV に未反映)
MV の古さ0.0 秒読み取り通常 1,200 ms / MV 3 ms集計の負荷(直近10秒)0%
○ 通常のビューは書くとすぐ反映○ MV は古い値のまま○ REFRESH で追いつく○ 周期リフレッシュで古さを許容線の下に

左の 「+ 本」「+ 家電」「+ 食品」で元テーブルに注文を1件ずつ書き込んでください。右の2つのダッシュボードは、閲覧者が2秒ごとに開いています。

元テーブルは 120 万行、全件の集計に 1,200 ms かかる想定です。時間は実時間で、アニメーションの集計は 0.9 秒に縮めています。

使いどころと落とし穴

マテリアライズドビューが輝くのは、「書き込みより読み取りがずっと多く」「多少古くてもよく」「計算が重い」データです。ダッシュボード、ランキング、月次レポート、全文検索のために複数のテーブルを1つにまとめた検索用テーブル(正規化をあえて崩した非正規化の形)などが典型です。読み取り用に最適化した形を書き込みモデルと分けて持つという点で、CQRS の読み取りモデルをデータベース機能だけで実現した最小構成と見ることもできます。逆に、残高や在庫のように常に厳密な最新値が必要なデータには不向きで、そこは元テーブルを直接参照すべきです。

実務の落とし穴としては、リフレッシュのバッチが静かに失敗し、誰も気づかないまま古いデータが表示され続けるパターンが代表的です。リフレッシュの成否と最終更新時刻は監視の対象に含めておくべきです。また、マテリアライズドビューが増殖して依存関係が絡まり、リフレッシュの順序管理が新たな複雑さになることもあります。「重いクエリはとりあえずマテビュー化」ではなく、鮮度要件を確認したうえで、依存の浅い少数のビューに絞って使うのが健全な運用です。

試問

左の元テーブル(120万行、全件の集計に約1秒)に注文が書き込まれ、右のダッシュボードは集計を作り置きしたマテリアライズドビュー(MV)を読んでいます。上のボタンで、注文を自動で流れ込ませることと、MV を作り直す(REFRESH)周期を選べます。業務の要件は「MV に映る値の古さは5秒まで」です。

✦ 命題挑戦中

注文が流れ込み続けるなか、MV の古さを5秒以下に保ちつつ、リフレッシュの集計負荷を40%以下に抑えよ

  • 注文を「自動で流れ込む」にする
  • リフレッシュの集計負荷(直近10秒)を40%以下にする(いま 0%)
  • MV の古さを許せる5秒以下のまま15秒保つ(0/15秒)
⚡ 体験: 集計を作り置きする
注文
リフレッシュ
orders(元テーブル)120万行(集計していない)+ 本+ 家電+ 食品通常のビュー読み取り 1,200 ms本40家電25食品55閲覧のたびに orders を全件集計するマテリアライズドビュー読み取り 3 ms本40家電25食品55すべて反映済みREFRESH0510秒MV の古さ(最も古い未反映の注文から・直近30秒)許せる古さ
注文の書き込みリフレッシュ本当の値(MV に未反映)
MV の古さ0.0 秒読み取り通常 1,200 ms / MV 3 ms集計の負荷(直近10秒)0%
○ 通常のビューは書くとすぐ反映○ MV は古い値のまま○ REFRESH で追いつく○ 周期リフレッシュで古さを許容線の下に

左の 「+ 本」「+ 家電」「+ 食品」で元テーブルに注文を1件ずつ書き込んでください。右の2つのダッシュボードは、閲覧者が2秒ごとに開いています。

元テーブルは 120 万行、全件の集計に 1,200 ms かかる想定です。時間は実時間で、アニメーションの集計は 0.9 秒に縮めています。