Ce diaporama a bien été signalé.
Nous utilisons votre profil LinkedIn et vos données d’activité pour vous proposer des publicités personnalisées et pertinentes. Vous pouvez changer vos préférences de publicités à tout moment.

PostgreSQL:行数推定を読み解く

第7回 PostgreSQLアンカンファレンス@東京 - https://atnd.org/events/75718

  • Soyez le premier à commenter

PostgreSQL:行数推定を読み解く

  1. 1. 行数推定を読み解く @Kyabatalian
  2. 2. はじめまして @Kyabatalian フロントエンドっぽい貧弱エンジニア PostgreSQL歴 2ヶ月 【重要】間違ったことを話す可能性が高いです。
  3. 3. 先月 あれ、ここヒント句使っとるやんけ。 何とかして!! ヒントク……? (お任せください!!!!!1) 開発のえらいひと
  4. 4. 解決すべき問題
  5. 5. こんなふうに考えていた時期がぼくにもありました よくわからんけど、 サンプル数?を上げれば いいらしい!いけるで!
  6. 6. サンプル数を上げてみる
  7. 7. サンプル数を上げてみる 実行時間[ms]
  8. 8. サンプル数を上げてみる 実行時間[ms] っ ょ ぃ。
  9. 9. 実行計画をみてみる
  10. 10. 実行計画をみてみる 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
  11. 11. 実行計画をみてみる 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 ?
  12. 12. 実行計画をみてみる 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)
  13. 13. 実行計画をみてみる 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
  14. 14. 実行計画をみてみる 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))
  15. 15. 行数推定外れすぎ問題 なんで?
  16. 16. 行数推定を読み解く
  17. 17. 実行計画とは何か SQLって書いたとおりに 実行されるんじゃないん ですか?
  18. 18. 実行計画とは何か(1/4) ユーザから与えられたクエリを 効率よく実行するために が内部的に最適化する。
  19. 19. 実行計画とは何か(1/4) ユーザから与えられたクエリを 効率よく実行するために が内部的に最適化する。?!?
  20. 20. 実行計画とは何か(2/4) クエリとは ユーザから に対する「依頼」 を記述したもの。
  21. 21. 実行計画とは何か(3/4) は、ユーザが記述したクエリを リレーショナルモデルにおける 「演算」に変換する。
  22. 22. 実行計画とは何か(4/4) 実行計画とは、統計情報をもとに 最もコストの低い演算子と順序を 選択したもの。
  23. 23. 行数推定の精度が悪いとプラン演算子の選択を誤る 行数少なそう。 ここはSeqScanやな。 行数多かった!!!! IndexScanのほうが速かった!
  24. 24. 行数推定の精度が悪いと実行順序の選択を誤る 行数少なそう。 ネストさせとこ。 行数多かった!!!! コストめっちゃ高い!!!
  25. 25. PostgreSQLが収集する統計情報※1 ● DB単位のコミット数やロールバック数 ● DBやテーブル、インデックス単位のI/O発生状況 ● DBやテーブルに対して挿入/更新/削除された行数 ● テーブル単位で実施された表スキャン回数 ● DBやテーブル、インデックス単位でスキャンされた行数 ● テーブル単位の行数、ガベージ量 ● 現在実施中のSQLやメンテナンス処理 ● カラム単位の度数分布と最頻値・最頻度 … ※1 稼動統計情報を活用しよう(1) : http://lets.postgresql.jp/documents/technical/statistics/1
  26. 26. PostgreSQLが収集する統計情報 ● DB単位のコミット数やロールバック数 ● DBやテーブル、インデックス単位のI/O発生状況 ● DBやテーブルに対して挿入/更新/削除された行数 ● テーブル単位で実施された表スキャン回数 ● DBやテーブル、インデックス単位でスキャンされた行数 ● テーブル単位の行数、ガベージ量 ● 現在実施中のSQLやメンテナンス処理 ● カラム単位の度数分布と最頻値・最頻度 … 行数推定で使う統計情報はこれだけ
  27. 27. テーブル単位の行数 ● 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等ではテーブル全体を操作しないため、概算値が保持される。
  28. 28. カラム単位の最頻値・最頻度と度数分布 ● 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';
  29. 29. 行数推定の考え方 「テーブル全体の行数の推定値」に 「全体に占める対象データの割合の推定値」を  掛け合わせたものを推定行数とする。
  30. 30. 行数推定の方法 推定行数 = 濃度 * 選択度
  31. 31. 行数推定の方法 推定行数 = 濃度 * 選択度 pg_class.reltuples ?
  32. 32. 選択度決定の方法は条件句によって異なる 最頻値に 含まれない 最頻値に 含まれる 一致条件範囲条件 複合条件 条件なし
  33. 33. 選択度決定の方法は条件句によって異なる 最頻値に 含まれない 最頻値に 含まれる 一致条件範囲条件 複合条件 条件なし
  34. 34. 行数推定のアルゴリズム:条件なし 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の濃度を推定行数とする。
  35. 35. 選択度決定の方法は条件句によって異なる 最頻値に 含まれない 最頻値に 含まれる 一致条件範囲条件 複合条件 条件なし
  36. 36. 行数推定のアルゴリズム:範囲条件 ※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の濃度と選択度の積を推定行数とする。
  37. 37. column < c で占められる度数分布率 frequency = (1 + (c - bucket[ ic ].min) / (bucket[ ic ].max - bucket[ ic ].min)) / bucket.length 0 8616 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 ※各バケット内での線形分布を仮定。
  38. 38. 行数推定のアルゴリズム:範囲条件 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
  39. 39. 選択度決定の方法は条件句によって異なる 最頻値に 含まれない 最頻値に 含まれる 一致条件範囲条件 複合条件 条件なし
  40. 40. 行数推定のアルゴリズム:一致条件 SELECT * FROM user_table WHERE company_id = ‘sansan’; 1. 選択度関数を決定する※1。 2. “sansan” という値が最頻値に含まれるか調べる。 3. 以下により選択度を決定する。 a. 最頻値に含まれる場合 :最頻度を選択度とする。 b. 最頻値に含まれない場合: がんばってしぼりだす 。 4. user_tableの濃度と選択度の積を推定行数とする。 ※1 今回は eqsel関数。
  41. 41. 最頻値に含まれない値の選択度 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
  42. 42. 行数推定のアルゴリズム:一致条件 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)
  43. 43. 選択度決定の方法は条件句によって異なる 最頻値に 含まれない 最頻値に 含まれる 一致条件範囲条件 複合条件 条件なし
  44. 44. 行数推定のアルゴリズム:複合条件 SELECT * FROM user_table WHERE company_id = ‘sansan’ AND user_id = ‘kabata’ 1. 各条件の選択度を計算する。 2. user_tableの濃度、各条件の選択度の積を推定行数とする。 ※各条件は独立していると仮定。
  45. 45. 行数推定のアルゴリズム:複合条件 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))
  46. 46. 行数推定のアルゴリズム:まとめ 選択度(selectivity) ● 条件なし : 1 ● 範囲条件 : (1 + (c - b[ ic ].min) / (b[ ic ].max - b[ ic ].min) / b.length ● 一致条件 ○ 最頻値に含まれる  : mcfc ○ 最頻値に含まれない : (1 - sum(mcf)) / (num_distinct - num_mcv) ● 複合条件 : selectivity(a) * selectivity(b) 推定行数 rows = rel_cardinarity * selectivity
  47. 47. 問題の原因
  48. 48. 実行計画をみてみる(再掲) 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))
  49. 49. 実行計画をみてみる(再掲) 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)) 一致条件 + 一致条件 の複合条件
  50. 50. 複合条件の行数推定
  51. 51. 複合条件の行数推定
  52. 52. 事象の独立性 P(A∩B) = P(A)P(B) であるとき 事象Aと事象Bは独立である
  53. 53. 条件の独立性 S(A∩B) = S(A)S(B) であるとき 条件Aと条件Bは独立である ※S(X)は条件Sの選択度
  54. 54. 独立性を検証してみる S((company_id = ‘sansan’)∩((user_id = ‘kabata’))) = S((company_id = ‘sansan’))S((user_id = ‘kabata’)) 成り立つか?
  55. 55. 各条件による実際の行数を取得する 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’;
  56. 56. 各条件による実際の行数を取得する 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
  57. 57. 実際の選択度を計算する S(A∩B) = 322 / 1424450 ≒ 0.00023 S(A) = 109436 / 1424450 ≒ 0.071 S(B) = 322 / 1424450 ≒ 0.00023
  58. 58. 独立性を検証する S(A∩B) ≒ 0.00023 S(A)S(B) ≒ 0.071 * 0.00023 ≒0.000017 S(A∩B) ≠ S(A)S(B)
  59. 59. 問題の原因 行数推定において が想定する状態と 実際の状態が乖離。
  60. 60. 解決方法
  61. 61. 3つの解決方法 に従う を騙す に背く
  62. 62. 3つの解決方法 に従う を騙す に背く
  63. 63. 複合条件をやめる 部分関数従属に”限りなく近 い”状態をやめたい。 user_id を一意にする。
  64. 64. 3つの解決方法 に従う を騙す に背く
  65. 65. ヒント句を使う※1 が作成する実行計画を無視 し、手動で指定する。 本質的な問題解決ではない。 ※1 pg_hint_plan : http://pgdbmsstats.osdn.jp/pg_dbms_stats-ja.html
  66. 66. 3つの解決方法 に従う を騙す に背く
  67. 67. 統計情報の改竄 両条件の関数従属性を仮定する。 MCF(user_id) = MCF(user_id) / MCF(company_id) 統計情報は VACUUM や ANALYZE によって元に 戻ってしまう。
  68. 68. を騙し続ける 統計情報を改竄した状態で 固定化する。※1 魔改造。 ※1 pg_dbms_stats : http://pgdbmsstats.osdn.jp/pg_dbms_stats-ja.html
  69. 69. 結論 に従う を騙す に背く
  70. 70. まとめ
  71. 71. まとめ(1/3) 行数推定のアルゴリズム は意外と単純。
  72. 72. まとめ(2/3) DB設計やデータ構造的な 問題に帰結。
  73. 73. まとめ(3/3) 仕組みがわかると楽しい。
  74. 74. まとめ 行数推定のアルゴリズムは意外と単純。 DB設計やデータの構造的な問題に帰結。 仕組みがわかると楽しい。
  75. 75. 補足
  76. 76. 補足: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;

×