Redshiftで手動でAnalyzeを発行した方がよいケース

#はじめに
Redshiftにも他のDBMSと同様、統計情報を取得するための[Analyze](https://docs.aws.amazon.com/ja_jp/redshift/latest/dg/r_ANALYZE.html)コマンドがある。
ただ、AWSさんの「運用機能を可能な限り自動化し、開発者が本来注力すべき業務処理に注力できるように」との思いによって各種機能が年々Auto化（個人的には[分散キーのAuto化](https://aws.amazon.com/jp/about-aws/whats-new/2020/12/amazon-redshift-announces-automatic-table-optimization/)が最後の砦だと思っていたので、去年のre:Inventはインパクトが大きかった）されたことで、Analyzeコマンドを発行する機会は減ってきていると思われる。
本記事では2021/4現在における自動で発行されるAnalyzeを整理するとともに、Analyzeを手動で発行した方がよいケースについて個人的な見解を整理する。

#実機環境
リージョン：東京
インスタンス：dc2.large × 1ノード
※Redshiftのバージョンは2021/4/3時点での最新バージョンを使用

#自動的に発行されるAnalyeze
Redshiftで自動的に発行されるAnalyzeには大きく２種類存在。
①コマンドとセットで発行されるAnalyze
②AutoAnalyze機能により発行されるAnalyze

#コマンドとセットで発行されるAnalyze
Analyzeがセットで（自動的に）発行されるコマンドは下記４つ。
①COPY（STATUPDATEオプションとCOPY先テーブルのデータ状態により発行有無が変わる）
②CRETATE TALBE AS
③CREATE TEMP TABLE AS
④SELECT INTO
参考：Redshift開発者ガイド内「[テーブルを分析する](https://docs.aws.amazon.com/ja_jp/redshift/latest/dg/t_Analyzing_tables.html)」

また上記の派生として、下記コマンドについてもAnalyzeが発行される。
⑤CREATE MATERIALIZED VIEW
⑥REFRESH MATERIALIZED VIEW（増分更新の場合は発行されない）
※細かな挙動については記事の後半の「[MATERIALIZED VIEWに関する挙動について](#materialized-viewに関する挙動について)」に記載

#AutoAnalyze機能により発行されるAnalyze
COPYコマンドでtbl1テーブルにデータを格納し、AutoAnalyze機能の動きを[STL_ANALYZE](https://docs.aws.amazon.com/ja_jp/redshift/latest/dg/r_STL_ANALYZE.html)テーブルから観察。

```SQL
db1=# select distinct a.xid, trim(t.name) as name, a.status, a.rows, a.modified_rows, CONVERT_TIMEZONE('JST',a.starttime) as starttime_j,is_auto,is_background,substring(auto_analyze_phase,1,20) from STL_ANALYZE a join stv_tbl_perm t on t.id=a.table_id order by starttime;
  xid  |  name  |     status      |   rows   | modified_rows |         starttime_j        | is_auto | is_background |      substring
-------+--------+-----------------+----------+---------------+----------------------------+---------+---------------+----------------------
  7027 | tbl1   | Full            | 20000000 |      20000000 | 2021-03-28 08:30:45.877363 | t       | f             | Not Applicable        ---COPYコマンドでデータをLoad
  7711 | tbl1   | Skipped         | 20000000 |             0 | 2021-03-28 08:43:08.084746 | t       | t             | (Phase 1) Processing
  7711 | tbl1   | Skipped         | 20000000 |             0 | 2021-03-28 08:43:08.085832 | t       | t             | (Phase 2) Processing
  7711 | tbl1   | Skipped         | 20000000 |             0 | 2021-03-28 08:43:08.08619  | t       | t             | (Phase 3) Processing
 10959 | tbl1   | Skipped         | 20000000 |             0 | 2021-03-28 09:43:09.346016 | t       | t             | (Phase 1) Processing
 10971 | tbl1   | Skipped         | 20000000 |             0 | 2021-03-28 09:43:25.863039 | t       | t             | (Phase 2) Processing
 10971 | tbl1   | Skipped         | 20000000 |             0 | 2021-03-28 09:43:25.863496 | t       | t             | (Phase 3) Processing
 14338 | tbl1   | Skipped         | 20000000 |             0 | 2021-03-28 10:43:10.401768 | t       | t             | (Phase 1) Processing
 14351 | tbl1   | Skipped         | 20000000 |             0 | 2021-03-28 10:43:20.258824 | t       | t             | (Phase 2) Processing
 14351 | tbl1   | Skipped         | 20000000 |             0 | 2021-03-28 10:43:20.259437 | t       | t             | (Phase 3) Processing

```
8時から10時の間だけではあるが、各時刻の43分にAutoAnalyzeが発行されており、おそらく**1時間に1回**のペースでAutoAnalyzeが稼働している模様。
上記ログはCOPYコマンド以降、当該テーブルにクエリを発行していないケースだが、別途COPYコマンド後にテーブルに更新クエリを発行するケースも試行したが1時間に1回のペースは変わらなかった。（ただ、数回程度の試行のためAutoAnalyzeが稼働するケースがある可能性はあり）

#Analyzeを明示的に発行した方がよいケース
端的にまとめると「Analyzeが自動的に発行されないコマンドにより作成したテーブルを、1時間以内に参照し、かつ、統計情報が最新でないとパフォーマンスが出ないような参照クエリがある場合」。
（全然端的じゃない・・・）

基本的にRedshiftへ大量データを投入するときはCopyコマンド、CTASあたりかと思うが、他にあるとすれば[INSERT INTO SELECT](https://docs.aws.amazon.com/ja_jp/redshift/latest/dg/c_Examples_of_INSERT_30.html)でのデータ投入か。
※上記で記載した「SELECT INTO」とは別のコマンド。
※ISERT INTO SELECTでデータ投入した場合には、自動でAnalyzeが発行されないことも実機確認済み


#MATERIALIZED VIEWに関する挙動について

以下の流れでコマンドを発行し、Analyze関連で内部的に発行されたコマンドを[svl_statementtext](https://docs.aws.amazon.com/ja_jp/redshift/latest/dg/r_SVL_STATEMENTTEXT.html)テーブルから、テーブルへのAnalyze実績を[stl_analyze](https://docs.aws.amazon.com/ja_jp/redshift/latest/dg/r_STL_ANALYZE.html)テーブルから確認。
　①tbl1にCOPYコマンドで3000万件をLoad
　②CREATE MATERIALIZED VIEW
　③tbl1にCOPYコマンドで2000万件を追加Load（STATUPDATEオプション設定無し）
　④REFRESH MATERIALIZED VIEW（増分更新）
　⑤tbl1にCOPYコマンドで2000万件を追加Load（STATUPDATEオプションON）
　⑥tbl1をtruncata
　⑦tbl1にCOPYコマンドで3000万件をLoad
　⑧REFRESH MATERIALIZED VIEW（全量更新）

###①tbl1にCOPYコマンドで3000万件をLoad
空テーブルに対しCOPYコマンドを発行するとセットでAnalyzeが発行される。

```SQL
db1=# select xid, CONVERT_TIMEZONE('JST',starttime) as starttime,datediff(sec,starttime,endtime ) as secs, substring(text, 1, 300) from svl_statementtext where sequence = 0 and xid in (select xid from svl_statementtext s where s.text like 'padb_fetch_sample%' ) order by xid,starttime;
 xid  |         starttime          | secs |                                                                                                substring
------+----------------------------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 2616 | 2021-04-03 19:47:24.273663 |   64 | copy tbl1 from 's3://xxx';
 2616 | 2021-04-03 19:48:28.974304 |   20 | Analyze tbl1
 2616 | 2021-04-03 19:48:28.975073 |   14 | padb_fetch_sample: select * from tbl1
 2616 | 2021-04-03 19:48:42.26644  |    6 | padb_fetch_sample: select * from tbl1
 2616 | 2021-04-03 19:48:48.413595 |    0 | COMMIT

db1=# select distinct a.xid, trim(t.name) as name, a.status, a.rows, a.modified_rows, CONVERT_TIMEZONE('JST',a.starttime) as starttime_j ,is_auto,is_background,substring(auto_analyze_phase,1,20) as phase from stl_analyze a join stv_tbl_perm t  on t.id=a.table_id order by starttime;
 xid  | name |     status      |   rows   | modified_rows |        starttime_j         | is_auto | is_background |     phase
------+------+-----------------+----------+---------------+----------------------------+---------+---------------+----------------
 2616 | tbl1 | Full            | 30000000 |      30000000 | 2021-04-03 19:48:28.974304 | t       | f             | Not Applicable
```


###②CREATE MATERIALIZED VIEW
MATERIALIZED VIEWをCreateしたタイミングでAnalyzeコマンドが発行される。

```SQL
db1=# select xid, CONVERT_TIMEZONE('JST',starttime) as starttime,datediff(sec,starttime,endtime ) as secs, substring(text, 1, 300) from svl_statementtext where sequence = 0 and xid in (select xid from svl_statementtext s where s.text like 'padb_fetch_sample%' ) order by xid,starttime;
 xid  |         starttime          | secs |                                                                                                substring
------+----------------------------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 2798 | 2021-04-03 19:50:50.25773  |   37 | Create MATERIALIZED VIEW mv_tbl1\nas select * from tbl1;
 2798 | 2021-04-03 19:51:27.151279 |    0 | Create MATERIALIZED VIEW mv_tbl1\nas select * from tbl1;
 2798 | 2021-04-03 19:51:27.15358  |    0 | Create MATERIALIZED VIEW mv_tbl1\nas select * from tbl1;
 2798 | 2021-04-03 19:51:27.165518 |   16 | Analyze mv_tbl__mv_tbl1__0
 2798 | 2021-04-03 19:51:27.165717 |   16 | padb_fetch_sample: select * from mv_tbl__mv_tbl1__0
 2798 | 2021-04-03 19:51:43.301068 |    0 | padb_fetch_sample: select * from mv_tbl__mv_tbl1__0
 2798 | 2021-04-03 19:51:43.847555 |    0 | COMMIT

db1=# select distinct a.xid, trim(t.name) as name, a.status, a.rows, a.modified_rows, CONVERT_TIMEZONE('JST',a.starttime) as starttime_j ,is_auto,is_background,substring(auto_analyze_phase,1,20) as phase from stl_analyze a join stv_tbl_perm t  on t.id=a.table_id order by starttime;
 xid  |        name        |     status      |   rows   | modified_rows |        starttime_j         | is_auto | is_background |     phase
------+--------------------+-----------------+----------+---------------+----------------------------+---------+---------------+----------------
 2616 | tbl1               | Full            | 30000000 |      30000000 | 2021-04-03 19:48:28.974304 | t       | f             | Not Applicable
 2798 | mv_tbl__mv_tbl1__0 | Full            | 30000000 |             0 | 2021-04-03 19:51:27.165518 | t       | f             | Not Applicable
```

###③tbl1にCOPYコマンドで2000万件をLoad（STATUPDATEオプション設定無し）
Analyzeの発行無し。
データ有りのテーブルにSTATUPDATEオプション無しでCOPYを実行した場合はAnalyzeは発行されない。

###④REFRESH MATERIALIZED VIEW（増分更新）
Analyzeの発行無し。
増分更新の場合はAnalyzeは発行されない模様。
また、増分更新だったかどうかはリターンメッセージで確認可能。

```SQL
db1=# REFRESH MATERIALIZED VIEW mv_tbl1;
INFO:  Materialized view mv_tbl1 was incrementally updated successfully.
```
ちなみに、全量更新の場合のメッセージは下記。

```SQL
db1=# REFRESH MATERIALIZED VIEW mv_tbl1;
INFO:  Materialized view mv_tbl1 was recomputed successfully.
```

###⑤tbl1にCOPYコマンドで2000万件をLoad（STATUPDATEオプションON）
STATUPDATEオプションONでCOPYコマンドを実行すると想定通りAnalyzeが自動で実行。

```SQL
db1=# select xid, CONVERT_TIMEZONE('JST',starttime) as starttime,datediff(sec,starttime,endtime ) as secs, substring(text, 1, 300) from svl_statementtext where sequence = 0 and xid in (select xid from svl_statementtext s where s.text like 'padb_fetch_sample%' ) order by xid,starttime;
 xid  |         starttime          | secs |                                                                                                substring
------+----------------------------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 3503 | 2021-04-03 20:02:57.423461 |   46 | copy tbl1 from 's3://xxx';
 3503 | 2021-04-03 20:03:43.525802 |   33 | Analyze tbl1
 3503 | 2021-04-03 20:03:43.526022 |   28 | padb_fetch_sample: select * from tbl1
 3503 | 2021-04-03 20:04:11.532643 |    5 | padb_fetch_sample: select * from tbl1
 3503 | 2021-04-03 20:04:16.641362 |    0 | COMMIT

db1=# select distinct a.xid, trim(t.name) as name, a.status, a.rows, a.modified_rows, CONVERT_TIMEZONE('JST',a.starttime) as starttime_j ,is_auto,is_background,substring(auto_analyze_phase,1,20) as phase from stl_analyze a join stv_tbl_perm t  on t.id=a.table_id order by starttime;
 xid  |        name        |     status      |   rows   | modified_rows |        starttime_j         | is_auto | is_background |     phase
------+--------------------+-----------------+----------+---------------+----------------------------+---------+---------------+----------------
 2616 | tbl1               | Full            | 30000000 |      30000000 | 2021-04-03 19:48:28.974304 | t       | f             | Not Applicable
 2798 | mv_tbl__mv_tbl1__0 | Full            | 30000000 |             0 | 2021-04-03 19:51:27.165518 | t       | f             | Not Applicable
 3503 | tbl1               | Full            | 70000000 |      40000000 | 2021-04-03 20:03:43.525802 | t       | f             | Not Applicable
```

###⑥tbl1をtruncata
（MATERIALIZED VIEWを全量更新するため一度tbl1をtruncate）

###⑦tbl1にCOPYコマンドで3000万件をLoad
再度、tbl1に3000万件を投入。

```SQL
db1=# select xid, CONVERT_TIMEZONE('JST',starttime) as starttime,datediff(sec,starttime,endtime ) as secs, substring(text, 1, 300) from svl_statementtext where sequence = 0 and xid in (select xid from svl_statementtext s where s.text like 'padb_fetch_sample%' ) order by xid,starttime;
 xid  |         starttime          | secs |                                                                                                substring
------+----------------------------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 3782 | 2021-04-03 20:08:08.798301 |   60 | copy tbl1 from 's3://xxx';
 3782 | 2021-04-03 20:09:08.044916 |   12 | Analyze tbl1
 3782 | 2021-04-03 20:09:08.045211 |   10 | padb_fetch_sample: select * from tbl1
 3782 | 2021-04-03 20:09:18.211502 |    2 | padb_fetch_sample: select * from tbl1
 3782 | 2021-04-03 20:09:20.301491 |    0 | COMMIT

db1=# select distinct a.xid, trim(t.name) as name, a.status, a.rows, a.modified_rows, CONVERT_TIMEZONE('JST',a.starttime) as starttime_j ,is_auto,is_background,substring(auto_analyze_phase,1,20) as phase from stl_analyze a join stv_tbl_perm t  on t.id=a.table_id order by starttime;
 xid  |        name        |     status      |   rows   | modified_rows |        starttime_j         | is_auto | is_background |     phase
------+--------------------+-----------------+----------+---------------+----------------------------+---------+---------------+----------------
 2616 | tbl1               | Full            | 30000000 |      30000000 | 2021-04-03 19:48:28.974304 | t       | f             | Not Applicable
 2798 | mv_tbl__mv_tbl1__0 | Full            | 30000000 |             0 | 2021-04-03 19:51:27.165518 | t       | f             | Not Applicable
 3503 | tbl1               | Full            | 70000000 |      40000000 | 2021-04-03 20:03:43.525802 | t       | f             | Not Applicable
 3782 | tbl1               | Full            | 30000000 |      30000000 | 2021-04-03 20:09:08.044916 | t       | f             | Not Applicable
```

###⑧REFRESH MATERIALIZED VIEW（全量更新）
元テーブルがまるっと入れ替わった状態でREFRESHコマンドを発行すると、CTASで内部テーブルを作成し当該テーブルをMATERIALIZED VIEWに置き換えるような内部クエリが発行され、合わせてAnalyzeも発行。

```SQL
db1=# select xid, CONVERT_TIMEZONE('JST',starttime) as starttime,datediff(sec,starttime,endtime ) as secs, substring(text, 1, 300) from svl_statementtext where sequence = 0 and xid in (select xid from svl_statementtext s where s.text like 'padb_fetch_sample%' ) order by xid,starttime;
 xid  |         starttime          | secs |                                                                                                substring
------+----------------------------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 3903 | 2021-04-03 20:10:24.456465 |   50 | REFRESH MATERIALIZED VIEW mv_tbl1;
 3903 | 2021-04-03 20:10:24.457537 |   50 | CALL public.mv_sp__mv_tbl1__0_1(0, 3902, 1, '(0)');
 3903 | 2021-04-03 20:10:24.46982  |   39 |  CREATE TABLE "public"."mv_tbl__mv_tbl1__0_recomputed" BACKUP YES DISTSTYLE KEY DISTKEY(2) COMPOUND SORTKEY(1, 2) AS (SELECT "tbl1".xxx
 3903 | 2021-04-03 20:11:03.50325  |   11 | Analyze mv_tbl__mv_tbl1__0_recomputed
 3903 | 2021-04-03 20:11:03.503481 |   11 | padb_fetch_sample: select * from mv_tbl__mv_tbl1__0_recomputed
 3903 | 2021-04-03 20:11:14.327198 |    0 | padb_fetch_sample: select * from mv_tbl__mv_tbl1__0_recomputed
 3903 | 2021-04-03 20:11:14.825666 |    0 | CREATE OR REPLACE VIEW "public"."mv_tbl1" AS (SELECT "derived_table1".xxx
 3903 | 2021-04-03 20:11:14.827848 |    0 | DROP TABLE "public"."mv_tbl__mv_tbl1__0"
 3903 | 2021-04-03 20:11:14.831234 |    0 | ALTER TABLE "public"."mv_tbl__mv_tbl1__0_recomputed" RENAME TO "mv_tbl__mv_tbl1__0"
 3903 | 2021-04-03 20:11:14.832263 |    0 | CREATE OR REPLACE VIEW "public"."mv_tbl1" AS (SELECT "derived_table1".xxx
 3903 | 2021-04-03 20:11:14.835738 |    0 | COMMIT

db1=# select distinct a.xid, trim(t.name) as name, a.status, a.rows, a.modified_rows, CONVERT_TIMEZONE('JST',a.starttime) as starttime_j ,is_auto,is_background,substring(auto_analyze_phase,1,20) as phase from stl_analyze a join stv_tbl_perm t  on t.id=a.table_id order by starttime;
 xid  |        name        |     status      |   rows   | modified_rows |        starttime_j         | is_auto | is_background |     phase
------+--------------------+-----------------+----------+---------------+----------------------------+---------+---------------+----------------
 2616 | tbl1               | Full            | 30000000 |      30000000 | 2021-04-03 19:48:28.974304 | t       | f             | Not Applicable
 3503 | tbl1               | Full            | 70000000 |      40000000 | 2021-04-03 20:03:43.525802 | t       | f             | Not Applicable
 3782 | tbl1               | Full            | 30000000 |      30000000 | 2021-04-03 20:09:08.044916 | t       | f             | Not Applicable
 3903 | mv_tbl__mv_tbl1__0 | Full            | 30000000 |             0 | 2021-04-03 20:11:03.50325  | t       | f             | Not Applicable
```


#終わりに
記載が誤っている点や、わかりづらい点がありましたらコメントいただけますと幸いです。