Amazon Redshift [新機能]マテリアライズドビューの徹底解説

2020.03.08

昨年のre:Invent2019で発表されたAmazon Redshiftのマテリアライズドビューがクラスタバージョン1.0.13448からGA(Generally Available:正式リリース)になりました。マイグレーションの際にご要望の多かったマテリアライズドビューが本番リリースしましたので早速試してみました。

目次

マテリアライズドビューとは

通常のビューはクエリを実行するたびにビュー定義内のクエリ(結合、フィルタ、集計)も繰り返し実行されます。一方、マテリアライズドビューはビュー定義内のクエリ(結合、フィルタ、集計)の実行結果を保持することで、ビュー定義内のクエリの実行することなく結果が得られるため、CPUやIOの負荷を抑え、素早く結果が得られるようになります。通常のビューはマクロで、マテリアライズドビューはテーブルとイメージに近いです。Redshiftのマテリアライズドビューは、新規作成した時に実行結果が保持され、これ以降はリフレッシュコマンドを実行することでデータが更新されます。

通常のビューとマテリアライズドビューの違いは以下のとおりです。

View Materialized View
ユースケース 複雑なクエリをビューにまとめたり、レコードやカラムの参照を制限する 繰り返し同じテーブルを参照するクエリのパフォーマンス改善
動作 ビュー内SQLを常に実行して結果を返す 保持しているSQLの実行結果を返す
データ更新 不要(常に最新の結果が得られる) 必要(リフレッシュが必要、増分データのみ更新、更新したいタイミングでコマンドを実行する)
応答速度 遅い 速い

ユーザーの観点から見て、クエリの結果は、ソーステーブルから同じデータを取得に要する時間に比べて短時間で返ってきます。

マテリアライズドビューの基本操作

作成

マテリアライズドビューは、通常のビューと同じ様に作成可能です。追加のオブションについては後ほど解説します。

cmdb=> CREATE MATERIALIZED VIEW tickets_mv AS
cmdb->     select   catgroup,
cmdb->     sum(qtysold) as sold
cmdb->     from     category c, event e, sales s
cmdb->     where    c.catid = e.catid
cmdb->     and      e.eventid = s.eventid
cmdb->     group by catgroup;
CREATE MATERIALIZED VIEW
Time: 1128.051 ms (00:01.128)

補足:psqlからオブジェクトを参照すると、マテリアライズドビューの実態は、rdsdbユーザーが所有するtickets_mvテーブルとオブジェクトオーナーが作成したmv_tbl__tickets_mv__0ビューを組み合わせです。マテリアライズドビューはこの組み合わせで管理されます。

cmdb=> \d
 schema |         name          | type  | owner
--------+-----------------------+-------+---------
 tickit | category              | table | cm_user
 tickit | date                  | table | cm_user
 tickit | event                 | table | cm_user
 tickit | listing               | table | cm_user
 tickit | mv_tbl__tickets_mv__0 | table | rdsdb
 tickit | sales                 | table | cm_user
 tickit | tickets_mv            | view  | cm_user
 tickit | users                 | table | cm_user
 tickit | venue                 | table | cm_user
(9 rows)

cmdb=> \d tickets_mv
                     View "tickit.tickets_mv"
  Column  |         Type          | Collation | Nullable | Default
----------+-----------------------+-----------+----------+---------
 catgroup | character varying(10) |           |          |
 sold     | bigint                |           |          |

問い合わせ

通常のビューと同じ様に問い合わせできます。

cmdb=> SELECT count(*) FROM tickets_mv;
 count
-------
     2
(1 row)
Time: 232.459 ms

cmdb=> SELECT * FROM tickets_mv;
 catgroup |  sold
----------+--------
 Concerts | 195444
 Shows    | 149905
(2 rows)
Time: 249.984 ms

リフレッシュ

ソーステーブルは更新していませんがREFRESH MATERIALIZED VIEWコマンドを実行してみました。最近コミットされた更新を除いて更新されましたといったメッセージが出力されれています。

cmdb=> REFRESH MATERIALIZED VIEW tickets_mv;
INFO:  Materialized view tickets_mv was updated excluding some recently committed updates. Please run REFRESH again later.
REFRESH
Time: 530.498 ms

削除

DROP VIEWではなく、DROP MATERIALIZED VIEWで削除します。

cmdb=> DROP VIEW tickets_mv;
ERROR:  "tickets_mv" is not a view
HINT:  Use DROP MATERIALIZED VIEW to remove a materialized view.
Time: 332.463 ms

cmdb=> DROP MATERIALIZED VIEW tickets_mv;
DROP MATERIALIZED VIEW
Time: 413.982 ms

最低限、マテリアライズドビューを利用したいのであればここまでで十分かもしれません。データマートの更新とどう違うのか、内部的のどの様に実現されているかを把握したい場合は以降を御覧ください。

マテリアライズドビューの強み「増分リフレッシュ」

クエリの実行結果をテーブルに保存して再利用するならデータマートや1次集計テーブルで十分です。マテリアライズドビューでは、ビューを構成するソーステーブルに加えられた増分データを対象にデータ更新する「増分リフレッシュ」するので更新負荷が少なく、短時間で済みます。

REFRESH MATERIALIZED VIEWを実行すると、「増分リフレッシュ」を試み、インクリメンタルに更新できない場合は「フルリフレッシュ」によって更新します。「増分リフレッシュ」が可能であるかは、STV_MV_INFOビューのstate(マテリアライズドビューの状態)から確認できます。

マテリアライズドビューの最適化

マテリアライズドビューは「テーブルとイメージに近い」と解説しましたが、実態はテーブルなので分散キーとソートキーを指定してテーブルを最適化し、クエリのパフォーマンスを改善できます。2〜4行目に追加しています。

cmdb=> CREATE MATERIALIZED VIEW tickets_mv
  DISTSTYLE KEY
  DISTKEY(catgroup)
  SORTKEY(catgroup)
  AS (
    select   catgroup,
    sum(qtysold) as sold
    from     category c, event e, sales s
    where    c.catid = e.catid
    and      e.eventid = s.eventid
    group by catgroup
  )
;
CREATE MATERIALIZED VIEW
Time: 1136.064 ms (00:01.136)

マテリアライズドビューの監視

マテリアライズドビューを監視するビューが追加されています。

  • STV_MV_INFO:すべてのマテリアライズドビューの行、データが古くなっているかどうか、およびステータス情報が含まれる
  • STL_MV_STATE:マテリアライズドビューのすべてのステータス遷移の行が含まれる
  • SVL_MV_REFRESH_STATUS:マテリアライズドビューの更新作業用の行が含まれる

上記のビューをそれぞれ実行した結果です。STV_MV_INFOビューのis_staleが

cmdb=> select * from STV_MV_INFO;
-[ RECORD 1 ]----+----------------------------------------------------------------------
db_name          | cmdb
schema           | tickit
name             | tickets_mv
updated_upto_xid | 44998616
is_stale         | t
owner_user_name  |
state            | 1
Time: 255.584 ms

cmawsteamdb=> select * from STL_MV_STATE;
-[ RECORD 1 ]----+----------------------------------------------------------------------
userid            | 111
starttime         | 2020-03-06 08:17:19.199417
xid               | 44998647
event_desc        | Vacuum
db_name           | cmdb
base_table_schema | tickit
base_table_name   | category
mv_schema         | tickit
mv_name           | tickets_mv
state             | Recompute
Time: 236.227 ms

cmdb=> select * from SVL_MV_REFRESH_STATUS;
-[ RECORD 1 ]----+----------------------------------------------------------------------
db_name     | cmdb
userid      | 111
schema_name | tickit
mv_name     | tickets_mv
xid         | 44998617
starttime   | 2020-03-06 08:10:47.425842
endtime     | 2020-03-06 08:11:00.511995
status      | Refresh partially updated MV incrementally up to an active transaction
Time: 260.425 ms

増分リフレッシュの検証

検証シナリオ

マテリアライズドビューで最も気にな機能は、「増分リフレッシュ」です。追加したデータにのみ集計・追加が実行され、マテリアライズドビューのリフレッシュ時間が単調増加しないことを確認します。今回は、顧客ごとの1992年から1998年までの注文額(c_orderyaer)を集計するマテリアライズドビューを例に機能を検証します。1992年、1993年、1994年、、、1998年と年毎にデータ(lineorder_all)を追加しながらマテリアライズドビューをリフレッシュします。

ファクトがlineorder、ディメンジョンがcustomer、データの分布や件数は以下のとおりです。

cmdb=# select count(*) from lineorder_all;
   count
-----------
 600037902
(1 row)

cmdb=# select trunc(lo_orderdate,-4) as c_orderyear, count(*) as cnt from lineorder_all group by 1 order by 1;
 c_orderyear |   cnt
-------------+----------
    19920000 | 91248844
    19930000 | 91007488
    19940000 | 91044214
    19950000 | 91016436
    19960000 | 91301792
    19970000 | 91050840
    19980000 | 53368288
(7 rows)

cmdb=# select count(*) from customer;
  count
---------
 3000000
(1 row)

マテリアライズドビュー

顧客ごとの1992年から1998年までの注文額(c_orderyaer)を集計するマテリアライズドビューです。ファクト(lineorder)とディメンジョン(customer)を結合して、顧客と年度で注文額(c_orderyaer)を集計します。下記の通り、集計クエリを書いているだけで、差分をどうするなど何も定義する必要はありません。

-- DROP MATERIALIZED VIEW sales_customer_mv;
CREATE MATERIALIZED VIEW sales_customer_mv
  DISTSTYLE KEY
  DISTKEY(c_custkey)
  SORTKEY(c_custkey, c_name, c_orderyear)
  AS (
    SELECT
      c_custkey,
      c_name,
      trunc(lo_orderdate,-4) as c_orderyear,
      sum(lo_ordertotalprice) as c_ordertotal
    FROM lineorder l
    INNER JOIN customer c ON l.lo_custkey = c.c_custkey
    GROUP BY 1,2,3
  )
;

年度ごとのデータの追加とマテリアライズドビューのリフレッシュ

マテリアライズドビューの作成

cmdb=> DROP MATERIALIZED VIEW sales_customer_mv;
DROP MATERIALIZED VIEW
Time: 385.378 ms
cmdb=> CREATE MATERIALIZED VIEW sales_customer_mv
cmdb->   DISTSTYLE KEY
cmdb->   DISTKEY(c_custkey)
cmdb->   SORTKEY(c_custkey, c_name, c_orderyear)
cmdb->   AS (
cmdb(>     SELECT
cmdb(>       c_custkey,
cmdb(>       c_name,
cmdb(>       trunc(lo_orderdate,-4) as c_orderyear,
cmdb(>       sum(lo_ordertotalprice) as c_ordertotal
cmdb(>     FROM lineorder l
cmdb(>     INNER JOIN customer c ON l.lo_custkey = c.c_custkey
cmdb(>     GROUP BY 1,2,3
cmdb(>   )
cmdb-> ;
CREATE MATERIALIZED VIEW
Time: 944.159 ms

cmdb=> select count(*) from sales_customer_mv;
 count
-------
     0
(1 row)

Time: 223.625 ms

1992年のデータの追加とリフレッシュ

cmdb=> INSERT INTO lineorder
SELECT * FROM lineorder_all WHERE lo_orderdate > 19920000 AND lo_orderdate < 19930000;
INSERT 0 91248844

cmdb=> REFRESH MATERIALIZED VIEW sales_customer_mv;
INFO:  Table "mv_tbl__sales_customer_mv__0_tmp" does not exist and will be skipped
INFO:  Table "mv_tbl__sales_customer_mv__0__insert_tmp" does not exist and will be skipped
INFO:  Materialized view sales_customer_mv was incrementally updated successfully.
REFRESH
Time: 61010.016 ms (01:01.010)

cmdb=> select count(*) from sales_customer_mv;
  count
---------
 1999625
(1 row)

Time: 214.519 ms

1993年のデータの追加とリフレッシュ

cmdb=> INSERT INTO lineorder
SELECT * FROM lineorder_all WHERE lo_orderdate > 19930000 AND lo_orderdate < 19940000;
INSERT 0 91007488

cmdb=> REFRESH MATERIALIZED VIEW sales_customer_mv;
INFO:  Materialized view sales_customer_mv was incrementally updated successfully.
REFRESH
Time: 32679.170 ms (00:32.679)

cmdb=> select count(*) from sales_customer_mv;
  count
---------
 3999239
(1 row)

Time: 230.856 ms

1994年のデータの追加とリフレッシュ

cmdb=> INSERT INTO lineorder
SELECT * FROM lineorder_all WHERE lo_orderdate > 19940000 AND lo_orderdate < 19950000;
INSERT 0 91044214

cmdb=> REFRESH MATERIALIZED VIEW sales_customer_mv;
INFO:  Materialized view sales_customer_mv was incrementally updated successfully.
REFRESH
Time: 47750.169 ms (00:47.750)

cmdb=> select count(*) from sales_customer_mv;
  count
---------
 5998849
(1 row)

Time: 274.657 ms

1995年のデータの追加とリフレッシュ

cmdb=> INSERT INTO lineorder
SELECT * FROM lineorder_all WHERE lo_orderdate > 19950000 AND lo_orderdate < 19960000;
INSERT 0 91016436

cmdb=> REFRESH MATERIALIZED VIEW sales_customer_mv;
INFO:  Materialized view sales_customer_mv was incrementally updated successfully.
REFRESH
Time: 26067.248 ms (00:26.067)

cmdb=> select count(*) from sales_customer_mv;
  count
---------
 7998447
(1 row)

Time: 242.446 ms

1996年のデータの追加とリフレッシュ

cmdb=> INSERT INTO lineorder
SELECT * FROM lineorder_all WHERE lo_orderdate > 19960000 AND lo_orderdate < 19970000;
INSERT 0 91301792

cmdb=> REFRESH MATERIALIZED VIEW sales_customer_mv;
INFO:  Materialized view sales_customer_mv was incrementally updated successfully.
REFRESH
Time: 22812.124 ms (00:22.812)

cmdb=> select count(*) from sales_customer_mv;
  count
---------
 9998075
(1 row)

Time: 240.592 ms

1997年のデータの追加とリフレッシュ

cmdb=> INSERT INTO lineorder
SELECT * FROM lineorder_all WHERE lo_orderdate > 19970000 AND lo_orderdate < 19980000;
INSERT 0 91050840

cmdb=> REFRESH MATERIALIZED VIEW sales_customer_mv;
INFO:  Materialized view sales_customer_mv was incrementally updated successfully.
REFRESH
Time: 23197.663 ms (00:23.198)

cmdb=> select count(*) from sales_customer_mv;
  count
----------
 11997683
(1 row)

Time: 260.385 ms

1998年のデータの追加とリフレッシュ

cmdb=> INSERT INTO lineorder
SELECT * FROM lineorder_all WHERE lo_orderdate > 19980000 AND lo_orderdate < 19990000;
INSERT 0 53368288

cmdb=> REFRESH MATERIALIZED VIEW sales_customer_mv;
INFO:  Materialized view sales_customer_mv was incrementally updated successfully.
REFRESH
Time: 18362.876 ms (00:18.363)

cmdb=> select count(*) from sales_customer_mv;
  count
----------
 13987587
(1 row)

Time: 251.285 ms

上記クエリのメトリクス

ソースデータの追加と増分リフレッシュを交互に実行したことが影響しているかもしれませんが、想定以上にCPUの利用が高い印象です。

マテリアライズドビューの情報

リフレッシュ実行後の状態を確認します。STV_MV_INFOは、マテリアライズドビューの行、データが古くなっているかどうか、およびステータス情報が含まれます。以下の結果から「増分リフレッシュ」であることが確認できます。

  • is_stale:f(最新状態)
  • state:1(マテリアライズドビューは増分)
cmdb=# select * from STV_MV_INFO;
-[ RECORD 1 ]----+------------------------------------------------------------------------
db_name          | cmdb
schema           | cm_user
name             | sales_customer_mv
updated_upto_xid | 45002987
is_stale         | f
owner_user_name  |
state            | 1

マテリアライズドビューを一括で作成した時間の計測

増分リフレッシュ時間と比較するため、マテリアライズドビューを一括で作成した時間を計測します。

cmdb=> CREATE MATERIALIZED VIEW sales_customer_all_mv
cmdb->   DISTSTYLE KEY
cmdb->   DISTKEY(c_custkey)
cmdb->   SORTKEY(c_custkey, c_name, c_orderyear)
cmdb->   AS (
cmdb(>     SELECT
cmdb(>       c_custkey,
cmdb(>       c_name,
cmdb(>       trunc(lo_orderdate,-4) as c_orderyear,
cmdb(>       sum(lo_ordertotalprice) as c_ordertotal
cmdb(>     FROM lineorder l
cmdb(>     INNER JOIN customer c ON l.lo_custkey = c.c_custkey
cmdb(>     GROUP BY 1,2,3
cmdb(>   )
cmdb-> ;
CREATE MATERIALIZED VIEW
Time: 126897.630 ms (02:06.898)

検証結果

初回(1992年)と最後(1998年)の増分リフレッシュを除くと約30秒程度でした。マテリアライズドビューを一括で作成した時間が126秒でしたので、増分リフレッシュのほうが累計では時間がかかっていますが、明らかにフルリフフレッシュよりも処理時間が短縮できたことが確認できました。

ファクト(lineorder)の
追加レコード数
マテリアライズドビューの
追加レコード数
増分リフレッシュ時間(秒)
1992年 91248844 1999625 61.010
1993年 91007488 1999614 32.679
1994年 91044214 1999610 47.750
1995年 91016436 1999598 26.067
1996年 91301792 1999628 22.812
1997年 91050840 1999628 23.197
1998年 53368288 1989904 18.362

内部的な動きの確認

では、どのように増分リフレッシュが実現されているのか気になるところです。クエリ履歴からリフレッシュコマンド REFRESH MATERIALIZED VIEWを実行すると、内部的には以下の2つクエリが順に実行されることが確認できました。insertxiddeletexidなどの更新情報を利用して、増分リフレッシュを実現していることが推測されます。一方、汎用的な仕組みであるため、私がデータマートの更新に用いている更新クエリと比較して複雑になっており、それがCPUやIOの上昇の要因の一つであると考えられます。

CREATE TEMPORARY TABLE "mv_tbl__sales_customer_mv__0_tmp" AS(
  SELECT
    "derived_table1"."X1" AS "grvar_1",
    "derived_table1"."X2" AS "grvar_2",
    TRUNC("derived_table1"."X3", CAST(- 4 AS INT4)) AS "grvar_3",
    SUM(("derived_table1"."X4" * "derived_table1"."X5")) AS "aggvar_1",
    SUM("derived_table1"."X5") AS "num_rec"
  FROM
    ((
        SELECT
          "c"."c_custkey" AS "X1",
          "c"."c_name" AS "X2",
          "l"."lo_orderdate" AS "X3",
          "l"."lo_ordertotalprice" AS "X4",
          (CAST(1 AS INT4) * CAST(1 AS INT4)) AS "X5",
          "l"."lo_custkey" AS "X6"
        FROM
          "cm_user"."customer" AS "c",
          "cm_user"."lineorder" AS "l"
        WHERE
          (
            (
              CAST("c"."insertxid" AS INT8) > $1
            )
          AND (
              (
                CAST("c"."insertxid" AS INT8) <= $2
              )
            AND (
                CAST("c"."deletexid" AS INT8) > $3
              )
            )
          )
        AND (
            (
              CAST("l"."insertxid" AS INT8) <= $4
            )
          AND (
              CAST("l"."deletexid" AS INT8) > $5
            )
          )
      ) UNION ALL((
        SELECT
          "c"."c_custkey" AS "X1",
          "c"."c_name" AS "X2",
          "l"."lo_orderdate" AS "X3",
          "l"."lo_ordertotalprice" AS "X4",
          (CAST(1 AS INT4) * CAST(1 AS INT4)) AS "X5",
          "l"."lo_custkey" AS "X6"
        FROM
          "cm_user"."customer" AS "c",
          "cm_user"."lineorder" AS "l"
        WHERE
          (
            (
              CAST("c"."insertxid" AS INT8) <= $6
            )
          AND (
              CAST("c"."deletexid" AS INT8) > $7
            )
          )
        AND (
            (
              CAST("l"."insertxid" AS INT8) > $8
            )
          AND (
              (
                CAST("l"."insertxid" AS INT8) <= $9
              )
            AND (
                CAST("l"."deletexid" AS INT8) > $10
              )
            )
          )
      ) UNION ALL((
        SELECT
          "c"."c_custkey" AS "X1",
          "c"."c_name" AS "X2",
          "l"."lo_orderdate" AS "X3",
          "l"."lo_ordertotalprice" AS "X4",
          (CAST(- 1 AS INT4) * CAST(1 AS INT4)) AS "X5",
          "l"."lo_custkey" AS "X6"
        FROM
          "cm_user"."customer" AS "c",
          "cm_user"."lineorder" AS "l"
        WHERE
          (
            (
              CAST("c"."insertxid" AS INT8) <= $11
            )
          AND (
              (
                CAST("c"."deletexid" AS INT8) > $12
              )
            AND (
                CAST("c"."deletexid" AS INT8) <= $13
              )
            )
          )
        AND (
            (
              CAST("l"."insertxid" AS INT8) <= $14
            )
          AND (
              CAST("l"."deletexid" AS INT8) > $15
            )
          )
      ) UNION ALL(
      SELECT
        "c"."c_custkey" AS "X1",
        "c"."c_name" AS "X2",
        "l"."lo_orderdate" AS "X3",
        "l"."lo_ordertotalprice" AS "X4",
        (CAST(1 AS INT4) * CAST(- 1 AS INT4)) AS "X5",
        "l"."lo_custkey" AS "X6"
      FROM
        "cm_user"."customer" AS "c",
        "cm_user"."lineorder" AS "l"
      WHERE
        (
          (
            CAST("c"."insertxid" AS INT8) <= $16
          )
        AND (
            CAST("c"."deletexid" AS INT8) > $17
          )
        )
      AND (
          (
            CAST("l"."insertxid" AS INT8) <= $18
          )
        AND (
            (
              CAST("l"."deletexid" AS INT8) > $19
            )
          AND (
              CAST("l"."deletexid" AS INT8) <= $20
            )
          )
        )
    )))) AS "derived_table1"
  WHERE
    "derived_table1"."X1" = "derived_table1"."X6"
  GROUP BY
    "derived_table1"."X1",
    "derived_table1"."X2",
    TRUNC("derived_table1"."X3", CAST(- 4 AS INT4))
)
CREATE TEMPORARY TABLE "mv_tbl__sales_customer_mv__0__insert_tmp" AS (SELECT
    "mv_tbl__sales_customer_mv__0_tmp"."grvar_1",
    "mv_tbl__sales_customer_mv__0_tmp"."grvar_2",
    "mv_tbl__sales_customer_mv__0_tmp"."grvar_3",
    CASE
        WHEN "mv_tbl__sales_customer_mv__0"."aggvar_1" IS NULL THEN "mv_tbl__sales_customer_mv__0_tmp"."aggvar_1"
        ELSE "mv_tbl__sales_customer_mv__0"."aggvar_1" + "mv_tbl__sales_customer_mv__0_tmp"."aggvar_1"
    END AS "aggvar_1",
    CASE
        WHEN "mv_tbl__sales_customer_mv__0".num_rec IS NULL THEN "mv_tbl__sales_customer_mv__0_tmp".num_rec
        ELSE "mv_tbl__sales_customer_mv__0".num_rec + "mv_tbl__sales_customer_mv__0_tmp".num_rec
    END AS num_rec
FROM
    "mv_tbl__sales_customer_mv__0_tmp"
LEFT OUTER JOIN
    "cm_user"."mv_tbl__sales_customer_mv__0"
        ON "mv_tbl__sales_customer_mv__0"."grvar_1" = "mv_tbl__sales_customer_mv__0_tmp"."grvar_1"
        AND "mv_tbl__sales_customer_mv__0"."grvar_2" = "mv_tbl__sales_customer_mv__0_tmp"."grvar_2"
        AND "mv_tbl__sales_customer_mv__0"."grvar_3" = "mv_tbl__sales_customer_mv__0_tmp"."grvar_3"
WHERE
    "mv_tbl__sales_customer_mv__0_tmp".num_rec + NVL("mv_tbl__sales_customer_mv__0".num_rec, 0) > 0)

psqlからオブジェクトを参照するとsales_customer_mvというビューとmv_tbl__sales_customer_mv__0テーブルが追加されています。最終的にmv_tbl__sales_customer_mv__0を更新するクエリは確認できませんが、内部的に実行されているのではないかと考えられます。

cmdb=> \d
                    List of relations
 schema  |               name               | type  |  owner
---------+----------------------------------+-------+---------
 cm_user | mv_tbl__sales_customer_mv__0     | table | rdsdb
 cm_user | sales_customer_mv                | view  | cm_user

マテリアライズドビュー利用のベストプラクティス

複雑なクエリをビューにまとめたり、レコードやカラムの参照を制限が必要な場合は、まずは通常のビューで作成します。次に参照頻度が高いビューやパフォーマンスの改善が必要な場合はマテリアライズドビューに置き換えます。更に関連するソーステーブルや対象レコードが多く、テーブル間の仕様を把握して更新クエリの最適化による効率的なメンテナンスが可能な場合は、データマートの置き換えを検討すると良いでしょう。

これまでマテリアライズドビューのワークアラウンドとして利用してきたデータマートは、テーブルの仕様を把握して更新系クエリを組み合わせてUPSERTしていますので、手間がかかる一方で最適化による効率的なメンテナンスができるというメリットもあります。以下の条件に従い、これらを選択することをおすすめします。

  • 通常のビュー
    • 複雑なクエリやビジネスロジックを集約したい
    • レコードやカラムのアクセスコントロール
  • マテリアライズドビュー(通常のビューを置き換える)
    • ソーステーブルの更新頻度が低い
    • スループットやレスポンスの改善が求められる
    • 参照頻度が高い通常のビューの置き換え
  • データマート(マテリアライズドビューを置き換える)
    • 関連するソーステーブルや対象レコードが多い
    • テーブル間の仕様を把握して更新クエリの最適化による効率的なメンテナンス

最後に

Redshiftのマテリアライズドビューは、通常のビューと同じ様に作成することが可能であり、任意のタイミングでリフレッシュを実行することで更新できます。リフレッシュを実行すると、増分リフレッシュを試み、インクリメンタルに更新できない場合はフルリフレッシュされます。マテリアライズドビューは、Redshiftのテーブルと同様に分散キーやソートキーによる最適化が可能です。増分リフレッシュは、テーブル内部のinsertxiddeletexidなどの更新情報を用いて実現していますので、ユーザーはマテリアライズドビューをリフレッシュするだけで簡単に更新できます。

通常のビュー、マテリアライズドビュー、データマートの使い分けについては、「マテリアライズドビュー利用のベストプラクティス」を参考にしていただけると幸いです。

合わせて読みたい

フルリフレッシュではなく、増分リフレッシュになる条件については以下のブログを御覧ください。

Amazon Redshift: マテリアライズド・ビュー(Materialized View)のリフレッシュ(REFRESH)について