Upload
masahiko-sawada
View
2.380
Download
5
Embed Size (px)
Citation preview
VACUUM解析資料第28回PostgreSQLしくみ+アプリケーション勉強会
2014/02/01 澤田雅彦、江川大地
目次0. はじめに ~プレ勉強会のご紹介 1. VACUUMとは 2. VACUUMの進化 3. VACUUM内部処理の流れ 4. CONCURRENT VACUUMの詳細 5. FULL VACUUMの詳細
2
はじめに ~まずは自己紹介から✦名前 澤田雅彦(twitter : @sawada_masaho)
✦ソースコードの読み方 emacs + etags
✦近況 •chefと格闘しました。 •gitのソースコードを読んでます。 !
✦名前 江川大地(twitter : @daiti0804)
✦ソースコードの読み方 vi + ctags
✦近況 • 転職しました。 • シアトルに行ってきました。 3
はじめに ~プレ勉強会のご紹介
4
プレ勉強会とは、しくみ分科会の前に集まり、PostgreSQLのコアに限定した内容を学んでいこうという趣旨の勉強会です。★勉強会の場
■ 日時:しくみ分科会+アプリケーション分科会の開催同日 ■ 場所:しくみ分科会+アプリケーション分科会と同じ ■ 勉強内容:ソースコードをはじめとするPostgreSQLのコア !
★進め方 1. 主導する人と勉強する内容を決定 2. 主導する人を中心に次回の勉強会の準備
(ソースコードリーディングを例にすると) 2.1. 主導する人がソースコードを読む 2.2. 詰まった部分や進捗状況をMLでシェア。 2.3. MLに参加している人がサポート。 2.4. プレ勉強会で話す資料の作成
3. プレ勉強会での発表
勉強内容の例• PostgreSQLソースコードリーディング
‣ PostgreSQLコアのソースコードを読み、実装方法を共有。
• PostgreSQL機能追加
‣ プレ勉強会で、PostgreSQL本体に機能追加する案を出して、議論する。実装する。
• PostgreSQLパッチレビュー
‣ CommitFestに出されているパッチをレビュー。その機能やレビューないようをプレ勉強会で共有。
• PostgreSQL放置モジュールのアップデート
‣ textsearch-jaなど、PostgreSQLのアップデートに対応していないツールのアップデート。ツールやアップデート内容を共有。
5
ソースコードリーディング案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以降に追加されたデータ型について
:
ソースコードの読み方の一例(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”
•読み方 タグ付けを行ったディレクトリから読む
ソースコードの読み方の一例(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
ソースコードの読み方の一例(doxygen編) •doxygenによるソースコードの読み方とは
doxygenとは、PostgreSQLのソースコードを読めるコンテンツです。メソッドの呼出し元、呼び出し先がリンク付けられたweb上のドキュメントを読み進める流儀。
!
• URL : http://doxygen.postgresql.org/ !
9
1.Vacuumとは
VACUUMとは…の前に簡単にPostgreSQLのアーキテクチャの話を…
11
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;
:不要領域:ページ
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完了
再利用可能に!
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
2.Vacuumの進化
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!ログの改良
autovacuumの改良autovacuumは定期的にテーブル状態を監視し、必要があれば自動でVACUUMやANALYZE
を実施する機能です。8.2までは1プロセスしか割り当てられませんでしたが、8.3以降、複数プロセスを割り当てる(autovacuum_max_workers)ことができるようになりました。
17
VACUUM VACUUM VACUUM
~ 8.2 8.3~ シングルプロセス マルチプロセス
大きなテーブルに長時間かかり、他のテーブルの不要領域がなかなか回収されないことも…
大きなテーブルのVACUUM中も並行して実行できる。
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回数が削減。
Visibility MapVisibility Map(VM)は、テーブルのどの部分に不要領域があるかを記録しているマップファイルです。各テーブル毎に持っています。これにより、VACUUMは、VACUUMすべき箇所を特定して処理を実施します。
VACUUM
VACUUM
~ 8.3 8.4~ テーブル テーブルVM
VACUUMの必要のない部分もスキャン。
VMを見て、必要な箇所のみスキャン。
これにより、VACUUMのスキャン範囲が局所化され、実施時間の短縮やI/O削減が期待できます。特に、ガベージがテーブルの一部分に集中している場合にはその効果が明確に現れます。
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を実施してもブランク領域が増え続けテーブルの肥大化が発生していました。今では必要な分を自動で記録するように改善されています。
3. VACUUM内部処理の流れ
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
VACUUMの概要
23
vacuum()
vacuum_rel()
対象のリレーションをリストアップ
対象のリレーション一つずつに対して以下を繰り返す
VACUUMまたはVACUUM FULLの場合
ANALYZEの場合
analyze_rel()
リレーションにロックの取得
権限のチェック
VACUUM FULLの場合
VACUUMの場合
lazy_vacuum_rel()
cluster_rel()
VACUUM FULL : AccessExclusiveLock VACUUM : SharedUpdateExclusiveLock
4. CONCURRENT VACUUMの詳細
CONCURRENT VACUUM概要✦オプションなしで VACUUM を実行した場合の処理です。 !
!
✦テーブルのページ単位での不要領域の回収とそのインデックスの削除を行います。
!
✦処理中は、テーブルの排他的ロックを取得しないため、通常のデータベース処理と並行して実行することができる。
25
postgres=# VACUUM;
CONCURRENT VACUUMのメカニズム✦CONCURRENT VACUUMは4つの段階を経て実施します。 !
1. 不要領域の列挙 maintenance_work_memに格納可能な分、ヒープページを読み込み、不要領域の一覧を書き出します。
2. VACUUM対象タプルのインデックス・エントリ削除 全インデックスページを読み込み、不要領域へのエントリを除去し、書き出します。
3. ページごとの不要領域除去(lazy_vacuum_page)
VACUUM 対象ページを読み込み、不要領域を除去し、書き出します。
4. テーブル末尾の切り詰め テーブル末尾に有効な行が無い場合、その領域を切り詰め、ファイルを縮小します。
26
lazy_vacuum_rel()
lazy_scan_heap()
lazy_vacuum_heap()
lazy_vacuum_page()
2.インデックス・エントリ削除
3. ページごとの不要領域除去
↓対象のリレーション一つずつに対して呼ばれる
lazy_vacuum_index()
1.不要領域の列挙
4.テーブル末尾の切り詰め
lazy_truncate_heap()
27
ブロックごとにループ
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
テーブルをスキャンして、ページ 単位の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
テーブルをスキャンして、ページ 単位のVACUUM、インデックスのVACUUMを行い、スキャン中にページ のフリースペース情報を構築していくメソッド
lazy_scan_heap()
① lazy_space_alloc()を呼び出し、max_dead_tuplesに、maintenance_work_mem 分の領域を割り当て、CONCURRENT VACUUMで使用する領域を確保する。 ② テーブルの先頭のブロックから順番に、visibilitymap_test()で確認していき、初めて不要領域があるブロックを探す。 ③ (ここからブロックごとのループに入る) ブロックを順番に確認していき、不要領域がないブロックの場合はスキップする。 ④ 不要領域があるブロックの場合、次の不要領域があるブロックを探す。
30
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
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()で不要領域があるかチェックする。
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()で不要領域があるかチェックする。
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()で不要領域があるかチェックする。
ここは不要領域あり
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()で不要領域があるかチェックする。
ここは不要領域あり
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()で不要領域があるかチェックする。
テーブルをスキャンして、ページ 単位の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
5. FULL VACUUMの詳細
VACUUM FULLの概要•ページをまたがった領域の切り詰めを行う •処理はCONCURRENT VACUUMに比べてシンプル •CLUSTERコマンドでの処理と似ている • postgres=# VACUUM FULL; •テーブルを指定することで、指定されたテーブルに対してのみ
VACUUM FULLを行う •テーブルが指定されていない場合は全テーブルが対象
39
vacuum fullのメカニズム•cluster.cの中でCLUSTERコマンドで使用される関数と同じ関数を使用する
•同じ関数の中で、CLUSTERコマンドでの処理との区別は、引数で渡されるindexOidでしている •CLUSTERコマンドの場合は、CLUSTERで使用するIndexのOIDが入っている
•VACUUM FULLではインデックスは使用しないのでInvalidOID(=0)がはいっている
•そのため、ソースを読むときはCLUSTERコマンドでの処理と分けながら読んでいくことが大切
40
41
cluster_rel()
rebuild_relation()
copy_heap_data()
reform_and_rewrite_tuple()
rewrite_heap_tuple()
raw_tuple_insert()
移動先のリレーション作成
タプルを一つずつ読み込んで、以下を繰り返し
古いタプルを元に新しいタプルを作成
新しいタプルを新しいリレーションに挿入
↓対象のリレーション一つずつに対して呼ばれる
DEAD
DEADDEAD
DEAD
図にすると
42
DEAD
DEAD
古いリレーション
DEAD
DEADDEAD
DEAD
図にすると
43
DEAD
DEAD
古いリレーション
新しいリレーション
rebuild_relation() 移動先のリレーションを作成
DEAD
DEADDEAD
DEAD
図にすると
44
DEAD
DEAD
古いリレーション
新しいリレーション
copy_heap_data() タプルを一つに読み込む
DEAD
DEADDEAD
DEAD
図にすると
45
DEAD
DEAD
古いリレーション
新しいリレーション
reform_and_rewrite_tuple() 新しいタプルを作成
DEAD
DEADDEAD
DEAD
図にすると
46
DEAD
DEAD
古いリレーション
新しいリレーション
rewrite_heap_tuple() raw_insert_tuple()
新しいリレーションに挿入
DEAD
DEADDEAD
DEAD
図にすると
47
DEAD
DEAD
古いリレーション
新しいリレーション
これらの処理を生きている タプルに対して行う
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でもこの関数を使用
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に記録するかどうか
: :
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
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
• VACUMM FULLでは元のリレーションから新しいリレーションにタプルが移動される !
!
!
• 最近のhackersでの問題を紹介します
52
とある日の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
改善するために(今まで)cluster_rel() { - reform_and_rewrite_heap() - reformする - rewrite_heap_tuple() - raw_heap_insert() - 移動先のページ1に空きがなくなったら、 - 新しいページ2を開く - 新しいページ2にタプルを挿入 : } !
!
54
改善するために(現在の案)cluster_rel() { - reform_and_rewrite_heap() - reformする - rewrite_heap_tuple() - raw_heap_insert() - 移動先のページ1に空きがなくなったら、 - ページ1に入っているタプルがすべて可視(Visibule)で
あるかを確認
- 可視である場合はVMを更新(visibility_map_set())
- 新しいページを開く - 新しいページにタプルを挿入 : } 55
レビューしてみましょう
56
参考資料
その他、VACUUMのあれこれVACUUMについては、他にも様々な点が強化されています。
58
手動VACUUM、autovacuum双方でゆっくりVACUUMを行う設定が可能です。負荷の高い時間帯においてもシステム性能への影響を最小限にできます。
性能
autovacuumは、指定時間以上かかった場合には処理内容とともにログ出力する機能があります。問題発生時の切り分けに有用です。
ログ
各テーブルへのVACUUMの実施時間や回数をシステムビュー経由で確認可能です。問題なく手動 or 自動でのVACUUMがなされているかの監視に有用です。(VACUUM回数の確認は9.2から)
監視
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の値と比較される値。