59
VACUUM解析資料 28PostgreSQLしくみ+アプリケーション勉強会 2014/02/01 澤田雅彦、江川大地

Inside vacuum - 第一回PostgreSQLプレ勉強会

Embed Size (px)

Citation preview

Page 1: Inside vacuum - 第一回PostgreSQLプレ勉強会

VACUUM解析資料第28回PostgreSQLしくみ+アプリケーション勉強会

2014/02/01 澤田雅彦、江川大地

Page 2: Inside vacuum - 第一回PostgreSQLプレ勉強会

目次0. はじめに ~プレ勉強会のご紹介 1. VACUUMとは 2. VACUUMの進化 3. VACUUM内部処理の流れ 4. CONCURRENT VACUUMの詳細 5. FULL VACUUMの詳細

2

Page 3: Inside vacuum - 第一回PostgreSQLプレ勉強会

はじめに ~まずは自己紹介から✦名前 澤田雅彦(twitter : @sawada_masaho)

✦ソースコードの読み方 emacs + etags

✦近況 •chefと格闘しました。 •gitのソースコードを読んでます。 !

✦名前 江川大地(twitter : @daiti0804)

✦ソースコードの読み方 vi + ctags

✦近況 • 転職しました。 • シアトルに行ってきました。 3

Page 4: Inside vacuum - 第一回PostgreSQLプレ勉強会

はじめに ~プレ勉強会のご紹介

4

プレ勉強会とは、しくみ分科会の前に集まり、PostgreSQLのコアに限定した内容を学んでいこうという趣旨の勉強会です。★勉強会の場

■ 日時:しくみ分科会+アプリケーション分科会の開催同日 ■ 場所:しくみ分科会+アプリケーション分科会と同じ ■ 勉強内容:ソースコードをはじめとするPostgreSQLのコア !

★進め方 1. 主導する人と勉強する内容を決定 2. 主導する人を中心に次回の勉強会の準備

(ソースコードリーディングを例にすると) 2.1. 主導する人がソースコードを読む 2.2. 詰まった部分や進捗状況をMLでシェア。 2.3. MLに参加している人がサポート。 2.4. プレ勉強会で話す資料の作成

3. プレ勉強会での発表

Page 5: Inside vacuum - 第一回PostgreSQLプレ勉強会

勉強内容の例• PostgreSQLソースコードリーディング

‣ PostgreSQLコアのソースコードを読み、実装方法を共有。

• PostgreSQL機能追加

‣ プレ勉強会で、PostgreSQL本体に機能追加する案を出して、議論する。実装する。

• PostgreSQLパッチレビュー

‣ CommitFestに出されているパッチをレビュー。その機能やレビューないようをプレ勉強会で共有。

• PostgreSQL放置モジュールのアップデート

‣ textsearch-jaなど、PostgreSQLのアップデートに対応していないツールのアップデート。ツールやアップデート内容を共有。

5

Page 6: Inside vacuum - 第一回PostgreSQLプレ勉強会

ソースコードリーディング案1つの案として、井久保さんのソースコード解析資料(http://ikubo.x0.com/PostgreSQL/pg_source.htm)をアップデートする形で進めていこうと考えています。No テーマ 説明 担当1 ソースツリーとPostgreSQLのアーキテクチャ ソースコードの概要とPostgreSQLのアーキテクチャ2 初期DBの作成処理(Bootstrap) 主に initdb で実行される内容3 ストレージマネージャ smgr, file, page, buffer, freespace4 メモリ管理 utils/mmgr5 postmaster postmaster の処理フロー、シグナル処理など6 postgresプロセスの概要 postgresプロセスの処理フロー、エラー処理など7 プロセス間通信(IPC) 共有メモリ、シグナル、共有 inval メッセージ8 MVCC MVCCの動作および実装方法について9 node 構造 node 構造の説明10 字句解析と構文解析 字句解析器と構文解析器の説明。簡単にflexとbisonも解11 トランザクションマネージャ  12 VACUUMの実装 VACUUMの実装に付いての説明。 澤田、江川13 意味解析器(アナライザ) parse_analyze() の処理についての説明。14 バックグラウンドライタプロセス バックグラウンドライタプロセスについて説明。15 レプリケーション 非同期、同期、カスケードレプリケーションの実装方法16 WAL 基本的なWALの構造。17 ロック ロックの実装方法  18 View View Materialized Viewの実装方法19 トリガ トリガー、イベントトリガーの実装方法20 FDW FDWの実装方法21 データ型 9.0以降に追加されたデータ型について

:

Page 7: Inside vacuum - 第一回PostgreSQLプレ勉強会

ソースコードの読み方の一例(vi + ctags編) • vi + ctagsによるソースコードの読み方とは

ctagsにより、ソース内に書かれているメソッドなどの呼出し元と呼び出し側を対応づけるタグファイルを生成し、viでソースコードを読む流儀

• 読むための準備(タグの付け方)

7

$ wget http://ftp.postgresql.org/pub/source/v9.3.2/postgresql-9.3.2.tar.gz $ tar xvfz postgresql-9.3.2.tar.gz $ ./configure --enable-debug $ make $ sudo make install $ cd postgresql-9.3.2.tar.gz/src/backend $ ctags */*.[chyl] */*/*.[ch] ../include/*.h ../include/*/*.h

$ view commands/vacuum.c

※メソッドに入るには、”Ctrl” + “]” ※メソッドから抜けるには、”Ctrl” + “t”

•読み方 タグ付けを行ったディレクトリから読む

Page 8: Inside vacuum - 第一回PostgreSQLプレ勉強会

ソースコードの読み方の一例(emacs編) •etagsによるソースコードの読み方とは

etagsにより、ソース内に書かれているメソッドなどの呼出し元と呼び出し側を対応づけるタグファイルを生成し、emacsでソースコードを読む流儀

• 読むための準備(タグの付け方)

!!!!!!

•読み方

!※ M-. <関数名> でタグジャンプ ※ M-* でジャンプ元に戻る

8

$ wget http://ftp.postgresql.org/pub/source/v9.3.2/postgresql-9.3.2.tar.gz $ tar xvfz postgresql-9.3.2.tar.gz $ ./configure --enable-debug $ make $ sudo make install $ cd postgresql-9.3.2.tar.gz/src/ $ sh tool/make-etags

$ emacs vacuum.c

Page 9: Inside vacuum - 第一回PostgreSQLプレ勉強会

ソースコードの読み方の一例(doxygen編) •doxygenによるソースコードの読み方とは

doxygenとは、PostgreSQLのソースコードを読めるコンテンツです。メソッドの呼出し元、呼び出し先がリンク付けられたweb上のドキュメントを読み進める流儀。

!

• URL : http://doxygen.postgresql.org/ !

9

Page 10: Inside vacuum - 第一回PostgreSQLプレ勉強会

1.Vacuumとは

Page 11: Inside vacuum - 第一回PostgreSQLプレ勉強会

VACUUMとは…の前に簡単にPostgreSQLのアーキテクチャの話を…

11

Page 12: Inside vacuum - 第一回PostgreSQLプレ勉強会

VACUUMとは…の前にPostgreSQLは追記型アーキテクチャを採用しています。 ➢データの変更があっても元のレコードを物理的に消さず、新しい行を追加して、元のレコードを無効とマークします。

12

ID NAME1 test12 test23 test3

ID NAME1 test12 test23 test32 test2’3 test3’

UPDATE

UPDATE tablename SET NAME=test2’ WHERE ID = 2;!UPDATE tablename SET NAME=test3’ WHERE ID = 3;

:不要領域:ページ

Page 13: Inside vacuum - 第一回PostgreSQLプレ勉強会

VACUUMとはVACUUMは、不要領域を再利用可能にし、その箇所をFSM(空き領域マップ)に登録するコマンドです。

13

ID NAME1 test12 test23 test32 test2’3 test3’

VACUUM実行

VACUUMにより!不要領域回収

:不要領域:ページ

ID NAME1 test12 test23 test32 test2’3 test3’

ID NAME1 test1

2 test2’3 test3’

VACUUM完了

再利用可能に!

Page 14: Inside vacuum - 第一回PostgreSQLプレ勉強会

VACUUMとVACUUM FULL✦ VACUUMは、不要領域を再利用可能な状態にします。 ✦ VACUUM FULLは、物理的にファイルを圧縮します。

‣ 排他ロックが必要なため、VACUUM FULL中はテーブルへアクセス不可

14

:不要領域:ページ

ID NAME1 test12 test23 test32 test2’3 test3’

VACUUM

VACUUM FULL

ID NAME1 test1

2 test2’3 test3’

ID NAME1 test12 test2’3 test3’

物理的に!サイズ縮小

SHARE UPDATE!EXCLUSIVE

ACCESS!EXCLUSIVE

Page 15: Inside vacuum - 第一回PostgreSQLプレ勉強会

2.Vacuumの進化

Page 16: Inside vacuum - 第一回PostgreSQLプレ勉強会

VACUUMの進化PostgreSQLのアップグレードとともにVACUUMも進化しています。特に8系では様々な改良が入り、PostgreSQLの運用性向上の要素となっています。

16

8.4 9.0

VACUUM FULL高速化!(新VACUUM FULLの実装)

~ 8.2

VACUUMの!基礎固め

9.2~

FSM自動管理!Visibility Mapの導入8.3

autovacuumの改良!HOTの導入 9.1

VACUUM FULL VERBOSE!ログの改良

Page 17: Inside vacuum - 第一回PostgreSQLプレ勉強会

autovacuumの改良autovacuumは定期的にテーブル状態を監視し、必要があれば自動でVACUUMやANALYZE

を実施する機能です。8.2までは1プロセスしか割り当てられませんでしたが、8.3以降、複数プロセスを割り当てる(autovacuum_max_workers)ことができるようになりました。

17

VACUUM VACUUM VACUUM

~ 8.2 8.3~ シングルプロセス マルチプロセス

大きなテーブルに長時間かかり、他のテーブルの不要領域がなかなか回収されないことも…

大きなテーブルのVACUUM中も並行して実行できる。

Page 18: Inside vacuum - 第一回PostgreSQLプレ勉強会

HOT HOT(Heap Only Tuple)は、テーブルデータの更新時にインデックスデータの更新をスキップできる仕組みです。これにより、以下のメリットが生まれます。

1.インデックスデータの更新が無いため、更新処理そのものが高速化 2.更新済みデータ(ガベージ)は、VACUUMを待たずして再利用可能領域化できる

UPDATE tbl SET 在庫= 99 WHERE ID=2

ID 名前 在庫1 A 102 B 8

索引

ID 名前 在庫1 A 102 B 82 B 99

索引

ID 名前 在庫1 A 102 B 82 B 99

索引

~ 8.2 8.3~ VACUUMが実行されるまで不要領域は再利用不可

インデックスも更新される

VACUUMを待たずに不要領域を再利用可能!→不要領域の増加を抑制

転送エントリを利用することで、インデックスの更新を回避。

これにより、VACUUMが改修すべきガベージの量を大幅に削減でき、これにより頻繁に更新が走るテーブルへのVACUUM回数が削減。

Page 19: Inside vacuum - 第一回PostgreSQLプレ勉強会

Visibility MapVisibility Map(VM)は、テーブルのどの部分に不要領域があるかを記録しているマップファイルです。各テーブル毎に持っています。これにより、VACUUMは、VACUUMすべき箇所を特定して処理を実施します。

VACUUM

VACUUM

~ 8.3 8.4~ テーブル テーブルVM

VACUUMの必要のない部分もスキャン。

VMを見て、必要な箇所のみスキャン。

これにより、VACUUMのスキャン範囲が局所化され、実施時間の短縮やI/O削減が期待できます。特に、ガベージがテーブルの一部分に集中している場合にはその効果が明確に現れます。

Page 20: Inside vacuum - 第一回PostgreSQLプレ勉強会

Free Space Map(FSM)自動管理FSMは、VACUUMによる不要領域が除去され、再利用可能になった箇所を記録するマップファイルです。8.3まではDBクラスタにひとつ存在し、ユーザがサイズを指定(max_fsm_pages)する必要がありました。8.4からはテーブル毎にFSMを持ち、自動で管理されています。

VACUUM

VACUUM

~ 8.3 8.4~

再利用OK

ブランク

ブランク

ブランク

再利用OK

再利用OK

再利用OK

再利用OK

テーブル テーブル

FSM (DBクラスタで1つ)

FSM (テーブルで1つ)

NG

空き領域として登録

max_fsm_pagesを超えると記録不可

FSMは自動で 必要な分を記録

再利用されず、肥大化の原因に

従来はFSMサイズにガベージ回収に必要十分な量を指定しておかなければ、VACUUMを実施してもブランク領域が増え続けテーブルの肥大化が発生していました。今では必要な分を自動で記録するように改善されています。

Page 21: Inside vacuum - 第一回PostgreSQLプレ勉強会

3. VACUUM内部処理の流れ

Page 22: Inside vacuum - 第一回PostgreSQLプレ勉強会

VACUUMソースコードのありか✦VACUUMのソースコード •src/backend/commands配下 vacuum.c:VACUUM, VACUUM FULLの流れ vacuumlazy.c:VACUUMの処理 cluster.c:VACUUM FULLの処理 !

✦VACUUMに関係する機能のソースコード •Visibility Map access/heap/visibilitymap.c !

•フリースペースマップ(FSM) storage/freespace/freespace.c

22

Page 23: Inside vacuum - 第一回PostgreSQLプレ勉強会

VACUUMの概要

23

vacuum()

vacuum_rel()

対象のリレーションをリストアップ

対象のリレーション一つずつに対して以下を繰り返す

VACUUMまたはVACUUM FULLの場合

ANALYZEの場合

analyze_rel()

リレーションにロックの取得

権限のチェック

VACUUM FULLの場合

VACUUMの場合

lazy_vacuum_rel()

cluster_rel()

VACUUM FULL : AccessExclusiveLock VACUUM : SharedUpdateExclusiveLock

Page 24: Inside vacuum - 第一回PostgreSQLプレ勉強会

4. CONCURRENT VACUUMの詳細

Page 25: Inside vacuum - 第一回PostgreSQLプレ勉強会

CONCURRENT VACUUM概要✦オプションなしで VACUUM を実行した場合の処理です。 !

!

✦テーブルのページ単位での不要領域の回収とそのインデックスの削除を行います。

!

✦処理中は、テーブルの排他的ロックを取得しないため、通常のデータベース処理と並行して実行することができる。

25

postgres=# VACUUM;

Page 26: Inside vacuum - 第一回PostgreSQLプレ勉強会

CONCURRENT VACUUMのメカニズム✦CONCURRENT VACUUMは4つの段階を経て実施します。 !

1. 不要領域の列挙 maintenance_work_memに格納可能な分、ヒープページを読み込み、不要領域の一覧を書き出します。

2. VACUUM対象タプルのインデックス・エントリ削除 全インデックスページを読み込み、不要領域へのエントリを除去し、書き出します。

3. ページごとの不要領域除去(lazy_vacuum_page)

VACUUM 対象ページを読み込み、不要領域を除去し、書き出します。

4. テーブル末尾の切り詰め テーブル末尾に有効な行が無い場合、その領域を切り詰め、ファイルを縮小します。

26

Page 27: Inside vacuum - 第一回PostgreSQLプレ勉強会

lazy_vacuum_rel()

lazy_scan_heap()

lazy_vacuum_heap()

lazy_vacuum_page()

2.インデックス・エントリ削除

3. ページごとの不要領域除去

↓対象のリレーション一つずつに対して呼ばれる

lazy_vacuum_index()

1.不要領域の列挙

4.テーブル末尾の切り詰め

lazy_truncate_heap()

27

ブロックごとにループ

Page 28: Inside vacuum - 第一回PostgreSQLプレ勉強会

CONCURRENT VACUUMのソースコードlazy_vacuum_rel()

- vacuum_set_xid_limits():実行中の全トランザクション中での一番古い xmin の取得と、FREEZE する XID のカットオフポイントの計算を行う。 - LVRelStats構造体: (VACUUM情報保持用) の初期化 - vac_open_indexes:リレーションが保有しているインデックスをオープンする(RowExclusiveLock)。 - lazy_scan_heap:vacuumの実行。

- lazy_space_alloc() : 不要タプル格納領域の確保。(max_fsm_pagesの設定値は8.4以降削除) ✦ [不要タプルが一杯の場合]

- lazy_vacuum_index():インデックス・エントリ削除 - lazy_vacuum_heap():lazy_vacuum_page()呼び出し後、テーブル末尾の切り詰め。 - lazy_vacuum_page() :不要領域除去

✦ [不要タプルが一杯でない場合] - 不要領域の列挙 - lazy_vacuum_index() : インデックス・エントリ削除 - lazy_vacuum_heap() : lazy_vacuum_page()呼び出す。 - lazy_vacuum_page() : 不要領域除去

- vac_close_indexes : インデックスをClose(vacuum.cを参照) - lazy_truncate_heap(): ファイル末尾を切り詰め。scan_allしている場合 - FreeSpaceMapVacuum():FSMのvacuum - vac_update_relstats : 統計情報の更新 - pgstat_report_vacuum:stats collectorへもvacuumしたテーブルのことを送信

28

Page 29: Inside vacuum - 第一回PostgreSQLプレ勉強会

テーブルをスキャンして、ページ 単位のVACUUM、インデックスのVACUUMを行い、スキャン中にページ のフリースペース情報を構築していくメソッド

lazy_scan_heap()

✦ lazy_scan_heap() - lazy_space_alloc()

‣ CONCURRENT VACUUMで使用する領域を確保する(max_dead_tuplesに、maintenance_work_mem 分の領域を割り当てる)。 - visibility map によるテーブルの確認

‣ heap内のブロックを順番に確認し、ブロック上の全タプルが、誰からも可視(visible)かチェックする。可視でないブロック(next_not_all_visible_block)が見つかった時点で、ループを中断(break)する。

‣可視であるブロックが、SKIP_PAGES_THRESHOLDよりも多かった場合は、skipping_all_visible_blocksにtrueを格納する(そうでない場合はfalse)。

-ブロックごとにループ(ブロックナンバーを0からインクリメントしていく) ‣ブロックナンバーが、next_not_all_visible_blockと一致する場合

‣次のnext_not_all_visible_blockを探す(ブロック上の全タプルが、誰からも可視(visible)かチェックし、可視でないブロックが見つかった時点で、ループを中断)。 ‣可視であるブロックが、SKIP_PAGES_THRESHOLDよりも多かった場合は、skipping_all_visible_blocksにtrueを格納する

(そうでない場合はfalse)

‣上記に当てはまらない場合

‣ VACUUM処理をスキップする(continue文)。 ‣ lazy_vacuum_index() : index_bulk_delete()を呼び出して、VACUUM対象タプルのインデックスエントリをまとめて削除する。 ‣ lazy_vacuum_heap() : lazy_vacuum_page()を呼び出して、ページごとの不要領域を除去する。

29

Page 30: Inside vacuum - 第一回PostgreSQLプレ勉強会

テーブルをスキャンして、ページ 単位のVACUUM、インデックスのVACUUMを行い、スキャン中にページ のフリースペース情報を構築していくメソッド

lazy_scan_heap()

① lazy_space_alloc()を呼び出し、max_dead_tuplesに、maintenance_work_mem 分の領域を割り当て、CONCURRENT VACUUMで使用する領域を確保する。 ② テーブルの先頭のブロックから順番に、visibilitymap_test()で確認していき、初めて不要領域があるブロックを探す。 ③ (ここからブロックごとのループに入る) ブロックを順番に確認していき、不要領域がないブロックの場合はスキップする。 ④ 不要領域があるブロックの場合、次の不要領域があるブロックを探す。

30

Page 31: Inside vacuum - 第一回PostgreSQLプレ勉強会

lazy_scan_heap() lazy_scan_heap() では、Visibility Mapを利用して、VACUUMする必要のないブロックをスキップします。visibilitymap_test()で、テーブルの先頭のブロックから順番に、visibilitymap_test()で不要領域があるかチェックする。

0

1

2

3

4

5

6

7

8

テーブルVM visibilitymap_test()true

falsetruetrue

Page 32: Inside vacuum - 第一回PostgreSQLプレ勉強会

lazy_scan_heap()

0

1

2

3

4

5

6

7

8

テーブルVM visibilitymap_test()true

falsetruetrue

next_not_all_visible_block = 3 次に不要領域が現れるのは 3ブロック目

lazy_scan_heap() では、Visibility Mapを利用して、VACUUMする必要のないブロックをスキップします。visibilitymap_test()で、テーブルの先頭のブロックから順番に、visibilitymap_test()で不要領域があるかチェックする。

Page 33: Inside vacuum - 第一回PostgreSQLプレ勉強会

lazy_scan_heap()

0

1

2

3

4

5

6

7

8

テーブルVM visibilitymap_test()true

falsetruetrue

next_not_all_visible_block = 3 次に不要領域が現れるのは 3ブロック目

ここはスキップ

lazy_scan_heap() では、Visibility Mapを利用して、VACUUMする必要のないブロックをスキップします。visibilitymap_test()で、テーブルの先頭のブロックから順番に、visibilitymap_test()で不要領域があるかチェックする。

Page 34: Inside vacuum - 第一回PostgreSQLプレ勉強会

lazy_scan_heap()

0

1

2

3

4

5

6

7

8

テーブルVM visibilitymap_test()true

falsetruetrue

next_not_all_visible_block = 3 次に不要領域が現れるのは 3ブロック目

ここはスキップ

lazy_scan_heap() では、Visibility Mapを利用して、VACUUMする必要のないブロックをスキップします。visibilitymap_test()で、テーブルの先頭のブロックから順番に、visibilitymap_test()で不要領域があるかチェックする。

ここは不要領域あり

Page 35: Inside vacuum - 第一回PostgreSQLプレ勉強会

lazy_scan_heap()

0

1

2

3

4

5

6

7

8

テーブルVM visibilitymap_test()true

falsetruetrue

ここはスキップ

falsetrue

next_not_all_visible_block

を設定しよう!

lazy_scan_heap() では、Visibility Mapを利用して、VACUUMする必要のないブロックをスキップします。visibilitymap_test()で、テーブルの先頭のブロックから順番に、visibilitymap_test()で不要領域があるかチェックする。

ここは不要領域あり

Page 36: Inside vacuum - 第一回PostgreSQLプレ勉強会

lazy_scan_heap()

VACUUM

0

1

2

3

4

5

6

7

8

テーブルVM visibilitymap_test()true

falsetruetrue

ここはスキップ

falsetrue

next_not_all_visible_block = 5

lazy_scan_heap() では、Visibility Mapを利用して、VACUUMする必要のないブロックをスキップします。visibilitymap_test()で、テーブルの先頭のブロックから順番に、visibilitymap_test()で不要領域があるかチェックする。

Page 37: Inside vacuum - 第一回PostgreSQLプレ勉強会

テーブルをスキャンして、ページ 単位のVACUUM、インデックスのVACUUMを行い、スキャン中にページ のフリースペース情報を構築していくメソッド

lazy_scan_heap()

① lazy_space_alloc()を呼び出し、max_dead_tuplesに、maintenance_work_mem 分の領域を割り当て、CONCURRENT VACUUMで使用する領域を確保する。 ② テーブルの先頭のブロックから順番に、visibilitymap_test()で確認していき、初めて不要領域があるブロックを探す。 ③ (ここからブロックごとのループに入る) ブロックを順番に確認していき、不要領域がないブロックの場合はスキップする。 ④ 不要領域があるブロックの場合、次の不要領域があるブロックを探す。 ⑤ visibilitymap_pin()で、vmのどこまでを読んだか記録する。 ⑥ ページのタプルの状態を検査する。 タプルの状態によって処理がかわる。

HEAPTUPLE_DEAD : HOT更新されたタプルでなければ、lazy_record_dead_tuple()で VACUUM対象として、そのタプルの位置を登録。 HEAPTUPLE_LIVE : oidの妥当性チェックなどを行うのみで、VACUUM対象とはしない。 その他の状態 : VACUUM対象とはしない。

⑦ lazy_vacuum_index()で、ページ内のVACUUM対象のタプルのインデックスエントリを削除する。 ⑧ lazy_vacuum_page()で、ページ内の不要領域を除去する、 ⑨ テーブル末尾に空白がある場合は、lazy_truncate_heap()で、空白を切り詰める。

37

Page 38: Inside vacuum - 第一回PostgreSQLプレ勉強会

5. FULL VACUUMの詳細

Page 39: Inside vacuum - 第一回PostgreSQLプレ勉強会

VACUUM FULLの概要•ページをまたがった領域の切り詰めを行う •処理はCONCURRENT VACUUMに比べてシンプル •CLUSTERコマンドでの処理と似ている • postgres=# VACUUM FULL; •テーブルを指定することで、指定されたテーブルに対してのみ

VACUUM FULLを行う •テーブルが指定されていない場合は全テーブルが対象

39

Page 40: Inside vacuum - 第一回PostgreSQLプレ勉強会

vacuum fullのメカニズム•cluster.cの中でCLUSTERコマンドで使用される関数と同じ関数を使用する

•同じ関数の中で、CLUSTERコマンドでの処理との区別は、引数で渡されるindexOidでしている •CLUSTERコマンドの場合は、CLUSTERで使用するIndexのOIDが入っている

•VACUUM FULLではインデックスは使用しないのでInvalidOID(=0)がはいっている

•そのため、ソースを読むときはCLUSTERコマンドでの処理と分けながら読んでいくことが大切

40

Page 41: Inside vacuum - 第一回PostgreSQLプレ勉強会

41

cluster_rel()

rebuild_relation()

copy_heap_data()

reform_and_rewrite_tuple()

rewrite_heap_tuple()

raw_tuple_insert()

移動先のリレーション作成

タプルを一つずつ読み込んで、以下を繰り返し

古いタプルを元に新しいタプルを作成

新しいタプルを新しいリレーションに挿入

↓対象のリレーション一つずつに対して呼ばれる

Page 42: Inside vacuum - 第一回PostgreSQLプレ勉強会

DEAD

DEADDEAD

DEAD

図にすると

42

DEAD

DEAD

古いリレーション

Page 43: Inside vacuum - 第一回PostgreSQLプレ勉強会

DEAD

DEADDEAD

DEAD

図にすると

43

DEAD

DEAD

古いリレーション

新しいリレーション

rebuild_relation() 移動先のリレーションを作成

Page 44: Inside vacuum - 第一回PostgreSQLプレ勉強会

DEAD

DEADDEAD

DEAD

図にすると

44

DEAD

DEAD

古いリレーション

新しいリレーション

copy_heap_data() タプルを一つに読み込む

Page 45: Inside vacuum - 第一回PostgreSQLプレ勉強会

DEAD

DEADDEAD

DEAD

図にすると

45

DEAD

DEAD

古いリレーション

新しいリレーション

reform_and_rewrite_tuple() 新しいタプルを作成

Page 46: Inside vacuum - 第一回PostgreSQLプレ勉強会

DEAD

DEADDEAD

DEAD

図にすると

46

DEAD

DEAD

古いリレーション

新しいリレーション

rewrite_heap_tuple() raw_insert_tuple()

新しいリレーションに挿入

Page 47: Inside vacuum - 第一回PostgreSQLプレ勉強会

DEAD

DEADDEAD

DEAD

図にすると

47

DEAD

DEAD

古いリレーション

新しいリレーション

これらの処理を生きている タプルに対して行う

Page 48: Inside vacuum - 第一回PostgreSQLプレ勉強会

VACUUM FULLのソースコード(1/4):cluster_rel()

!cluster_rel(tableOid, indexOid, recheck, verbose)

{

- try_relation_open()で対象のリレーションをopenする - CheckTableNotInUse() - TransferPredicateLocks(OldHeap) - rebuild_relation(OldHeap, indexOid, verbose)

}

——————————————————————————————————————————

rebuild_relation(OldHeap, indexOid, verbose)

{

- make_new_heap(tableOid, tableSpace, false, AccessExclusiveLock) -移動先のリレーションを作成

- copy_heap_data(OIDNewHeap, tableOid, indexOid, verbose &swap_toast_by_content, &frozenXid, &cutoffMulti);

- finish_heap_swap(tableOid, OIDNewHeap, is_system_catalog swap_toast_by_content, false true, frozenXid, cutoffMulti)

} 48

• VACUMM FULLを実行するリレーションひとつずつに対して呼ばれる • VACUUM FULLから呼ばれる場合は、recheckは必ずfalse • CLUSTERでもこの関数を使用

Page 49: Inside vacuum - 第一回PostgreSQLプレ勉強会

VACUUM FULLのソースコード(2/4)

copy_heap_data()copy_heap_data(OIDNewHeap, OIDOldHeap, OIDOldIndex, verbose, pSwapToastByContent, pFreezeXid, pCutoffMulti)

{

- heap_open() - NewHeap, OldHeapをOIDNewHeapとOIDOldheapを元にopen

- RelationGetDescr() - newTupDesc(), oldTupDesc()を取得

- use_wal = XLogIsNeeded() && RelationNeedsWAL(NewHeap); - XLogIsNeeded() : - RelationNeedsWAL() :

- vacuum_set_xid_limits() - pFreezeXid, pCutoffMultiに代入する値を取得 - VACUUM FREEZEのしきい値となるXIDを計算

- begin_heap_rewrite(NewHeap, OldestXmin, Freezexid, MultiXactCutoff, use_wal);

- for(;;) - 詳細は次のスライド

- heap_endscan() - end_heap_rewrite() - heap_close(OldHeap) - heap_close(NewHeap)

} 49

RewriteState

rs_new_rel 移動先のリレーション

rs_buffer 移動先リレーションの現在のバッファ

rs_use_wal WALに記録するかどうか

: :

Page 50: Inside vacuum - 第一回PostgreSQLプレ勉強会

VACUUM FULLのソースコード(3/4)

copy_heap_data()の処理の一部copy_heap_data(OIDNewHeap, OIDOldHeap, OIDOldIndex, verbose, pSwapToastByContent, pFreezeXid, pCutoffMulti)

{

- もろもろ準備 - for(;;)

- tuple = heap_getnext() - タプルの状態を示すフラグによって処理を変える

- tups_vacuumed +=1 -死んでいるタプルは、

rewrite_heap_dead_tuple() → continue

- num_tuples += 1 - reform_and_rewrite_tuple(tuple, oldTupeDesc, newTupDesc,

values, isnull, NewHeap->rd_rel->relhasoids, rwstate) - heap_endscan() - end_heap_rewrite() - heap_close(OldHeap) - heap_close(NewHeap)

} 50

状態 isdead

HEAPTUPLE_DEAD T

HEAPTUPLE_RECENTLY_DEAD F

HEAPTUPLE_LIVE F

Page 51: Inside vacuum - 第一回PostgreSQLプレ勉強会

VACUUM FULLのソースコード(4/4)

copy_heap_data()の処理の一部reform_and_rewrite_tuple(tuple, oldTupleDesc, newTupleDesc, values, isnull, newRelHadOids, rwstate)

{

- heap_deform_tuple() - tupleのなかの情報をvaluesに展開

- heap_form_tuple() - valuesとisnullを元に新しいタプルを作成する

- rewrite_heap_tuple(rwstate, tuple, copiedTuple)

- rwstate : 移動先の情報が入っている - tuple : 古いタプル(この後、freeされる) - copied : tupleを元に作られた新しいタプル(新しいHeapに入れられる)

- heap_freetuple(copiedTuple) }

51

Page 52: Inside vacuum - 第一回PostgreSQLプレ勉強会

• VACUMM FULLでは元のリレーションから新しいリレーションにタプルが移動される !

!

!

• 最近のhackersでの問題を紹介します

52

Page 53: Inside vacuum - 第一回PostgreSQLプレ勉強会

とある日のpgsql-performanceへのメール(2012/11/29)

!

<VACUUM FULLのあとIndex Only Scanをすると遅くなる>

After vacuum:

Index Only Scan using i on ta (cost=0.00..50882.62 rows=2018667 width=4) (actual time=0.014..193.120 rows=2000000 loops=1) Index Cond: (ca = 1) Heap Fetches: 0 !

After vacuum full:

!

Index Only Scan using i on ta (cost=0.00..155991.44 rows=1990333 width=4) (actual time=0.042..364.412 rows=2000000 loops=1) Index Cond: (ca = 1) Heap Fetches: 2000000

53

Page 54: Inside vacuum - 第一回PostgreSQLプレ勉強会

改善するために(今まで)cluster_rel() { - reform_and_rewrite_heap() - reformする - rewrite_heap_tuple() - raw_heap_insert() - 移動先のページ1に空きがなくなったら、 - 新しいページ2を開く - 新しいページ2にタプルを挿入 : } !

!

54

Page 55: Inside vacuum - 第一回PostgreSQLプレ勉強会

改善するために(現在の案)cluster_rel() { - reform_and_rewrite_heap() - reformする - rewrite_heap_tuple() - raw_heap_insert() - 移動先のページ1に空きがなくなったら、 - ページ1に入っているタプルがすべて可視(Visibule)で

あるかを確認

       - 可視である場合はVMを更新(visibility_map_set())

- 新しいページを開く - 新しいページにタプルを挿入 : } 55

Page 56: Inside vacuum - 第一回PostgreSQLプレ勉強会

レビューしてみましょう

56

Page 57: Inside vacuum - 第一回PostgreSQLプレ勉強会

参考資料

Page 58: Inside vacuum - 第一回PostgreSQLプレ勉強会

その他、VACUUMのあれこれVACUUMについては、他にも様々な点が強化されています。

58

手動VACUUM、autovacuum双方でゆっくりVACUUMを行う設定が可能です。負荷の高い時間帯においてもシステム性能への影響を最小限にできます。

性能

autovacuumは、指定時間以上かかった場合には処理内容とともにログ出力する機能があります。問題発生時の切り分けに有用です。

ログ

各テーブルへのVACUUMの実施時間や回数をシステムビュー経由で確認可能です。問題なく手動 or 自動でのVACUUMがなされているかの監視に有用です。(VACUUM回数の確認は9.2から)

監視

Page 59: Inside vacuum - 第一回PostgreSQLプレ勉強会

vacuum_set_xid_limitsの出力値

59

出力値 説明

oldestXmin実行中の全トランザクション中の一番古いxid。タプルがDEAD or RECENTLY_DEAD か判定する際に使用されるカットオフポイント。

freezeLimitVACUUM中の凍結トランザクションIDに置き換えられるすべてのxidよりも古い値。

xidFullScanLimit

最小のxid。この値よりも古いrelfrozenxidを持つテーブルは、テーブル全体にわたってタプルを凍結するため、テーブル全体のVACUUMが適用されます。この値よりも新しいテーブルのVACUUMは部分的にスキャンすることになります。

multiXactCutoff Xmaxから除去されるすべてのMultiXactIdsよりも古い値。

mxactFullScanLimitテーブル全体のVACUUMを行うため、xidFullScanLimitとともにテーブルのrelminmxidの値と比較される値。