【クエリ性能10倍】Snowflakeにてクエリ実行時間を10分の１にするクラスタリングキーの利用のすすめ

この記事は [Snowflake Advent Calendar 2020](https://adventar.org/calendars/5085) の9日目の記事です。

## 概要
Snowflakeにてクエリプルーニング（クエリ実行時のパーテイション参照のスキップ）を行う要因となるクラスタリングキーの利用方法を紹介します。6億レコードのテーブルにて指定期間の集計を行うクエリで検証したところ、約10倍の性能向上を確認できました。

クラスタリングキーを設定しない場合の実行時間を基準とした実行時間の比率を含めた性能検証結果を下記表に示します。単一列にクラスタリングキーとして指定したほうがいいが、複数列でクラスタリングキーに設定しても向上されるようです。

| 番号 | 施策                                               | 実行時間の比率 | Partitions scanned | Bytes scanned |
| ---- | -------------------------------------------------- | ------------ | ------------------ | ------------- |
| 1    | クラスタリングキーの設定なし                       | 1            | 10863              | 26.80GB       |
| 2    | L_SHIPDATE列にクラスタリングキーを設定             | 10  | 1241               | 2.30GB        |
| 3    | L_SHIPDATE列を含む複数列にクラスタリングキーを設定 | 7  | 1613               | 3.66GB        |


## 結論
よく利用する大規模なファクトテーブルに対しては、利用する複数の日付列をクラスタリング列に設定したほうがよさそうです。ただし、自動クラスタリングにコストがかかるため、他頻度で更新するテーブルやあまり利用しないテーブルへ設定する場合に、コストメリットの考慮が必要です。

ドキュメントにおいても、日付列に設定が勧められております。

> - 選択フィルターで最もアクティブに使用されるクラスタ列。日付ベースのクエリに関係する多くのファクトテーブル（例：WHERE invoice_date > x AND 請求日 <= y」）の場合、日付列を選択することをお勧めします。

引用元：https://docs.snowflake.com/ja/user-guide/tables-clustering-keys.html#strategies-for-selecting-clustering-keys

## クラスタリングキー
### クラスタリングキーとは
ドキュメントにて下記のように説明されております。
> クラスタリングキーは、同じ マイクロパーティション 内のテーブル内のデータを共存させるために明示的に指定されたテーブル（またはテーブル上の式）の列のサブセットです。

引用元：https://docs.snowflake.com/ja/user-guide/tables-clustering-keys.html#what-is-a-clustering-key

### クラスタリングキーのメンテナンス
下記のドキュメントに記載がある通り、クラスタリングキーのメンテナンスは自動で実施されます。

[自動クラスタリング — Snowflake Documentation](https://docs.snowflake.com/ja/user-guide/tables-auto-reclustering.html)

"AUTOMATIC_CLUSTERING"として課金されるようです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/f3b369ec-6b4e-04be-f167-edc0445d24f5.png)

なお、手動クラスタリングは実施できなくなったようです。
> 2020年5月現在、手動再クラスタリングはすべてのアカウントで廃止されました。

引用元：[手動再クラスタリング--- 非推奨 — Snowflake Documentation](https://docs.snowflake.com/ja/user-guide/tables-clustering-manual.html)

## 検証方法
### 1. 検証用データベースとテーブルを作成
```sql
CREATE OR REPLACE DATABASE WK_PRUNING_TEST;

USE WK_PRUNING_TEST;
Create or replace table LINEITEM (
  L_ORDERKEY	NUMBER(38,0)
  ,L_PARTKEY	NUMBER(38,0)
  ,L_SUPPKEY	NUMBER(38,0)
  ,L_LINENUMBER	NUMBER(38,0)
  ,L_QUANTITY	NUMBER(12,2)
  ,L_EXTENDEDPRICE	NUMBER(12,2)
  ,L_DISCOUNT	NUMBER(12,2)
  ,L_TAX	NUMBER(12,2)
  ,L_RETURNFLAG	VARCHAR(255)
  ,L_LINESTATUS	VARCHAR(255)
  ,L_SHIPDATE	DATE
  ,L_COMMITDATE	DATE
  ,L_RECEIPTDATE	DATE
  ,L_SHIPINSTRUCT	VARCHAR(255)
  ,L_SHIPMODE	VARCHAR(255)
  ,L_COMMENT	VARCHAR(255)
);

/*
--下記コードは本番環境で実施しないようにしてください。
--検証のために、リザルトキャッシュ機能をオフにします。
use role accountadmin;
ALTER ACCOUNT SET USE_CACHED_RESULT = FALSE;
SHOW PARAMETERS LIKE '%USE_CACHED_RESULT%'

--検証完了後、リザルトキャッシュ機能をオンにします。
use role accountadmin;
ALTER ACCOUNT SET USE_CACHED_RESULT = TRUE;
SHOW PARAMETERS LIKE '%USE_CACHED_RESULT%'
*/
```

### 2. サンプルデータの取り込み
```sql
USE WK_PRUNING_TEST;
TRUNCATE TABLE LINEITEM;
INSERT INTO LINEITEM
SELECT 
    *
    FROM 
        "SNOWFLAKE_SAMPLE_DATA"."TPCH_SF1000"."LINEITEM";
  
-- 5,999,989,709であることを確認
SELECT COUNT(*) FROM LINEITEM
```

### 3. 検証用テーブルの作成
```sql
CREATE OR REPLACE TABLE LINEITEM_CLONE_1 CLONE LINEITEM;
CREATE OR REPLACE TABLE LINEITEM_CLONE_2 CLONE LINEITEM;
```

#### 4. 検証用テーブル1のL_SHIPDATE列にクラスタリングキーを設定
```sql
ALTER TABLE "PUBLIC"."LINEITEM_CLONE_1" CLUSTER BY (L_SHIPDATE);

--cluster_by列が、LINEAR(L_SHIPDATE)となっていることを確認
show tables like 'LINEITEM_CLONE_1'; 
```

#### 5. 検証用テーブル2のL_COMMITDATE列、L_COMMITDATE列、L_SHIPDATE列にクラスタリングキーを設定
```sql
/*
--カーディナリティチェック
--Docsより、"低いカーディナリティ列の前に高いカーディナリティ列を配置すると、後者の列でのクラスタリングの有効性が低下します。"との記載あり
SELECT
    approx_count_distinct(L_SHIPDATE)
    ,approx_count_distinct(L_COMMITDATE)
    ,approx_count_distinct(L_COMMITDATE)
    FROM LINEITEM
;
*/
ALTER TABLE "PUBLIC"."LINEITEM_CLONE_2" CLUSTER BY (L_COMMITDATE,L_COMMITDATE,L_SHIPDATE) ;

--cluster_by列が、LINEAR(L_COMMITDATE,L_COMMITDATE,L_SHIPDATE)となっていることを確認
show tables like 'LINEITEM_CLONE_2';
```

#### 6. 自動で再クラスタリグされるため、少し待機後、検証用クエリを実行
```sql
SELECT
    SUM(L_EXTENDEDPRICE)
    FROM 
        "PUBLIC"."LINEITEM"
    WHERE L_SHIPDATE >= '1998-01-01'
;

SELECT
    SUM(L_EXTENDEDPRICE)
    FROM 
        "PUBLIC"."LINEITEM_CLONE_1"
    WHERE L_SHIPDATE >= '1998-01-01'
;
    

SELECT
    SUM(L_EXTENDEDPRICE)
    FROM 
        "PUBLIC"."LINEITEM_CLONE_2"
    WHERE L_SHIPDATE >= '1998-01-01'
;
```

### 7. Historyタブにてクエリの実行結果を確認
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/55491dce-c2ee-26f0-e751-4cc4be405599.png)


## 参考リンク
- [How To: Recognize Unsatisfactory Pruning (snowflake.com)](https://community.snowflake.com/s/article/How-to-recognize-unsatisfactory-pruning)