Slide 1

Slide 1 text

行数推定を読み解く @Kyabatalian

Slide 2

Slide 2 text

はじめまして @Kyabatalian フロントエンドっぽい貧弱エンジニア PostgreSQL歴 2ヶ月 【重要】間違ったことを話す可能性が高いです。

Slide 3

Slide 3 text

先月 あれ、ここヒント句使っとるやんけ。 何とかして!! ヒントク……? (お任せください!!!!!1) 開発のえらいひと

Slide 4

Slide 4 text

解決すべき問題

Slide 5

Slide 5 text

こんなふうに考えていた時期がぼくにもありました よくわからんけど、 サンプル数?を上げれば いいらしい!いけるで!

Slide 6

Slide 6 text

サンプル数を上げてみる

Slide 7

Slide 7 text

サンプル数を上げてみる 実行時間[ms]

Slide 8

Slide 8 text

サンプル数を上げてみる 実行時間[ms] っ ょ ぃ。

Slide 9

Slide 9 text

実行計画をみてみる

Slide 10

Slide 10 text

実行計画をみてみる Sort (cost=582.43..582.44 rows=1 width=312) (actual time=3112.054..3113.061 rows=5579 loops=1) Sort Key: [略] Sort Method: quicksort Memory: 1674kB -> Nested Loop (cost=2.59..582.42 rows=1 width=312) (actual time=548.120..3088.081 rows=5579 loops=1) -> Nested Loop (cost=2.16..581.58 rows=1 width=297) (actual time=548.032..3008.849 rows=5579 loops=1) -> Nested Loop (cost=1.73..573.12 rows=1 width=308) (actual time=548.020..2980.460 rows=5579 loops=1) Join Filter: [略] -> Nested Loop (cost=1.29..570.17 rows=1 width=256) (actual time=544.342..2929.895 rows=6286 loops=1) -> Nested Loop (cost=0.86..456.48 rows=85 width=245) (actual time=0.054..1044.752 rows=473464 loops=1) -> Index Scan using idx_[略] on user_table (cost=0.43..8.46 rows=1 width=70) (actual time=0.031..0.139 rows=115 loops=1) Index Cond: (((company_id)::text = 'sansan'::text) AND ((user_id)::text = 'kabata'::text)) -> Index Scan using idx_[略] (cost=0.43..446.91 rows=111 width=191) (actual time=0.015..4.347 rows=4117 loops=115) Index Cond: (((company_id)::text = 'sansan'::text) AND ((user_id)::text = 'kabata'::text)) Filter: [略] Rows Removed by Filter: 1 -> Index Scan using [略] (cost=0.43..1.33 rows=1 width=22) (actual time=0.003..0.003 rows=0 loops=473464) Index Cond: [略] Filter: [略] Rows Removed by Filter: 1 -> Index Scan using [略] (cost=0.43..2.92 rows=1 width=84) (actual time=0.006..0.006 rows=1 loops=6286) Index Cond: [略] Filter: [略] Rows Removed by Filter: 0 -> Index Scan using [略] (cost=0.43..8.45 rows=1 width=11) (actual time=0.004..0.004 rows=1 loops=5579) Index Cond: [略] Filter: [略] -> Index Scan using [略] (cost=0.43..0.61 rows=1 width=42) (actual time=0.004..0.005 rows=1 loops=5579) Index Cond: [略] Filter: [略] Total runtime: 3114.997 ms

Slide 11

Slide 11 text

実行計画をみてみる Sort (cost=582.43..582.44 rows=1 width=312) (actual time=3112.054..3113.061 rows=5579 loops=1) Sort Key: [略] Sort Method: quicksort Memory: 1674kB -> Nested Loop (cost=2.59..582.42 rows=1 width=312) (actual time=548.120..3088.081 rows=5579 loops=1) -> Nested Loop (cost=2.16..581.58 rows=1 width=297) (actual time=548.032..3008.849 rows=5579 loops=1) -> Nested Loop (cost=1.73..573.12 rows=1 width=308) (actual time=548.020..2980.460 rows=5579 loops=1) Join Filter: [略] -> Nested Loop (cost=1.29..570.17 rows=1 width=256) (actual time=544.342..2929.895 rows=6286 loops=1) -> Nested Loop (cost=0.86..456.48 rows=85 width=245) (actual time=0.054..1044.752 rows=473464 loops=1) -> Index Scan using idx_[略] on user_table (cost=0.43..8.46 rows=1 width=70) (actual time=0.031..0.139 rows=115 loops=1) Index Cond: (((company_id)::text = 'sansan'::text) AND ((user_id)::text = 'kabata'::text)) -> Index Scan using idx_[略] (cost=0.43..446.91 rows=111 width=191) (actual time=0.015..4.347 rows=4117 loops=115) Index Cond: (((company_id)::text = 'sansan'::text) AND ((user_id)::text = 'kabata'::text)) Filter: [略] Rows Removed by Filter: 1 -> Index Scan using [略] (cost=0.43..1.33 rows=1 width=22) (actual time=0.003..0.003 rows=0 loops=473464) Index Cond: [略] Filter: [略] Rows Removed by Filter: 1 -> Index Scan using [略] (cost=0.43..2.92 rows=1 width=84) (actual time=0.006..0.006 rows=1 loops=6286) Index Cond: [略] Filter: [略] Rows Removed by Filter: 0 -> Index Scan using [略] (cost=0.43..8.45 rows=1 width=11) (actual time=0.004..0.004 rows=1 loops=5579) Index Cond: [略] Filter: [略] -> Index Scan using [略] (cost=0.43..0.61 rows=1 width=42) (actual time=0.004..0.005 rows=1 loops=5579) Index Cond: [略] Filter: [略] Total runtime: 3114.997 ms ?

Slide 12

Slide 12 text

実行計画をみてみる Sort (cost=582.43..582.44 rows=1 width=312) (actual time=3112.054..3113.061 rows=5579 loops=1) Sort Key: [略] Sort Method: quicksort Memory: 1674kB -> Nested Loop (cost=2.59..582.42 rows=1 width=312) (actual time=548.120..3088.081 rows=5579 loops=1) -> Nested Loop (cost=2.16..581.58 rows=1 width=297) (actual time=548.032..3008.849 rows=5579 loops=1) -> Nested Loop (cost=1.73..573.12 rows=1 width=308) (actual time=548.020..2980.460 rows=5579 loops=1) Join Filter: [略] -> Nested Loop (cost=1.29..570.17 rows=1 width=256) (actual time=544.342..2929.895 rows=6286 loops=1) -> Nested Loop (cost=0.86..456.48 rows=85 width=245) (actual time=0.054..1044.752 rows=473464 loops=1) -> Index Scan using idx_[略] on user_table (cost=0.43..8.46 rows=1 width=70) (actual time=0.031..0.139 rows=115 loops=1) Index Cond: (((company_id)::text = 'sansan'::text) AND ((user_id)::text = 'kabata'::text)) -> Index Scan using idx_[略] (cost=0.43..446.91 rows=111 width=191) (actual time=0.015..4.347 rows=4117 loops=115) Index Cond: (((company_id)::text = 'sansan'::text) AND ((user_id)::text = 'kabata'::text)) Filter: [略] Rows Removed by Filter: 1 -> Index Scan using [略] (cost=0.43..1.33 rows=1 width=22) (actual time=0.003..0.003 rows=0 loops=473464) Index Cond: [略] Filter: [略] Rows Removed by Filter: 1 -> Index Scan using [略] (cost=0.43..2.92 rows=1 width=84) (actual time=0.006..0.006 rows=1 loops=6286) Index Cond: [略] Filter: [略] Rows Removed by Filter: 0 -> Index Scan using [略] (cost=0.43..8.45 rows=1 width=11) (actual time=0.004..0.004 rows=1 loops=5579) Index Cond: [略] Filter: [略] -> Index Scan using [略] (cost=0.43..0.61 rows=1 width=42) (actual time=0.004..0.005 rows=1 loops=5579) Index Cond: [略] Filter: [略] Total runtime: 3114.997 ms -> Nested Loop (cost=0.86..456.48 rows=85 width=245) (actual time=0.054..1044.752 rows=473464 loops=1)

Slide 13

Slide 13 text

実行計画をみてみる Sort (cost=582.43..582.44 rows=1 width=312) (actual time=3112.054..3113.061 rows=5579 loops=1) Sort Key: [略] Sort Method: quicksort Memory: 1674kB -> Nested Loop (cost=2.59..582.42 rows=1 width=312) (actual time=548.120..3088.081 rows=5579 loops=1) -> Nested Loop (cost=2.16..581.58 rows=1 width=297) (actual time=548.032..3008.849 rows=5579 loops=1) -> Nested Loop (cost=1.73..573.12 rows=1 width=308) (actual time=548.020..2980.460 rows=5579 loops=1) Join Filter: [略] -> Nested Loop (cost=1.29..570.17 rows=1 width=256) (actual time=544.342..2929.895 rows=6286 loops=1) -> Nested Loop (cost=0.86..456.48 rows=85 width=245) (actual time=0.054..1044.752 rows=473464 loops=1) -> Index Scan using idx_[略] on user_table (cost=0.43..8.46 rows=1 width=70) (actual time=0.031..0.139 rows=115 loops=1) Index Cond: (((company_id)::text = 'sansan'::text) AND ((user_id)::text = 'kabata'::text)) -> Index Scan using idx_[略] (cost=0.43..446.91 rows=111 width=191) (actual time=0.015..4.347 rows=4117 loops=115) Index Cond: (((company_id)::text = 'sansan'::text) AND ((user_id)::text = 'kabata'::text)) Filter: [略] Rows Removed by Filter: 1 -> Index Scan using [略] (cost=0.43..1.33 rows=1 width=22) (actual time=0.003..0.003 rows=0 loops=473464) Index Cond: [略] Filter: [略] Rows Removed by Filter: 1 -> Index Scan using [略] (cost=0.43..2.92 rows=1 width=84) (actual time=0.006..0.006 rows=1 loops=6286) Index Cond: [略] Filter: [略] Rows Removed by Filter: 0 -> Index Scan using [略] (cost=0.43..8.45 rows=1 width=11) (actual time=0.004..0.004 rows=1 loops=5579) Index Cond: [略] Filter: [略] -> Index Scan using [略] (cost=0.43..0.61 rows=1 width=42) (actual time=0.004..0.005 rows=1 loops=5579) Index Cond: [略] Filter: [略] Total runtime: 3114.997 ms

Slide 14

Slide 14 text

実行計画をみてみる Sort (cost=582.43..582.44 rows=1 width=312) (actual time=3112.054..3113.061 rows=5579 loops=1) Sort Key: [略] Sort Method: quicksort Memory: 1674kB -> Nested Loop (cost=2.59..582.42 rows=1 width=312) (actual time=548.120..3088.081 rows=5579 loops=1) -> Nested Loop (cost=2.16..581.58 rows=1 width=297) (actual time=548.032..3008.849 rows=5579 loops=1) -> Nested Loop (cost=1.73..573.12 rows=1 width=308) (actual time=548.020..2980.460 rows=5579 loops=1) Join Filter: [略] -> Nested Loop (cost=1.29..570.17 rows=1 width=256) (actual time=544.342..2929.895 rows=6286 loops=1) -> Nested Loop (cost=0.86..456.48 rows=85 width=245) (actual time=0.054..1044.752 rows=473464 loops=1) -> Index Scan using idx_[略] on user_table (cost=0.43..8.46 rows=1 width=70) (actual time=0.031..0.139 rows=115 loops=1) Index Cond: (((company_id)::text = 'sansan'::text) AND ((user_id)::text = 'kabata'::text)) -> Index Scan using idx_[略] (cost=0.43..446.91 rows=111 width=191) (actual time=0.015..4.347 rows=4117 loops=115) Index Cond: (((company_id)::text = 'sansan'::text) AND ((user_id)::text = 'kabata'::text)) Filter: [略] Rows Removed by Filter: 1 -> Index Scan using [略] (cost=0.43..1.33 rows=1 width=22) (actual time=0.003..0.003 rows=0 loops=473464) Index Cond: [略] Filter: [略] Rows Removed by Filter: 1 -> Index Scan using [略] (cost=0.43..2.92 rows=1 width=84) (actual time=0.006..0.006 rows=1 loops=6286) Index Cond: [略] Filter: [略] Rows Removed by Filter: 0 -> Index Scan using [略] (cost=0.43..8.45 rows=1 width=11) (actual time=0.004..0.004 rows=1 loops=5579) Index Cond: [略] Filter: [略] -> Index Scan using [略] (cost=0.43..0.61 rows=1 width=42) (actual time=0.004..0.005 rows=1 loops=5579) Index Cond: [略] Filter: [略] Total runtime: 3114.997 ms -> Index Scan using idx_[略] on user_table (cost=0.43..8.46 rows=1 width=70) (actual time=0.031..0.139 rows=115 loops=1) Index Cond: (((company_id)::text = 'sansan'::text) AND ((user_id)::text = 'kabata'::text))

Slide 15

Slide 15 text

行数推定外れすぎ問題 なんで?

Slide 16

Slide 16 text

行数推定を読み解く

Slide 17

Slide 17 text

実行計画とは何か SQLって書いたとおりに 実行されるんじゃないん ですか?

Slide 18

Slide 18 text

実行計画とは何か(1/4) ユーザから与えられたクエリを 効率よく実行するために が内部的に最適化する。

Slide 19

Slide 19 text

実行計画とは何か(1/4) ユーザから与えられたクエリを 効率よく実行するために が内部的に最適化する。 ? !?

Slide 20

Slide 20 text

実行計画とは何か(2/4) クエリとは ユーザから に対する「依頼」 を記述したもの。

Slide 21

Slide 21 text

実行計画とは何か(3/4) は、ユーザが記述したクエリを リレーショナルモデルにおける 「演算」に変換する。

Slide 22

Slide 22 text

実行計画とは何か(4/4) 実行計画とは、統計情報をもとに 最もコストの低い演算子と順序を 選択したもの。

Slide 23

Slide 23 text

行数推定の精度が悪いとプラン演算子の選択を誤る 行数少なそう。 ここはSeqScanやな。 行数多かった!!!! IndexScanのほうが速かった!

Slide 24

Slide 24 text

行数推定の精度が悪いと実行順序の選択を誤る 行数少なそう。 ネストさせとこ。 行数多かった!!!! コストめっちゃ高い!!!

Slide 25

Slide 25 text

PostgreSQLが収集する統計情報※1 ● DB単位のコミット数やロールバック数 ● DBやテーブル、インデックス単位のI/O発生状況 ● DBやテーブルに対して挿入/更新/削除された行数 ● テーブル単位で実施された表スキャン回数 ● DBやテーブル、インデックス単位でスキャンされた行数 ● テーブル単位の行数、ガベージ量 ● 現在実施中のSQLやメンテナンス処理 ● カラム単位の度数分布と最頻値・最頻度 … ※1 稼動統計情報を活用しよう (1) : http://lets.postgresql.jp/documents/technical/statistics/1

Slide 26

Slide 26 text

PostgreSQLが収集する統計情報 ● DB単位のコミット数やロールバック数 ● DBやテーブル、インデックス単位のI/O発生状況 ● DBやテーブルに対して挿入/更新/削除された行数 ● テーブル単位で実施された表スキャン回数 ● DBやテーブル、インデックス単位でスキャンされた行数 ● テーブル単位の行数、ガベージ量 ● 現在実施中のSQLやメンテナンス処理 ● カラム単位の度数分布と最頻値・最頻度 … 行数推定で使う統計情報はこれだけ

Slide 27

Slide 27 text

テーブル単位の行数 ● pg_class の reltuples カラムに保持される ※1 。 ● VACUUM, ANALYZE, CREATE INDEX 等で更新される。 relname reltuples user_table 1424335 SELECT relname, reltuples FROM pg_class WHERE relname = ‘user_table’; ※1 VACUUM, ANALYZE等ではテーブル全体を操作しないため、概算値が保持される。

Slide 28

Slide 28 text

カラム単位の最頻値・最頻度と度数分布 ● pg_statistic に Array型で格納される。※1 ● ANALYZE, VACUUM ANALYZE で更新される。 ※1 Arrayの最大長(=保持する最大数)をカラムごとまたはグローバルに設定できる( default:100)。 ※2 pg_statistic はシステムカタログであり読みづらいため、参照する際は pg_stats ビューを利用する。 most_common_vals most_common_freqs histogram_bounds {yontendo, gaagle, yanoo,... {0.1349666714668274,0.130199998 61717224,0.08696666359901428,... {twitto, bacefook, linkedout, …. SELECT most_common_vals, most_common_freqs, histogram_bounds FROM pg_stats ※2 WHERE tablename = 'user_table' and attname = 'company_id';

Slide 29

Slide 29 text

行数推定の考え方 「テーブル全体の行数の推定値」に 「全体に占める対象データの割合の推定値」を  掛け合わせたものを推定行数とする。

Slide 30

Slide 30 text

行数推定の方法 推定行数 = 濃度 * 選択度

Slide 31

Slide 31 text

行数推定の方法 推定行数 = 濃度 * 選択度 pg_class.reltuples ?

Slide 32

Slide 32 text

選択度決定の方法は条件句によって異なる 最頻値に 含まれない 最頻値に 含まれる 一致条件 範囲条件 複合条件 条件なし

Slide 33

Slide 33 text

選択度決定の方法は条件句によって異なる 最頻値に 含まれない 最頻値に 含まれる 一致条件 範囲条件 複合条件 条件なし

Slide 34

Slide 34 text

行数推定のアルゴリズム:条件なし SELECT * FROM user_table; Seq Scan on user_table (cost=0.00..84330.35 rows=1424335 width=355) (actual time=0.041..749.988 rows=1424450 loops=1) 1. 選択度は1となる。 2. user_tableの濃度を推定行数とする。

Slide 35

Slide 35 text

選択度決定の方法は条件句によって異なる 最頻値に 含まれない 最頻値に 含まれる 一致条件 範囲条件 複合条件 条件なし

Slide 36

Slide 36 text

行数推定のアルゴリズム:範囲条件 ※1 SELECT * FROM user_table WHERE crttimestamp < ‘2015-10-10 10:10:10’; ※1 本当はもう少し複雑。 https://www.postgresql.jp/document/9.5/html/row-estimation-examples.html ※2 pg_operator の oprrest カラムに保持されている。今回は scalarltsel関数。 1. 選択度関数を決定する※2 。 2. pg_statistic から user_id の度数分布を取り出す。 3. 「<」で占められる度数分布率を選択度とする。 4. user_tableの濃度と選択度の積を推定行数とする。

Slide 37

Slide 37 text

column < c で占められる度数分布率 frequency = (1 + (c - bucket[ i c ].min) / (bucket[ i c ].max - bucket[ i c ].min)) / bucket.length 0 86 16 32 48 64 70 bucket[1] bucket[2] bucket[2].min bucket[2].min 例:対象の値 c = 20 、度数分布が右図の場合、 度数分布率は以下で計算される。 frequency = (1 + (20 - 16) / (32 -16)) / 6 ≒ 0.21 ※各バケット内での線形分布を仮定。

Slide 38

Slide 38 text

行数推定のアルゴリズム:範囲条件 SELECT * FROM user_table WHERE crttimestamp < ‘2015-10-10 10:10:10’; Seq Scan on user_table (cost=0.00..87891.19 rows=508065 width=355) (actual time=0.015..266.237 rows=503049 loops=1) Filter: (crttimestamp < '2015-10-10 10:10:10+09'::timestamp with time zone) Rows Removed by Filter: 921401

Slide 39

Slide 39 text

選択度決定の方法は条件句によって異なる 最頻値に 含まれない 最頻値に 含まれる 一致条件 範囲条件 複合条件 条件なし

Slide 40

Slide 40 text

行数推定のアルゴリズム:一致条件 SELECT * FROM user_table WHERE company_id = ‘sansan’; 1. 選択度関数を決定する※1 。 2. “sansan” という値が最頻値に含まれるか調べる。 3. 以下により選択度を決定する。 a. 最頻値に含まれる場合 :最頻度を選択度とする。 b. 最頻値に含まれない場合: がんばってしぼりだす 。 4. user_tableの濃度と選択度の積を推定行数とする。 ※1 今回は eqsel関数。

Slide 41

Slide 41 text

最頻値に含まれない値の選択度 selectivity = (1 - sum(mvf)) / (num_distinct - num_mcv) 例:対象のカラムのとりうる値が 100通りで、MCV・MCFが   右表の場合、”sansan” の選択度は以下で計算される。 selectivity = (1 - (0.32 + 0.18 + 0.09 + 0.04 + 0.01)) / (100 - 5) ≒ 0.0038 ※MCV以外の列の一様分布を仮定。 MCV MCF yontendo 0.32 yanoo 0.18 gaagle 0.09 hitaz 0.04 basefook 0.01

Slide 42

Slide 42 text

行数推定のアルゴリズム:一致条件 SELECT * FROM user_table WHERE company_id = ‘sansan’; Bitmap Heap Scan on user_table (cost=2836.15..77415.72 rows=108867 width=355) (actual time=19.782..68.394 rows=109436 loops=1) Index Cond: ((company_id)::text = 'sansan'::text)

Slide 43

Slide 43 text

選択度決定の方法は条件句によって異なる 最頻値に 含まれない 最頻値に 含まれる 一致条件 範囲条件 複合条件 条件なし

Slide 44

Slide 44 text

行数推定のアルゴリズム:複合条件 SELECT * FROM user_table WHERE company_id = ‘sansan’ AND user_id = ‘kabata’ 1. 各条件の選択度を計算する。 2. user_tableの濃度、各条件の選択度の積を推定行数とする。 ※各条件は独立していると仮定。

Slide 45

Slide 45 text

行数推定のアルゴリズム:複合条件 SELECT * FROM user_table WHERE company_id = ‘sansan’ AND user_id = ‘kabata’ Index Scan using user_table_pkey on user_table (cost=0.43..8.46 rows=1 width=70) (actual time=0.031..0.139 rows=115 loops=1) Index Cond: (((company_id)::text = 'sansan'::text) AND ((user_id)::text = 'kabata'::text))

Slide 46

Slide 46 text

行数推定のアルゴリズム:まとめ 選択度(selectivity) ● 条件なし : 1 ● 範囲条件 : (1 + (c - b[ i c ].min) / (b[ i c ].max - b[ i c ].min) / b.length ● 一致条件 ○ 最頻値に含まれる  : mcf c ○ 最頻値に含まれない : (1 - sum(mcf)) / (num_distinct - num_mcv) ● 複合条件 : selectivity(a) * selectivity(b) 推定行数 rows = rel_cardinarity * selectivity

Slide 47

Slide 47 text

問題の原因

Slide 48

Slide 48 text

実行計画をみてみる(再掲) Sort (cost=582.43..582.44 rows=1 width=312) (actual time=3112.054..3113.061 rows=5579 loops=1) Sort Key: [略] Sort Method: quicksort Memory: 1674kB -> Nested Loop (cost=2.59..582.42 rows=1 width=312) (actual time=548.120..3088.081 rows=5579 loops=1) -> Nested Loop (cost=2.16..581.58 rows=1 width=297) (actual time=548.032..3008.849 rows=5579 loops=1) -> Nested Loop (cost=1.73..573.12 rows=1 width=308) (actual time=548.020..2980.460 rows=5579 loops=1) Join Filter: [略] -> Nested Loop (cost=1.29..570.17 rows=1 width=256) (actual time=544.342..2929.895 rows=6286 loops=1) -> Nested Loop (cost=0.86..456.48 rows=85 width=245) (actual time=0.054..1044.752 rows=473464 loops=1) -> Index Scan using idx_[略] on user_table (cost=0.43..8.46 rows=1 width=70) (actual time=0.031..0.139 rows=115 loops=1) Index Cond: (((company_id)::text = 'sansan'::text) AND ((user_id)::text = 'kabata'::text)) -> Index Scan using idx_[略] (cost=0.43..446.91 rows=111 width=191) (actual time=0.015..4.347 rows=4117 loops=115) Index Cond: (((company_id)::text = 'sansan'::text) AND ((user_id)::text = 'kabata'::text)) Filter: [略] Rows Removed by Filter: 1 -> Index Scan using [略] (cost=0.43..1.33 rows=1 width=22) (actual time=0.003..0.003 rows=0 loops=473464) Index Cond: [略] Filter: [略] Rows Removed by Filter: 1 -> Index Scan using [略] (cost=0.43..2.92 rows=1 width=84) (actual time=0.006..0.006 rows=1 loops=6286) Index Cond: [略] Filter: [略] Rows Removed by Filter: 0 -> Index Scan using [略] (cost=0.43..8.45 rows=1 width=11) (actual time=0.004..0.004 rows=1 loops=5579) Index Cond: [略] Filter: [略] -> Index Scan using [略] (cost=0.43..0.61 rows=1 width=42) (actual time=0.004..0.005 rows=1 loops=5579) Index Cond: [略] Filter: [略] Total runtime: 3114.997 ms -> Index Scan using idx_[略] on user_table (cost=0.43..8.46 rows=1 width=70) (actual time=0.031..0.139 rows=115 loops=1) Index Cond: (((company_id)::text = 'sansan'::text) AND ((user_id)::text = 'kabata'::text))

Slide 49

Slide 49 text

実行計画をみてみる(再掲) Sort (cost=582.43..582.44 rows=1 width=312) (actual time=3112.054..3113.061 rows=5579 loops=1) Sort Key: [略] Sort Method: quicksort Memory: 1674kB -> Nested Loop (cost=2.59..582.42 rows=1 width=312) (actual time=548.120..3088.081 rows=5579 loops=1) -> Nested Loop (cost=2.16..581.58 rows=1 width=297) (actual time=548.032..3008.849 rows=5579 loops=1) -> Nested Loop (cost=1.73..573.12 rows=1 width=308) (actual time=548.020..2980.460 rows=5579 loops=1) Join Filter: [略] -> Nested Loop (cost=1.29..570.17 rows=1 width=256) (actual time=544.342..2929.895 rows=6286 loops=1) -> Nested Loop (cost=0.86..456.48 rows=85 width=245) (actual time=0.054..1044.752 rows=473464 loops=1) -> Index Scan using idx_[略] on user_table (cost=0.43..8.46 rows=1 width=70) (actual time=0.031..0.139 rows=115 loops=1) Index Cond: (((company_id)::text = 'sansan'::text) AND ((user_id)::text = 'kabata'::text)) -> Index Scan using idx_[略] (cost=0.43..446.91 rows=111 width=191) (actual time=0.015..4.347 rows=4117 loops=115) Index Cond: (((company_id)::text = 'sansan'::text) AND ((user_id)::text = 'kabata'::text)) Filter: [略] Rows Removed by Filter: 1 -> Index Scan using [略] (cost=0.43..1.33 rows=1 width=22) (actual time=0.003..0.003 rows=0 loops=473464) Index Cond: [略] Filter: [略] Rows Removed by Filter: 1 -> Index Scan using [略] (cost=0.43..2.92 rows=1 width=84) (actual time=0.006..0.006 rows=1 loops=6286) Index Cond: [略] Filter: [略] Rows Removed by Filter: 0 -> Index Scan using [略] (cost=0.43..8.45 rows=1 width=11) (actual time=0.004..0.004 rows=1 loops=5579) Index Cond: [略] Filter: [略] -> Index Scan using [略] (cost=0.43..0.61 rows=1 width=42) (actual time=0.004..0.005 rows=1 loops=5579) Index Cond: [略] Filter: [略] Total runtime: 3114.997 ms -> Index Scan using idx_[略] on user_table (cost=0.43..8.46 rows=1 width=70) (actual time=0.031..0.139 rows=115 loops=1) Index Cond: (((company_id)::text = 'sansan'::text) AND ((user_id)::text = 'kabata'::text)) 一致条件 + 一致条件 の複合条件

Slide 50

Slide 50 text

複合条件の行数推定

Slide 51

Slide 51 text

複合条件の行数推定

Slide 52

Slide 52 text

事象の独立性 P(A∩B) = P(A)P(B) であるとき 事象Aと事象Bは独立である

Slide 53

Slide 53 text

条件の独立性 S(A∩B) = S(A)S(B) であるとき 条件Aと条件Bは独立である ※S(X)は条件Sの選択度

Slide 54

Slide 54 text

独立性を検証してみる S((company_id = ‘sansan’)∩((user_id = ‘kabata’))) = S((company_id = ‘sansan’))S((user_id = ‘kabata’)) 成り立つか?

Slide 55

Slide 55 text

各条件による実際の行数を取得する SELECT count(*) FROM user_table WHERE company_id = ‘sansan’ and user_id = ‘kabata’; SELECT count(*) FROM user_table WHERE company_id = ‘sansan’; SELECT count(*) FROM user_table WHERE user_id = ‘kabata’;

Slide 56

Slide 56 text

各条件による実際の行数を取得する SELECT count(*) FROM user_table WHERE company_id = ‘sansan’ and user_id = ‘kabata’; SELECT count(*) FROM user_table WHERE company_id = ‘sansan’; SELECT count(*) FROM user_table WHERE user_id = ‘kabata’; 322 109436 322

Slide 57

Slide 57 text

実際の選択度を計算する S(A∩B) = 322 / 1424450 ≒ 0.00023 S(A) = 109436 / 1424450 ≒ 0.071 S(B) = 322 / 1424450 ≒ 0.00023

Slide 58

Slide 58 text

独立性を検証する S(A∩B) ≒ 0.00023 S(A)S(B) ≒ 0.071 * 0.00023 ≒0.000017 S(A∩B) ≠ S(A)S(B)

Slide 59

Slide 59 text

問題の原因 行数推定において が想定する状態と 実際の状態が乖離。

Slide 60

Slide 60 text

解決方法

Slide 61

Slide 61 text

3つの解決方法 に従う を騙す に背く

Slide 62

Slide 62 text

3つの解決方法 に従う を騙す に背く

Slide 63

Slide 63 text

複合条件をやめる 部分関数従属に”限りなく近 い”状態をやめたい。 user_id を一意にする。

Slide 64

Slide 64 text

3つの解決方法 に従う を騙す に背く

Slide 65

Slide 65 text

ヒント句を使う※1 が作成する実行計画を無視 し、手動で指定する。 本質的な問題解決ではない。 ※1 pg_hint_plan : http://pgdbmsstats.osdn.jp/pg_dbms_stats-ja.html

Slide 66

Slide 66 text

3つの解決方法 に従う を騙す に背く

Slide 67

Slide 67 text

統計情報の改竄 両条件の関数従属性を仮定する。 MCF(user_id) = MCF(user_id) / MCF(company_id) 統計情報は VACUUM や ANALYZE によって元に 戻ってしまう。

Slide 68

Slide 68 text

を騙し続ける 統計情報を改竄した状態で 固定化する。 ※1 魔改造。 ※1 pg_dbms_stats : http://pgdbmsstats.osdn.jp/pg_dbms_stats-ja.html

Slide 69

Slide 69 text

結論 に従う を騙す に背く

Slide 70

Slide 70 text

まとめ

Slide 71

Slide 71 text

まとめ(1/3) 行数推定のアルゴリズム は意外と単純。

Slide 72

Slide 72 text

まとめ(2/3) DB設計やデータ構造的な 問題に帰結。

Slide 73

Slide 73 text

まとめ(3/3) 仕組みがわかると楽しい。

Slide 74

Slide 74 text

まとめ 行数推定のアルゴリズムは意外と単純。 DB設計やデータの構造的な問題に帰結。 仕組みがわかると楽しい。

Slide 75

Slide 75 text

補足

Slide 76

Slide 76 text

補足:pg_statistic からMCV/MCFを取得するSQL SELECT * FROM pg_statistic s JOIN pg_class c ON (c.oid = s.starelid) JOIN pg_attribute a ON (c.oid = attrelid AND attnum = s.staattnum) LEFT JOIN pg_namespace n ON (n.oid = c.relnamespace) WHERE relname = ‘user_table’ AND attname = ‘user_id’ AND stakind1 = 1;