Redshiftのコンパイル済みコード再利用(IN句要素数編)

#はじめに
Redshiftを触ったことがある方ならご存じと思いますが、Redshiftへクエリを発行する際、１回目のクエリ発行時はコンパイル処理が走るためそこそこ時間がかかり、2回目以降はコンパイル済みコードが再利用されるため高速になります。（Redshiftでのパフォーマンス検証は2回目以降のクエリで判断することが一般的）
そのため、コンパイル済みコードが再利用されることを前提として処理を検討していたところ、うまく再利用できないケースがあり、実機確認をしてみてわかった結果を記録した記事になります。

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

#本確認のきっかけになったクエリ
###コンパイル済みコードが流用できたケース

```SQL
#クエリ1
select col1,col2 from tbl1 where col1 = '111';
#クエリ2
select col1,col2 from tbl1 where col1 = '222';  --コンパイル済みコードを再利用できた
```
クエリ1を発行後(クエリ1発行時はコンパイルが実施)、クエリ2を発行するとコンパイル済みコードが再利用された。(コンパイル済みコードの再利用有無はSVL_COMPILEテーブルで確認可能)

###コンパイル済みコードが流用できなかったケース

```SQL
#クエリ1
select col1,col2 from tbl1 where col1 in ('111');
#クエリ2
select col1,col2 from tbl1 where col1 in ('111','222');    --コンパイル済みコードを再利用できなかった
```
クエリ1を発行後(クエリ1発行時はコンパイルが実施)、クエリ2を発行してもコンパイル処理が実施された。
=条件でコンパイル済みコードが再利用できていたので、IN句でも再利用できるかと単純に思っていたところ、要素数が変わると再利用できなかったため、以下実機確認を実施。

#実機確認
IN句内の要素数を1個、2個と増やしていきExplainの内容を調査。(ついでにクエリも発行し、コンパイル済みコードの再利用有無も確認）

###要素数1個

```SQL
db1=# explain select col1,col2 from tbl1 where col1 in ('111');
                       QUERY PLAN
 --------------------------------------------------------
 XN Seq Scan on tbl1  (cost=0.00..0.04 rows=1 width=14)
   Filter: (col1 = '111'::bpchar)
```

###要素数2個
OR条件で実行計画が作成。OR条件が増えているため要素数1のコンパイル済みコードは再利用されない。

```SQL
db1=# explain select col1,col2 from tbl1 where col1 in ('111','222');
                          QUERY PLAN
--------------------------------------------------------------
 XN Seq Scan on tbl1  (cost=0.00..0.04 rows=2 width=14)
   Filter: ((col1 = '111'::bpchar) OR (col1 = '222'::bpchar))
```
###要素数10個
10個まではOR条件が増えていく形で実行計画が作成。OR条件が増えているため異なる要素数間ではコンパイル済みコードが再利用されない。

```SQL
db1=# explain select col1,col2 from tbl1 where col1 in ('111','222','333','444','555','666','777','888','999','000');
                                                                                                                                  QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 XN Seq Scan on tbl1  (cost=0.00..0.10 rows=3 width=14)
   Filter: ((col1 = '000'::bpchar) OR (col1 = '111'::bpchar) OR (col1 = '222'::bpchar) OR (col1 = '333'::bpchar) OR (col1 = '444'::bpchar) OR (col1 = '555'::bpchar) OR (col1 = '666'::bpchar) OR (col1 = '777'::bpchar) OR (col1 = '888'::bpchar) OR (col1 = '999'::bpchar))
```
###要素数11個
11個を超えると配列を使用した実行計画になり、要素数を増やすと配列内の要素が増える形になる。
また、要素数11個以上のクエリが1回発行されると、要素数11個以上のクエリ（要素数100個でも、要素数1000個でも）はコンパイル済みコードが再利用された。

```SQL
db1=# explain select col1,col2 from tbl1 where col1 in ('111','222','333','444','555','666','777','888','999','000','123');
                                     QUERY PLAN
------------------------------------------------------------------------------------
 XN Seq Scan on tbl1  (cost=0.00..0.11 rows=3 width=14)
   Filter: (col1 = ANY ('{000,111,123,222,333,444,555,666,777,888,999}'::bpchar[]))
```


#クエリステップの確認
[SVL_QUERY_REPORT](https://docs.aws.amazon.com/ja_jp/redshift/latest/dg/r_SVL_QUERY_REPORT.html)で発行されたクエリのクエリステップを確認。


###IN句の要素数が1～10個の場合のクエリステップ
tbl1から単純にscan。

```SQL
db1=# select query,slice,segment,step,label from SVL_QUERY_REPORT where query in (225) order by query,slice,segment,step;
 query | slice | segment | step |                  label
-------+-------+---------+------+-----------------------------------------
   225 |     0 |       0 |    0 | scan   tbl=101559 name=tbl1
   225 |     0 |       0 |    1 | project
   225 |     0 |       0 |    2 | project
   225 |     0 |       0 |    3 | return
   225 | 12811 |       1 |    0 | scan   tbl=810 name=Internal Worktable
   225 | 12811 |       1 |    1 | return
```

###IN句の要素数が11個以上のクエリステップ
Internal WorktableにIN句内の要素を格納※し、Internal Worktableとtbl1を結合して抽出するクエリステップになっている。
そのため、11個以上は要素数が増えてもコンパイル済みコードが再利用されていると考えらえる。
※Internal WorktableにIN句内の要素を本当に格納しているかの裏が取れていないが、おそらく合っているはず。

```SQL
db1=# select query,slice,segment,step,label from SVL_QUERY_REPORT where query in (307) order by query,slice,segment,step;
 query | slice | segment | step |                  label
-------+-------+---------+------+-----------------------------------------
   307 |     0 |       1 |    0 | scan   tbl=1078 name=Internal Worktable
   307 |     0 |       1 |    1 | hash   tbl=843
   307 |     0 |       2 |    0 | scan   tbl=101559 name=tbl1
   307 |     0 |       2 |    1 | project
   307 |     0 |       2 |    2 | project
   307 |     0 |       2 |    3 | return
   307 |     1 |       1 |    0 | scan   tbl=1078 name=Internal Worktable
   307 |     1 |       1 |    1 | hash   tbl=843
   307 |     1 |       2 |    0 | scan   tbl=101559 name=tbl1
   307 |     1 |       2 |    1 | project
   307 |     1 |       2 |    2 | project
   307 |     1 |       2 |    3 | return
   307 | 12811 |       0 |    0 | scan   tbl=0 name=Internal Worktable
   307 | 12811 |       0 |    1 | bcast
   307 | 12811 |       3 |    0 | scan   tbl=1079 name=Internal Worktable
   307 | 12811 |       3 |    1 | return
```

#Redshift開発者ガイド(公式)
Redshift開発者ガイドにも上記挙動に関する記載がありました。（10個"未満"なのか、"以下"なのかはちょっと怪しいですが・・）
>大規模 IN リストの最適化
>クエリのパフォーマンスを最適化するために、10 個を超える値が含まれる IN リストは内部的にスカラー配列として評価されます。
>10 個未満の値が含まれる IN リストは一連の OR 述語として評価されます。

Redshift開発者ガイド：[IN条件](https://docs.aws.amazon.com/ja_jp/redshift/latest/dg/r_in_condition.html)

#結論
IN句を使用する単純なクエリで、IN句の要素数を変えた場合にコンパイル済みコードが再利用されるかどうかは以下の通り。
・要素数が10個以下の場合は、同一要素数間であればコンパイル済みコードが再利用されるが、要素数が異なると再利用されない。
・要素数が11個以上の場合は、一度要素数が11個以上のクエリが発行されていれば、要素数が異なっていてもコンパイル済みコードが再利用される。

#補足
・別のテーブル定義で検証していたときには、要素数が5個以上で配列を使用した実行計画になったこともあり、テーブル定義、クエリによって配列形式となる要素数は異なる可能性あり。

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

#参考URL
[クエリパフォーマンスに影響を与える要因](https://docs.aws.amazon.com/ja_jp/redshift/latest/dg/c-query-performance.html)
[Amazon Redshift がコンパイル時間を大幅に改善することで、コールドクエリのパフォーマンスが向上]
(https://aws.amazon.com/jp/about-aws/whats-new/2020/06/amazon-redshift-now-delivers-better-cold-query-performance/)