PostgreSQLスキルアップノート(自己啓発のための個人サイト)

HOT(Heap Only Tuple)を実機で確認


【一覧に戻る】

このページはそのままカット&ペーストで実行して試すことができます。
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
■■■■ PostgreSQL スキルアップノート
■■■■
■◆■■ HOT(Heap Only Tuple)を実機で確認
■■■■
■■■■
■■■■ 2012/12/11
■■■■ 使用環境:PostgreSQL9.1.4 (CentOS6.2)
                                                                   (C) 2012 ohdb
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
★自己都合により実機確認は9.1、マニュアルへのリンクは9.2としています。ご了承下さい。

マニュアルには詳しい記述が見つからない(?)のでリンクはありません。
下記Let's Postgresの記事を参照。

【マニュアル】

なし

【参考記事】
Let's Postgres→●[記事]




■1■ 確認の都合上、autovacuumを一旦停止
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

更新後すぐにautovacuum(※)されると確認に支障がでるため以下により一時的に停止。
※ postgresql.confのautovacuum_naptimeにもよる。(デフォルトなら1分)


【□】 pg_ctl restart -o '-c autovacuum=off' 



【参考】以下本題とは無関係

 ・pg_ctlの-oオプションとは?

    起動時の一時的なパラメータ変更ができる。
    postgresql.confはそのままにしておけるので主に試行的な用途に使用。
    今回はautovacuumパラメータをoffにして起動(restart)している。

    →●[マニュアル]

    なお、複数指定する場合は以下のようにする。
    pg_ctl restart -o '-c autovacuum=off -c archive_mode=off' 


 ・「-o autovacuum=off」ではなく「-o '-c autovacuum=off'」となるのはなぜ?   

    pg_ctlの-oオプションは、postgresコマンド(等)に渡すオプションをそのまま記述するためのもの。
    続けて指定している-c(シングルクォートの中)はpostgresコマンドの-cオプションを意味する。


■2■ テーブルの作成
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

以下の4テーブルを作成。

テーブル名  FILLFACTOR       備考
--------------------------------------------------------
t1          FILLFACTORなし   HOTは非適用になるはず
t2          FILLFACTOR=90    ある程度の効果?
t3          FILLFACTOR=75    かなりの効果?
t4          FILLFACTOR=50    十分に効く? でも格納効率上は問題


【□】 psql -d test1 -U test1

【□】 以下のcreate文を実行。

-- テーブルt1・FILLFACTORなし
drop table if exists t1;
create table t1 (c1 serial primary key,idxcol varchar(20),noidxcol varchar(20));
create index t1_idx1 on t1(idxcol);

-- テーブルt2・FILLFACTOR 90%
drop table if exists t2;
create table t2 (c1 serial primary key,idxcol varchar(20),noidxcol varchar(20))  WITH (FILLFACTOR=90);
create index t2_idx1 on t2(idxcol);

-- テーブルt3・FILLFACTOR 75%
drop table if exists t3;
create table t3 (c1 serial primary key,idxcol varchar(20),noidxcol varchar(20))  WITH (FILLFACTOR=75);
create index t3_idx1 on t3(idxcol);

-- テーブルt4・FILLFACTOR 50%
drop table if exists t4;
create table t4 (c1 serial primary key,idxcol varchar(20),noidxcol varchar(20))  WITH (FILLFACTOR=50);
create index t4_idx1 on t4(idxcol);


【□】 FILLFACTORを確認

   FILLFACTORの設定内容はpg_classのreloptionsで確認できる。

select 
    relowner,
    (select rolname from pg_roles r where r.oid=c.relowner) as ownername,
    (select nspname from pg_namespace n where n.oid=c.relnamespace) as namespace,
    relkind,
    relname,
    reloptions
  from pg_class c 
  where
    relkind='r' and    relname ~'^t[1-4]$'
    order by relname
  ;



 relowner | ownername | namespace | relkind | relname |   reloptions★
----------+-----------+-----------+---------+---------+-----------------
   272004 | test1     | public    | r       | t1      |
   272004 | test1     | public    | r       | t2      | {fillfactor=90}
   272004 | test1     | public    | r       | t3      | {fillfactor=75}
   272004 | test1     | public    | r       | t4      | {fillfactor=50}

【参考】reloptionsに表示されない場合

 指定なしでデフォルトで作成されたものはreloptionsには値がない。
 この場合
    テーブルであれば           100
    インデックスであれば        90    (Btreeの場合)
 を意味する。



■3■ データ格納
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

全カラムともc1のserial値と同じ値を入れておく。


【□】 以下によりテーブルのデータ格納用ファンクションを作成

-- ここから --------
CREATE OR REPLACE FUNCTION f_t1(kensu integer,tbl varchar) returns integer
AS $$
  DECLARE
   inscount integer;
   i integer;
  BEGIN
    FOR i IN 1..kensu LOOP
     execute 'insert into ' || tbl || ' values(default,cast(currval(''t1_c1_seq'') as varchar),cast(currval(''t1_c1_seq'') as varchar))';
     inscount=i;
    END LOOP;
    return inscount;
  END;
$$ LANGUAGE plpgsql
;
-- ここまで --------


以上で作成したファンクションf_t1を使い、4テーブルに同じデータを挿入してみる。


【□】 以下により各10万件のデータのinsert

-- ここから --------
\timing on
select f_t1(100000,'t1');
select f_t1(100000,'t2');
select f_t1(100000,'t3');
select f_t1(100000,'t4');
-- ここまで --------





■4■ 検証1 とりあえずHOTを発生をさせてみる
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

FILLFACTORの違うそれぞれのテーブルに対して実施


【□】 データのUPDATE

update t1 set noidxcol='9999';     /* t1 FILLFACTORなし */
update t2 set noidxcol='9999';     /* t2 FILLFACTORあり90 */
update t3 set noidxcol='9999';     /* t3 FILLFACTORあり75 */
update t4 set noidxcol='9999';     /* t4 FILLFACTORあり50 */



【参考】UPDATEにかかった処理時間 

t1 Time: 1081.013 ms
t2 Time: 954.607 ms
t3 Time: 979.461 ms
t4 Time: 308.316 ms


【□】 状態確認

select relname, n_tup_ins,n_tup_upd,n_tup_del,n_tup_hot_upd,n_live_tup,n_dead_tup,last_vacuum,last_autovacuum,last_analyze,last_autoanalyze
from pg_stat_user_tables where relname in('t1','t2','t3','t4') order by relname;

 relname | n_tup_ins | n_tup_upd | n_tup_del |★n_tup_hot_upd| n_live_tup |★n_dead_tup| last_vacuum | last_autovacuum | last_analyze | last_autoanalyze
---------+-----------+-----------+-----------+---------------+------------+------------+-------------+-----------------+--------------+------------------
 t1      |    100000 |    100000 |         0 |             0 |     100000 |     100000 |             |                 |              |
 t2      |    100000 |    100000 |         0 |         13490 |     100000 |     100000 |             |                 |              |
 t3      |    100000 |    100000 |         0 |         40184 |     100000 |     100000 |             |                 |              |
 t4      |    100000 |    100000 |         0 |        100000 |     100000 |     100000 |             |                 |              |


【上記からわかること】

n_tup_hot_updの値より

・テーブルt1はHOTが効いていない。(FILLFACTORが100だから同じページ内には書けなかった)
・テーブルt2〜t4はHOTが効いた
  ただし、HOTが適用できた度合いがテーブルによってかなり違う。【重要】
  FILLFACTORが90と高いテーブルt2は、わずか13490行に留まっている。

n_dead_tupの値より

・全テーブルともに10万行の不要な行が発生した。



autovacuumは止めてあるので、このままの状態で次の検証へ・・



■5■ 次の検証へのメモ
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

・現在autovacuumは停止中なので発生した不要領域はそのままになっている。

・autovacuumを再開させれば当然不要領域は回収される。
 しかし、vacuumしなくても解消されるはずのものがこの中に含まれている。

 ・・・それは、n_dead_tupのうちHOTが効いた分(n_tup_hot_upd)

 つまり
            回収予定    非回収予定
   ------------------------------------------------
    t1              0 | 100000行
    t2          13490 |  86510行    (10万-13490)
    t3          40184 |  59816行    (10万-40184)
    t4         100000 |      0行    


  SELECTしただけで回収されていくはず。
  本当にそうなるのか、次項の検証で確認する。



■6■ 検証2 vacuumなしで不要領域が回収されることの検証
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

vacuumなしで不要領域が回収される状況を確認する。
以下のようにselectすることで不要領域の回収が行われるはず。


【□】 それぞれ以下のように参照してみる。

     select * from t1 where c1<=1000;
     select * from t2 where c1<=1000;
     select * from t3 where c1<=1000;
     select * from t4 where c1<=1000;


【□】 状態確認

select relname, n_tup_ins,n_tup_upd,n_tup_del,n_tup_hot_upd,n_live_tup,n_dead_tup,last_vacuum,last_autovacuum,last_analyze,last_autoanalyze
from pg_stat_user_tables where relname in('t1','t2','t3','t4') order by 1;

 relname | n_tup_ins | n_tup_upd | n_tup_del | n_tup_hot_upd | n_live_tup |★n_dead_tup| last_vacuum | last_autovacuum | last_analyze | last_autoanalyze
---------+-----------+-----------+-----------+---------------+------------+------------+-------------+-----------------+--------------+------------------
 t1      |    100000 |    100000 |         0 |             0 |     100000 |     100000 |             |                 |              |
 t2      |    100000 |    100000 |         0 |         13490 |     100000 |★    86510 |             |                 |              |
 t3      |    100000 |    100000 |         0 |         40184 |     100000 |★    59816 |             |                 |              |
 t4      |    100000 |    100000 |         0 |        100000 |     100000 |★        4 |             |                 |              |


【上記からわかること】

★1000行のselectによって確かにn_dead_tupの値が減っている・・

・t1(FILLFACTORなし)はHOTupdateがないので、回収はされていない。
・t2(FILLFACTOR90)はHOTupdateされた13490行すべてが回収。
・t3(FILLFACTOR75)はHOTupdateされた40184行すべてが回収。
・t4(FILLFACTOR50)はHOTupdateされた100000行の大半(4行を除いて)がすべて回収。   

以上、HOTupdateにより発生した不要領域はvacuumしなくても大半が回収された。(ほぼ■5■項のとおり)



■7■ 実装にあたって考慮すべきこと(FILLFACTOR)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

大量に更新する場合、HOTの効果はFILLFACTORの設定に大きく左右される。

今回のように全件UPDATEするようなケースの場合、FILLFACTORが90程度ではHOTは部分的にしか効かなかった。

インデックスカラムの更新があろうがなかろうが、結局は「postgresは追記型」であることに変わりがない。
このため、たとえわずかな(1バイトの)UPDATEでも1行追記するだけの空きが同じページ内に必要。
繰り返せば90程度のFILLFACTOR値ではすぐにページに入らなくなる。


★疑問
今回はUPDATE1回で10万行の大量更新をさせたが、もし1行つづUPDATE(10万回UPDATE)したらどうなるのだろう?
次の処理に回収をまかせている仕組み上、上とは異なる結果になる(はず)
・・・次の機会に確認



【参考】インデックスのFILLFACTOR

インデックスはデフォルトでFILLFACTOR 90が設定されている。
(ただしBtreeの場合でインデックスの種類により異なる)
→●[マニュアル]

マニュアルより
・ページ内いっぱいになるとページ分割
・テーブルの変化がほとんどない場合は100が最善
・更新が非常に多い場合は小さな値を設定しておく必要がある(ページ分割が少なくなる)

ただし、Let's Postgresの記事にはインデックスのFILLFACTORはあまり考慮する必要がないと記載あり。
「・・・インデックスはデフォルトで・・・90となっています。こちらは、特に変更する必要はないでしょう。」




■8■ 検証4 INDEXカラムの更新ではHOTが効かないことの検証
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

・autovacuumはされていない。
・前の検証から引き続き実施。 各値は前の検証時の状態からスタート

状態確認

select relname, n_tup_ins,n_tup_upd,n_tup_del,n_tup_hot_upd,n_live_tup,n_dead_tup,last_vacuum,last_autovacuum,last_analyze,last_autoanalyze
from pg_stat_user_tables where relname in('t1','t2','t3','t4') order by 1;



 relname | n_tup_ins | n_tup_upd | n_tup_del | n_tup_hot_upd | n_live_tup | n_dead_tup | last_vacuum | last_autovacuum | last_analyze | last_autoanalyze
---------+-----------+-----------+-----------+---------------+------------+------------+-------------+-----------------+--------------+------------------
 t1      |    100000 |    100000 |         0 |             0 |     100000 |     100000 |             |                 |              |
 t2      |    100000 |    100000 |         0 |         13490 |     100000 |      86510 |             |                 |              |
 t3      |    100000 |    100000 |         0 |         40184 |     100000 |      59816 |             |                 |              |
 t4      |    100000 |    100000 |         0 |        100000 |     100000 |          4 |             |                 |              |



インデックスカラムの更新

update t1 set idxcol='9999';     /* FILLFACTORなしテーブル */
update t2 set idxcol='9999';     /* FILLFACTORあり90 テーブル*/
update t3 set idxcol='9999';     /* FILLFACTORあり75 テーブル*/
update t4 set idxcol='9999';     /* FILLFACTORあり50 テーブル*/



状態確認

select relname, n_tup_ins,n_tup_upd,n_tup_del,n_tup_hot_upd,n_live_tup,n_dead_tup,last_vacuum,last_autovacuum,last_analyze,last_autoanalyze
from pg_stat_user_tables where relname in('t1','t2','t3','t4') order by 1;



 relname | n_tup_ins | n_tup_upd | n_tup_del |★n_tup_hot_upd| n_live_tup | n_dead_tup | last_vacuum | last_autovacuum | last_analyze | last_autoanalyze
---------+-----------+-----------+-----------+---------------+------------+------------+-------------+-----------------+--------------+------------------
 t1      |    100000 |    200000 |         0 |             0 |     100000 |     200000 |             |                 |              |
 t2      |    100000 |    200000 |         0 |         13490 |     100000 |     186510 |             |                 |              |
 t3      |    100000 |    200000 |         0 |         40184 |     100000 |     159816 |             |                 |              |
 t4      |    100000 |    200000 |         0 |        100000 |     100000 |     100004 |             |                 |              |


【上記からわかること】

 n_tup_hot_updの値は前のまま。
 今回のupdateではHOTは発生しなかった。


■9■ 検証5 HOTupdateではインデックスは書き換えが発生していないことの確認
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

今回見送り



■10■ 最後にvacuum実行
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

vacuumdbコマンドの場合の例


【□】 \! vacuumdb -a -z    

  ※ -aはクラスタ内の全DB対象。 -zはアナライズも実施


状態確認

select relname, n_tup_ins,n_tup_upd,n_tup_del,n_tup_hot_upd,n_live_tup,n_dead_tup,last_vacuum,last_autovacuum,last_analyze,last_autoanalyze
from pg_stat_user_tables where relname in('t1','t2','t3','t4') order by 1;

 relname | n_tup_ins | n_tup_upd | n_tup_del | n_tup_hot_upd | n_live_tup | n_dead_tup |          last_vacuum          | last_autovacuum |         last_analyze          | last_autoanalyze
---------+-----------+-----------+-----------+---------------+------------+------------+-------------------------------+-----------------+-------------------------------+------------------
 t1      |    100000 |    200000 |         0 |             0 |     100000 |          0 | 2012-12-17 02:04:20.605032+09 |                 | 2012-12-17 02:04:20.719179+09 |
 t2      |    100000 |    200000 |         0 |         13490 |     100000 |          0 | 2012-12-17 02:04:21.450947+09 |                 | 2012-12-17 02:04:21.606505+09 |
 t3      |    100000 |    200000 |         0 |         40184 |     100000 |          0 | 2012-12-17 02:04:21.900873+09 |                 | 2012-12-17 02:04:22.046578+09 |
 t4      |    100000 |    200000 |         0 |        100000 |     100000 |          0 | 2012-12-17 02:04:22.216231+09 |                 | 2012-12-17 02:04:22.390233+09 |


last_vacuumとlast_analyzeに実施時刻が入り、vacuum/analyzeが実行されたことを確認。


【補足】pg_stat_user_tablesのvacuum日時のカラムは2つ

last_vacuum と last_autovacuum

現在autovacuumを一時的に停止しているので、上記のようになるが、
通常であれば自動で実行され、その状況はlast_vacuumカラムでなく、
last_autovacuumカラムに値が入る。

analyzeについても同様でlast_analyze,last_autoanalyzeの2カラムがある。



■11■ 後始末(vacuumデーモンを再開)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━


【□】 pg_ctl restart 


以上


■参考■
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━


\d pg_stat_user_tables

test1=# \d pg_stat_user_tables
          View "pg_catalog.pg_stat_user_tables"
      Column       |           Type           | Modifiers
-------------------+--------------------------+-----------
 relid             | oid                      |
 schemaname        | name                     |
 relname           | name                     |
 seq_scan          | bigint                   |
 seq_tup_read      | bigint                   |
 idx_scan          | bigint                   |
 idx_tup_fetch     | bigint                   |
 n_tup_ins         | bigint                   |
 n_tup_upd         | bigint                   |
 n_tup_del         | bigint                   |
 n_tup_hot_upd     | bigint                   |
 n_live_tup        | bigint                   |
 n_dead_tup        | bigint                   |
 last_vacuum       | timestamp with time zone |
 last_autovacuum   | timestamp with time zone |
 last_analyze      | timestamp with time zone |
 last_autoanalyze  | timestamp with time zone |
 vacuum_count      | bigint                   |
 autovacuum_count  | bigint                   |
 analyze_count     | bigint                   |
 autoanalyze_count | bigint                   |



\d pg_stat_user_indexes

View "pg_catalog.pg_stat_user_indexes"
    Column     |  Type  | Modifiers
---------------+--------+-----------
 relid         | oid    |
 indexrelid    | oid    |
 schemaname    | name   |
 relname       | name   |
 indexrelname  | name   |
 idx_scan      | bigint |
 idx_tup_read  | bigint |
 idx_tup_fetch | bigint |


test1=# \d pg_class
      Table "pg_catalog.pg_class"
     Column     |   Type    | Modifiers
----------------+-----------+-----------
 relname        | name      | not null
 relnamespace   | oid       | not null
 reltype        | oid       | not null
 reloftype      | oid       | not null
 relowner       | oid       | not null
 relam          | oid       | not null
 relfilenode    | oid       | not null
 reltablespace  | oid       | not null
 relpages       | integer   | not null
 reltuples      | real      | not null
 reltoastrelid  | oid       | not null
 reltoastidxid  | oid       | not null
 relhasindex    | boolean   | not null
 relisshared    | boolean   | not null
 relpersistence | "char"    | not null
 relkind        | "char"    | not null
 relnatts       | smallint  | not null
 relchecks      | smallint  | not null
 relhasoids     | boolean   | not null
 relhaspkey     | boolean   | not null
 relhasrules    | boolean   | not null
 relhastriggers | boolean   | not null
 relhassubclass | boolean   | not null
 relfrozenxid   | xid       | not null
 relacl         | aclitem[] |
 reloptions     | text[]    |
Indexes:
    "pg_class_oid_index" UNIQUE, btree (oid)
    "pg_class_relname_nsp_index" UNIQUE, btree (relname, relnamespace)


est1=# \d pg_roles
              View "pg_catalog.pg_roles"
     Column     |           Type           | Modifiers
----------------+--------------------------+-----------
 rolname        | name                     |
 rolsuper       | boolean                  |
 rolinherit     | boolean                  |
 rolcreaterole  | boolean                  |
 rolcreatedb    | boolean                  |
 rolcatupdate   | boolean                  |
 rolcanlogin    | boolean                  |
 rolreplication | boolean                  |
 rolconnlimit   | integer                  |
 rolpassword    | text                     |
 rolvaliduntil  | timestamp with time zone |
 rolconfig      | text[]                   |
 oid            | oid                      |





以上 
inserted by FC2 system