Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

PostgreSQL SQLチューニング入門 実践編(pgcon14j)

3,265 views

Published on

PostgreSQLカンファレンス2014チュートリアルセッションで発表した際の資料です。

Published in: Engineering
  • Dating direct: ❶❶❶ http://bit.ly/39mQKz3 ❶❶❶
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
  • Dating for everyone is here: ♥♥♥ http://bit.ly/39mQKz3 ♥♥♥
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here

PostgreSQL SQLチューニング入門 実践編(pgcon14j)

  1. 1. 1 やまそふと PostgreSQL SQL チューニング入門 実践編 PostgreSQL カンファレンス 2014 株式会社アシスト 山田 聡
  2. 2.   2 本セッションについて "PostgreSQL SQL チューニング入門 入門編"の続きです 細かいアクセスパスの説明は省略します 不参加の方は前セッションの資料とあわせて、後で復習を オススメします 入門セッションのため、ゆっくり進行でお送りします
  3. 3.   3 Who am I ? 名前:山田 聡(やまだ さとし) 会社:株式会社アシスト 仕事:PostgreSQL+PPASのサポート (●racleも...) PostgreSQL歴:3年 興味:機械学習,軽量言語(Python,JS等)
  4. 4. 4 今日の目的 EXPLAIN ANALYZEで 問題点を発見できるようになりましょう
  5. 5.   5 アジェンダ 1.実行プランの強制 2.EXPLAIN と EXPLAIN ANALYZE 3.問題解決例 4.まとめ
  6. 6.   6 アジェンダ 1.実行プランの強制 2.EXPLAIN と EXPLAIN ANALYZE 3.問題解決例 4.まとめ
  7. 7.   7 1.実行プランの強制 PostgreSQLには様々なアクセスパスがある 通常はPostgreSQLが最適なパスを選ぶ (稀に)選んで欲しくないパスになるケースがある
  8. 8. 8 そんな時に役に立つのがそんな時に役に立つのが"実行プランの強制"
  9. 9.   9 1.実行プランの強制 SET enable_演算子 = off; プランナーがある演算子を使おうとするのを「強く思いとどまらせる」ことができる SETを行ったセッションのみに影響する 演算子毎に設定可能(ON/OFF) enable_bitmapscan enable_hashagg enable_hashjoin enable_indexscan enable_indexonlyscan enable_material enable_mergejoin enable_nestloop enable_seqscan enable_sort enable_tidscan
  10. 10.   10 1.実行プランの強制 強く思いとどまらせるとは? 指定したアクセスパスの始動コストに100000000.0を足す 指定したアクセスパスが選択されなくなるわけではない →例えばseqscanを選択しないようにするとアクセスパスがなくなる可能性があるため  完全に無効化はしない sampledb=# explain analyze select * from pgbench_accounts;     QUERY PLAN ----------------------------------------------------------------------------------------------- Seq Scan on pgbench_accounts (cost=10000000000.00..10000025874.00 rows=1000000 width=97) (actual time=0.008..159.306 rows=1000000 loops=1) Total runtime: 285.398 ms
  11. 11.   11 1.実行プランの強制 プランを強制してみる sampledb=# explain analyze select * from pgbench_accounts where aid > 1; QUERY PLAN ------------------------------------------------------------------------ Seq Scan on pgbench_accounts (cost=0.00..28374.00 rows=1000000 width=97) (actual time=0.011..224.378 rows=999999 loops=1) Filter: (aid > 1) Rows Removed by Filter: 1 Total runtime: 345.456 ms (4 rows) sampledb=# SET enable_seqscan = off; SET sampledb=# explain analyze select * from pgbench_accounts where aid > 1; QUERY PLAN ------------------------------------------------------------------------ Index Scan using pgbench_accounts_pkey on pgbench_accounts (cost=0.42..42169.43 rows=1000000 width=97) (actual time=0.041..480.360 rows=999999 loops=1) Index Cond: (aid > 1) Total runtime: 603.765 ms (3 rows) 初期状態はSeq Scan (cost=28374.00) 変更後はIndex Scan (cost=42169.43)
  12. 12. 12 これさえあればPostgreSQLでもプランが自由自在?
  13. 13.   13 1.実行プランの強制 いつ使うの? プランの切り分け作業 開発時にどうしても特定プランにしたい時 なぜ無闇につかっちゃだめなの? 人はプランナーより賢くない(Tom Laneでもない限り) しかし、プランナーは推測しかしない 適切なコスト変数の設定を 統計情報更新のため定期的なANALYZEを まずはEXPLAIN ANALYZEで問題点を把握しましょう
  14. 14.   14 アジェンダ 1.実行プランの強制 2.EXPLAIN と EXPLAIN ANALYZE 3.問題解決例 4.まとめ
  15. 15.   15 2.EXPLAIN と EXPLAIN ANALYZE EXPLAIN プランナーが作成した"最良の"実行計画を確認するコマンド コストや行数は統計情報を元にした推定 EXPLAIN ANALYZE EXPLAINの出力に追加の情報を加えるオプション 実際にSQLを実行して情報を取得する 負荷のかかるSQLは注意 DMLの変更に注意 "実行時間"や"実際の行数"を取得する
  16. 16.   16 2.EXPLAIN と EXPLAIN ANALYZE EXPLAIN (ANALYZE)の読み方 実行計画は各ステップをノードとするツリー構成 インデントが深いところから実行 子ノードの結果を親ノードが受ける コスト・実行時間は子ノードからの累積
  17. 17. 17 実際の結果を 見てみましょう
  18. 18.   18 2.EXPLAIN と EXPLAIN ANALYZE 実行SQL: EXPLAIN ANALYZE SELECT e.empno,d.dname FROM emp e JOIN dept d ON e.deptno=d.deptno ; Column | Type ----------+----------------------------- empno | integer ename | character varying(10) job | character varying(9) mgr | integer hiredate | timestamp without time zone sal | integer comm | integer deptno | integer EMP表 Column | Type --------+----------------------- deptno | integer dname | character varying(14) loc | character varying(13) DEPT表
  19. 19.   19 2.EXPLAIN と EXPLAIN ANALYZE 実行結果 Hash Join (cost=1.09..2.32 rows=4 width=50) (actual time=0.100..0.120 rows=14 loops=1) Hash Cond:(e.deptno = d.deptno) -> Seq Scan on emp e (cost=0.00..1.14 rows=14 width=8) (actual time=0.013..0.022 rows=14 loops=1) -> Hash (cost=1.04..1.04 rows=4 width=50) (actual time=0.024..0.024 rows=4 loops=1) Buckets: 1024 Batches: 1 Memory Usage: 1kB -> Seq Scan on dept d (cost=0.00..1.04 rows=4 width=50) (actual time=0.011..0.015 rows=4 loops=1) ① ② ③ ④
  20. 20.   20 2.EXPLAIN と EXPLAIN ANALYZE 実行結果をツリーにすると… Seq Scan on emp e cost=0.00..1.14 time=0.013..0.022 Hash Join cost=1.09..2.32 time=0.100..0.120 Hash cost=1.04..1.04 time=0.024..0.024 Seq Scan on dept d cost=0.00..1.04 time=0.011..0.015 cost=1.04+0 time=0.015+0.009 cost=1.04+1.14+0.5 time=0.024+0.022+0.074
  21. 21.   21 2.EXPLAIN と EXPLAIN ANALYZE EXPLAIN ANALYZE SELECT e.empno,d.dname,s.grade FROM emp e JOIN dept d ON e.deptno=d.deptno JOIN salgrade s on e.sal between s.losal and s.hisal where e.job='SALESMAN'; Column | Type ----------+----------------------------- empno | integer ename | character varying(10) job | character varying(9) mgr | integer hiredate | timestamp without time zone sal | integer comm | integer deptno | integer EMP表 Column | Type --------+----------------------- deptno | integer dname | character varying(14) loc | character varying(13) DEPT表 Column | Type --------+--------- grade | integer losal | integer hisal | integer SALGRADE表
  22. 22.   22 2.EXPLAIN と EXPLAIN ANALYZE もうちょっと複雑な出力結果 Nested Loop (cost=0.00..3.39 rows=1 width=50) (actual time=0.031..0.089 rows=4 loops=1) Join Filter: ((emp.sal >= s.losal) AND (emp.sal <= s.hisal)) Rows Removed by Join Filter: 16 -> Nested Loop (cost=0.00..2.26 rows=1 width=54) (actual time=0.022..0.051 rows=4 loops=1) Join Filter: (emp.deptno = d.deptno) Rows Removed by Join Filter: 12 -> Seq Scan on emp (cost=0.00..1.18 rows=1 width=12) (actual time=0.011..0.018 rows=4 loops=1) Filter: ((job)::text = 'SALESMAN'::text) Rows Removed by Filter: 10 -> Seq Scan on dept d (cost=0.00..1.04 rows=4 width=50) (actual time=0.001..0.003 rows=4 loops=4) -> Seq Scan on salgrade s (cost=0.00..1.05 rows=5 width=8) (actual time=0.001..0.002 rows=5 loops=4) ① ② ③ ④ ⑤
  23. 23.   23 2.EXPLAIN と EXPLAIN ANALYZEの違い 実行結果をツリーにすると… Nested Loop Nested Loop Seq Scan on emp Seq Scan on dept d Seq Scan on dept d x 4 Seq Scan on salgrade s x 4 Seq Scan on salgrade s (cost=0.00..1.05 rows=5 width=8) (actual time=0.001..0.002 rows=5 loops=4)
  24. 24.   24 2.EXPLAIN と EXPLAIN ANALYZEの違い EXPLAIN ANALYZEの結果を見るポイント インデントが深いところから 出力結果は子ノードからの累積 各ステップのcost/rows(見積,実際)/actual timeに注目 疑うべきポイント actual timeが跳ね上がっているステップは怪しい rowsが見積もりと離れている箇所は怪しい costに比べてactual timeが長い箇所は怪しい
  25. 25. 25 実際に問題を解決 してみましょう!
  26. 26.   26 アジェンダ 1.実行プランの強制 2.EXPLAIN と EXPLAIN ANALYZE 3.問題解決例 4.まとめ
  27. 27.   27 3.問題解決例 Column | Type --------------+--------- exception_id | integer(primary key) complete | boolean EXCEPTION表 Column | Type -------------------------+--------- exception_notice_map_id | integer exception_id | integer notice_id | integer EXCEPTION_NOTICE_MAP表 complete 列の分布 TRUE FALSE Index Index ● indexは両表のexception_id列のみ作成 ● complete列はFalseのデータが1%未満 (1000行/10000000行)
  28. 28.   28 3.問題解決例 実行するSQL EXPLAIN ANALYZE SELECT exception_id,exception_notice_map_id FROM exception JOIN exception_notice_map USING (exception_id) WHERE complete is false and notice_id=3;
  29. 29.   29 3.問題解決例 結果 Hash Join (cost=175782.31..405182.03 rows=53172 width=8) (actual time=1834.952..1844.389 rows=9 loops=1) Hash Cond: (exception.exception_id = exception_notice_map.exception_id) -> Seq Scan on exception (cost=0.00..144263.00 rows=5000000 width=4) (actual time=789.879..790.120 rows=1000 loops=1) Filter: (complete IS FALSE) Rows Removed by Filter: 9999000 -> Hash (cost=174037.01..174037.01 rows=106344 width=8) (actual time=1044.821..1044.821 rows=100202 loops=1) Buckets: 4096 Batches: 4 Memory Usage: 690kB -> Seq Scan on exception_notice_map (cost=0.00..174037.01 rows=106344 width=8) (actual time=0.081..991.670 rows=100202 loops=1) Filter: (notice_id = 3) Rows Removed by Filter: 9900798 Total runtime: 1844.486 ms
  30. 30. 30 ● 状況整理 – 最上位のノードはrows=9 →9行戻すSQL – 結合はHash Join – 処理時間は1844.486 ms(約2秒) もっと早くならないかな?
  31. 31.   31 3.問題解決例 見積との差をチェック Hash Join (cost=175782.31..405182.03 rows=53172 width=8) (actual time=1834.952..1844.389 rows=9 loops=1) Hash Cond: (exception.exception_id = exception_notice_map.exception_id) -> Seq Scan on exception (cost=0.00..144263.00 rows=5000000 width=4) (actual time=789.879..790.120 rows=1000 loops=1) Filter: (complete IS FALSE) Rows Removed by Filter: 9999000 -> Hash (cost=174037.01..174037.01 rows=106344 width=8) (actual time=1044.821..1044.821 rows=100202 loops=1) Buckets: 4096 Batches: 4 Memory Usage: 690kB -> Seq Scan on exception_notice_map (cost=0.00..174037.01 rows=106344 width=8) (actual time=0.081..991.670 rows=100202 loops=1) Filter: (notice_id = 3) Rows Removed by Filter: 9900798 Total runtime: 1844.486 ms OK! OK! ずれてる!?
  32. 32.   32 3.問題解決例 exception表でcompelete is Falseの行は 5000000行くらいかな 結合相手も行が多いしたくさん もどりそうだからHashJoinしよう (cost=0.00..144263.00 rows=5000000 width=4) (actual time=789.879..790.120 rows=1000 loops=1) 1000行しかなかった… 統計情報が古い気がする… プランナー プランナー
  33. 33. 33 そうだ、ANALYZE、しよう
  34. 34.   34 3.問題解決例 ANALYZE exceptionしてみた Nested Loop (cost=0.43..152601.93 rows=11 width=8) (actual time=792.030..794.257 rows=9 loops=1) -> Seq Scan on exception (cost=0.00..144262.43 rows=1000 width=4) (actual time=790.677..790.885 rows=1000 loops=1) Filter: (complete IS FALSE) Rows Removed by Filter: 9999000 -> Index Scan using idx_nmap_exception_id on exception_notice_map (cost=0.43..8.33 rows=1 width=8) (actual time=0.003..0.003 rows=0 loops=1000) Index Cond: (exception_id = exception.exception_id) Filter: (notice_id = 3) Rows Removed by Filter: 1 Total runtime: 817.182 ms ずれがなくなった!! 早くなった! 1844.486 ms→817.182 ms
  35. 35. 35 ANALYZEで最新の 統計を使いましょう!
  36. 36.   36 3.問題解決例 再度結果を確認 Nested Loop (cost=0.43..152601.93 rows=11 width=8) (actual time=792.030..794.257 rows=9 loops=1) -> Seq Scan on exception (cost=0.00..144262.43 rows=1000 width=4) (actual time=790.677..790.885 rows=1000 loops=1) Filter: (complete IS FALSE) Rows Removed by Filter: 9999000 -> Index Scan using idx_nmap_exception_id on exception_notice_map (cost=0.43..8.33 rows=1 width=8) (actual time=0.003..0.003 rows=0 loops=1000) Index Cond: (exception_id = exception.exception_id) Filter: (notice_id = 3) Rows Removed by Filter: 1 Total runtime: 817.182 ms 1%未満の行にSeq Scanで アクセスしている complete 列の分布 TRUE FALSE
  37. 37.   37 3.問題解決例 Seq Scanを辞めたいならINDEXを張るのが定石 でもcomplete列はTrue/Falseの2種類しかない カーディナリティが低いのでINDEX作成の負荷が心配 INDEXを使って欲しいのがFalseの時だけ INDEXをつけるのは難しいかな・・・?
  38. 38. 38 そうだ部分インデックスがあるじゃないか! CREATE INDEX idx_is_complete ON exception(complete) WHERE complete IS false;
  39. 39.   39 3.問題解決例 部分インデックスとは 条件を満たす行のみを保持するインデックス 頻出値にインデックスを付けずに済むため インデックスのサイズが小さい インデックスの更新処理が発生しにくいので 更新パフォーマンスが有利
  40. 40.   40 3.問題解決例 部分INDEX作ったら... Nested Loop (cost=0.71..8347.79 rows=11 width=8) (actual time=0.266..5.241 rows=9 loops=1) -> Index Scan using idx_is_complete on exception (cost=0.28..8.29 rows=1000 width=4) (actual time=0.073..0.680 rows=1000 loops=1) Index Cond: (complete = false) -> Index Scan using idx_nmap_exception_id on exception_notice_map (cost=0.43..8.33 rows=1 width=8) (actual time=0.004..0.004 rows=0 loops=1000) Index Cond: (exception_id = exception.exception_id) Filter: (notice_id = 3) Rows Removed by Filter: 1 Total runtime: 5.286 ms Index Scanが使われるようになった! 817.182 ms->5.286 ms 160倍早い!
  41. 41. 41 PostgreSQLの色々な機能を活用しよう! ・部分インデックス ・Materialized View  等(細かい部分はマニュアルで)
  42. 42.   42 アジェンダ 1.実行プランの強制 2.EXPLAIN と EXPLAIN ANALYZE 3.問題解決例 4.まとめ
  43. 43.   43 4.まとめ EXPLAIN ANALYZEで問題を探すなら インデントが深いところから 見積もりがずれているところから 時間が伸びているところから
  44. 44.   44 4.まとめ 問題に対処するなら 見積もりがおかしかったら →ANALYZE してみましょう アクセス行数が少ないのにSeq Scanだったら →インデックスを検討しましょう PostgreSQLの機能を活用しましょう 最新のPostgreSQLを使いましょう
  45. 45. 45 それでも解決しない時はどうすれば いいのだろう?
  46. 46.   46 4.まとめ メーリングリストに投稿してみましょう まず自分でデバッグしてみる PostgreSQLのバージョンを書く VACUUMとANALYZEを正確に実行してあること EXPLAIN ANALYZEの結果を必ず書く クエリ、テーブル、データもできれば含める pgsql-performance@postgresql.org (英語) pgsql-jp@ml.postgresql.jp (日本語)
  47. 47. 47 ご清聴ありがとうございました 参考資料(サイト) Explaining Explain ~ PostgreSQLの実行計画を読む ~ http://lets.postgresql.jp/documents/technical/query_tuning/explaining_explain_ja.pdf/view 内部を知って業務に活かす PostgreSQL研究所第4回 http://www2b.biglobe.ne.jp/~caco/webdb-pdfs/vol29.pdf Robert Haas blog http://rhaas.blogspot.com/2011/10/index-only-scans-weve-got-em.html 問合せ最適化インサイド http://www.slideshare.net/ItagakiTakahiro/ss-4656848 象と戯れ http://postgresql.g.hatena.ne.jp/umitanuki/20110425/1303752697 Explaining Explain 第2回 http://www.postgresql.jp/wg/shikumi/study20_materials Explaining Explain 第3回 http://www.postgresql.jp/wg/shikumi/study21_materials 参考資料(書籍) PostgreSQL 全機能バイブル(技術評論社) PostgreSQL 設計・運用計画の鉄則(技術評論社)
  48. 48.   48 おまけ 実際の運用に際して有用な機能 auto_explain(contribモジュール) 自動的にexplainしてくれる 自動的にログに書いてくれる 実行時間等の条件を指定可能 psqlで試せないSQLでも取得可能
  49. 49.   49 おまけ マテリアライズドビューが効くケース EXPLAIN ANALYZE SELECT notice_id,count(*) as count FROM exception JOIN exception_notice_map USING (exception_id) GROUP BY notice_id order BY count;
  50. 50.   50 おまけ マテリアライズドビューが効くケース Sort (cost=864852.14..864852.64 rows=200 width=4) (actual time=78419.788..78419.803 rows=100 loops=1) Sort Key: (count(*)) Sort Method: quicksort Memory: 20kB -> HashAggregate (cost=864842.50..864844.50 rows=200 width=4) (actual time=78419.706..78419.736 rows=100 loops=1) -> Hash Join (cost=303459.50..814837.50 rows=10000000 width=4) (actual time=27756.900..75436.393 rows=10000000 loops=1) Hash Cond: (exception_notice_map.exception_id = exception.exception_id) -> Seq Scan on exception_notice_map (cost=0.00..149035.00 rows=10000000 width=8) (actual time=0.040..2860.579 rows=10000000 loops=1) -> Hash (cost=144263.00..144263.00 rows=10000000 width=4) (actual time=27575.905..27575.905 rows=10000000 loops=1) Buckets: 8192 Batches: 256 Memory Usage: 929kB -> Seq Scan on exception (cost=0.00..144263.00 rows=10000000 width=4) (actual time=0.021..2511.089 rows=10000000 loops=1) Total runtime: 78419.966 ms OK! OK! OK! OK! たぶんOK たぶんOK。。。 だけど遅すぎる
  51. 51. 51 そうだマテリアライズドビューがあるじゃないか! ※9.3〜 CREATE MATERIALIZED VIEW exception_notice_summary AS SELECT notice_id,count(*) as count FROM exception JOIN exception_notice_map USING (exception_id) GROUP BY notice_id ;
  52. 52.   52 おまけ マテリアライズドビューとは 実データを保持するビュー 計算済みのデータを保持するため、問合せ負荷が低い 最新の状態を反映するには定期的なリフレッシュが必須 DWH系の処理に向いている
  53. 53.   53 おまけ 結果 Sort (cost=135.34..140.19 rows=1940 width=12) (actual time=0.112..0.141 rows=100 loops=1) Sort Key: count Sort Method: quicksort Memory: 20kB -> Seq Scan on exception_notice_summary (cost=0.00..29.40 rows=1940 width=12) (actual time=0.010..0.046 rows=100 loops=1) Total runtime: 0.187 ms explain analyze SELECT * FROM exception_notice_summary ORDER BY count;

×