列ストアインデックスの Column Elimination / Rowgroup Elimination

日本語だと「列の削除」、「行グループの削除」です。

set statistics io on

で、IO の情報から内容を確認します。

列の削除

全ての列を使うクエリと一部の列を使うクエリで比較します。

set statistics io on
select 
max(c1), max(c2), count(c3), max(c4), max(c5), 
max(c6), min(c7), max(c8), max(c9), max(c10), max(c11), 
max(c12), max(c13), max(c14), max(c15), max(c16), max(c17), 
max(c18), max(c19), max(c20), max(c21), max(c22), max(c23), max(c24)
from cci_ordered

select 
max(c1), max(c2), count(c3), max(c4), max(c5)
from cci_ordered
set statistics io off
Warning: Null value is eliminated by an aggregate or other SET operation.

(1 row(s) affected)
Table 'cci_ordered'. Scan count 1, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 27656, lob physical reads 0, lob read-ahead reads 0.
Table 'cci_ordered'. Segment reads 3, segment skipped 0.

(1 row(s) affected)
Table 'cci_ordered'. Scan count 1, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 7757, lob physical reads 0, lob read-ahead reads 0.
Table 'cci_ordered'. Segment reads 3, segment skipped 0.

1度データをキャッシュに乗せてから実行したので、physical reads はありませんが、lob logical reads の数が、上と下では結構違います。

全ての列 一部の列
lob logical reads 27656 lob logical reads 7757

これが、Column Elimination(列の削除) です。

ちなみに Segment reads 3, segment skipped 0. って出てますが、これ行グループのことで、列セグメント(column segment) のことではありません。 sys.column_store_segments (Transact-SQL) | Microsoft Docs
の segment_id の説明を見てもらうとわかりますが、「行グループIDのことを指してる、互換性のため列名は segment_id のままにしてる」と書いてます。
要は行グループのことを昔は segment と呼んでたってことですかね。

行グループの除去

全く同じデータを順不同で CCI(Clustered Columnstore Index) を作成した cci テーブルと、
c1 列でソートしてCCIを作成した cci_ordered というテーブルで比較します。
※ちなみにソートした列ストアインデックスを作るにはちょっとしたコツが要ります。

まずはどの条件なら読む行グループが減るかを確認するため、sys.column_store_segments から情報を取ります。

select
  object_name(p.object_id) as table_name
  , c.name as column_name
  , css.segment_id, css.row_count, css.min_data_id, css.max_data_id
from
  sys.column_store_segments css
  left join sys.partitions p on p.hobt_id = css.hobt_id and p.partition_id = css.partition_id
  left join sys.indexes ind on p.index_id = ind.index_id and p.object_id = ind.object_id
  left join sys.index_columns indc on indc.object_id = ind.object_id and indc.index_id = ind.index_id and indc.index_column_id = css.column_id
  left join sys.columns c on c.column_id = indc.column_id and c.object_id = p.object_id
where object_name(p.object_id) in ('cci', 'cci_ordered') and c.name = 'c1'
order by
  p.object_id, css.column_id, css.segment_id

f:id:odashinsuke:20170331235239p:plain
min_data_id、max_data_id に注目し、cci_ordered は1個の行グループ、cci は3個の行グループになる条件を付けます。
今回は c1 <= 1028864 にします。
これで、cci テーブルは、全行グループが、cci_orderedテーブルは、segment_id = 0 の 1行グループが読み込まれるはずです。

set statistics io on
select 
max(c1), max(c2), count(c3), max(c4), max(c5)
from cci where c1 <= 1028864

select 
max(c1), max(c2), count(c3), max(c4), max(c5)
from cci_ordered where c1 <= 1028864
set statistics io off
(1 row(s) affected)
Table 'cci'. Scan count 1, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 7767, lob physical reads 0, lob read-ahead reads 0.
Table 'cci'. Segment reads 3, segment skipped 0.

(1 row(s) affected)
Table 'cci_ordered'. Scan count 1, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 3773, lob physical reads 0, lob read-ahead reads 0.
Table 'cci_ordered'. Segment reads 1, segment skipped 2.

lob logical reads と Segement reads に注目します。

順不同 (cci) ソート済 (cci_ordered)
lob logical reads 7767 lob logical reads 3773
Table ‘cci’. Segment reads 3, segment skipped 0. Table ‘cci_ordered’. Segment reads 1, segment skipped 2.

ソート済の cci_ordered は segment skiped 2 とでて、logical reads の数も減っています。
これが RowGroup Elimination(行グループの削除)です。

ソート済の列ストアインデックスを作成する

列ストア インデックスのクエリ パフォーマンス
に記載されていますが、

  1. 時系列でデータを追加する
  2. 行ストアクラスター化インデックスを作成し、データを並び替えたあとに列ストアインデックスに差し替える(MAXDOP=1 で実行)

2番目の方法が曲者で、MAXDOP=1 で動かさないとパラレルで行ストアが作成されるます。
そのため複数の行ストアにデータが分散してしまい、ソートが効かなくなってしまいます。

今回使った cci_ordered ではこんな感じで作成してます。

create clustered index cci_ordered_index 
  on cci_ordered(c1)
create clustered columnstore index cci_ordered_index 
  on cci_ordered with (drop_existing=ON,MAXDOP=1)

ただ MAXDOP=1 なので、時間がかかる!

列ストアを使う位デカいテーブルなのに MAXDOP=1は時間かかるよーって意見が Connect にも上がってます。
Multi-threaded rebuilds of Clustered Columnstore Indexes break the sequence of pre-sorted segment ordering (Order Clustering) | Microsoft Connect
2014 の時に登録されて、次のリリースで直すみたいな感じでしたが、2016 でも直ってませんね。vNext だと直ってるのかな?