| 1 | [[wiki:WikiStart 戻る]] |
| 2 | |
| 3 | = PostgreSQLの設定ファイル = |
| 4 | |
| 5 | = postgresql.confの設定の勘所 = |
| 6 | |
| 7 | 7.1 以降の PostgreSQL には、設定ファイルとして postgresql.conf があります。 |
| 8 | このファイルはデータベースのファイルがある場所(通常 /usr/local/pgsql/data/ )以下に存在します。 |
| 9 | このファイルでは、以下のような設定を行うことができます。 |
| 10 | |
| 11 | {{{ |
| 12 | * 接続に関する設定 |
| 13 | * メモリの使用に関する設定 |
| 14 | * オプティマイザに関する設定 |
| 15 | * ログに関する設定 |
| 16 | * WALに関する設定 |
| 17 | * その他 |
| 18 | }}} |
| 19 | |
| 20 | 本節では、このうちのパフォーマンスに関連するいくつかの設定に関する説明を行います。[[BR]] |
| 21 | |
| 22 | == ●活動状況の監視(stats_*) == |
| 23 | |
| 24 | どのようなクエリが要求され、それに対して PostgreSQL がどのように処理したか、その統計情報を得ることができます。これを知ることによって、より良いパフォーマンスを得るためのヒントとすることができます。 |
| 25 | デフォルトの設定ではこの統計情報の収集は行われません。これは、統計のための情報の保存自体がパフォーマンス的なオーバーヘッドになるためです。 |
| 26 | postgresql.conf の設定を変更することによって、統計情報を保存させることができます。 |
| 27 | |
| 28 | ||・stats_start_collector(デフォルト:true)||統計情報を保存する場合は true, しない場合は false です。この設定が true の場合、stats_command_string, stats_row_level, stats_block_level のうちで true が指定されているものに関して、情報を保存します(デフォルトでこれらは全て false です)。|| |
| 29 | ||・stats_reset_on_server_start(デフォルト:true)||サーバを再起動するたびに、統計情報をリセットするかどうかを指定します。true の場合、リセットされます。|| |
| 30 | ||・stats_command_string(デフォルト:false)||true にすると、その瞬間に実行している SQL を保存します。|| |
| 31 | ||・stats_row_level(デフォルト:false)||行レベルの情報を保存します。|| |
| 32 | ||・stats_block_level(デフォルト:false)||ブロックレベルの情報を保存します。|| |
| 33 | |
| 34 | == ●収集情報の取り出し == |
| 35 | |
| 36 | 収集・保存された情報は、標準で提供されるいくつかのビューと関数で参照することができます。 |
| 37 | 代表的なビュー(ドキュメントから抜粋) |
| 38 | |
| 39 | || 番号 || 変更コスト || |
| 40 | ||pg_stat_activity || サーバプロセス当たり 1 行の形で、プロセスの PID、データベース、ユーザ、現在の問い合わせを表示します。現在の問い合わせの列はスーパユーザのみアクセスできます。他のユーザからは NULL として読み出されます。(収集器による報告の遅れのため、現在の問い合わせは長時間実行中の問い合わせにおいてのみ現在のものを表します。) || |
| 41 | || pg_stat_database || データベース当たり 1 行の形で、そのデータベースに対して、活動中のバックエンド数、コミットされたトランザクションの総数、ロールバックされたトランザクションの総数、読みとられたディスクブロックの総数、バッファヒット(つまり、バッファキャッシュに対象とするブロックが存在するために防止されたブロック読みとり要求)の総数を表示します。|| |
| 42 | ||pg_stat_all_tables||現在のデータベース内の各テーブルに関する、シーケンシャルスキャン、インデックススキャンの総数、各種スキャンによって返されたタプルの総数、挿入、更新、削除されたタプルの総数。|| |
| 43 | ||pg_stat_all_indexes ||現在のデータベース内の各インデックスに関する、そのインデックスを使用したインデックススキャン数、読みとられたインデックスタプル、正常に抽出されたヒープタプル数(この値は、古く不要となったヒープタプルを示すインデックスエントリがある時に小さくなります)。|| |
| 44 | ||pg_statio_all_tables||現在のデータベース内の各テーブルに関する、そのテーブルから読みとられたディスクブロックの総数、バッファヒット数、そのテーブルに関する全てのインデックスに関するディスクブロック数とバッファヒット数、(存在する場合)そのテーブルの補助的な TOAST テーブルから読みとられたディスクブロック数とバッファヒット数、TOAST テーブルのインデックスに関するディスクブロック数とバッファヒット数。|| |
| 45 | ||pg_statio_all_indexes ||現在のデータベース内の各インデックスに関する、そのインデックスの読みとられたディスクブロック数とバッファヒット数。|| |
| 46 | |
| 47 | ※allの部分をsysにすればシステムカタログのみに関する統計情報、userにすればユーザ定義のテーブルのみに関する統計情報を得ることができます。 |
| 48 | |
| 49 | 例えば、次項で説明するキャッシュが効果的に働いているかどうかを知るためにディスクの入出力処理に関する情報を得たい場合には、 |
| 50 | {{{ |
| 51 | stats_start_collector = true |
| 52 | stats_block_level = true |
| 53 | }}} |
| 54 | |
| 55 | のように設定して PostgreSQL を再起動した後、次のような SQL を実行します。 |
| 56 | |
| 57 | SELECT relname AS テーブル名, |
| 58 | heap_blks_read AS ディスクからの読込, |
| 59 | heap_blks_hit AS キャッシュからの読込 |
| 60 | FROM pg_statio_user_tables ; |
| 61 | |
| 62 | |
| 63 | 結果は例えば以下のようになります。 |
| 64 | |
| 65 | {{{ |
| 66 | テーブル名 | ディスクからの読込 | キャッシュからの読込 |
| 67 | ------------+--------------------+---------------------- |
| 68 | hoge | 8 | 110 |
| 69 | foo | 12 | 1410 |
| 70 | }}} |
| 71 | |
| 72 | ディスクからの読み込みに対して、キャッシュから読み込んでいる回数が圧倒的に多いので、この場合はキャッシュが十分有効に働いているということを知ることができます。 |
| 73 | |
| 74 | 代表的な関数(ドキュメントから抜粋) |
| 75 | || 関数名(引数の型) || 戻り値の型 ||説明|| |
| 76 | || pg_stat_get_db_numbackends(oid) || integer ||データベース内で活動中のバックエンド数。|| |
| 77 | || pg_stat_get_db_xact_commit(oid) || bigint ||データベース内でコミットされたトランザクション。|| |
| 78 | || pg_stat_get_db_blocks_fetched(oid) || bigint ||データベースに関する、ディスクブロック抽出要求数。|| |
| 79 | || pg_stat_get_db_blocks_hit(oid) || bigint ||データベースに関する、ディスクブロック要求の内キャッシュに存在した数。|| |
| 80 | || pg_stat_get_numscans(oid) || bigint ||引数がテーブルの場合、シーケンシャルスキャンの実行回数。引数がインデックスの場合インデックススキャンの実行回数。|| |
| 81 | || pg_stat_get_tuples_returned(oid) || bigint|| 引数がテーブルの場合、シーケンシャルスキャンによって読みとられたタプル数。引数がインデックスの場合、読みとられたインデックスタプル数。|| |
| 82 | || pg_stat_get_tuples_inserted(oid) || bigint || テーブルに挿入されたタプル数。|| |
| 83 | || pg_stat_get_blocks_fetched(oid) || bigint|| テーブル、または、インデックスに関する、ディスクブロック抽出要求数。|| |
| 84 | || pg_stat_get_blocks_hit(oid) || bigint ||テーブル、または、インデックスに関する、ディスクブロック抽出要求の内キャッシュに存在した数。|| |
| 85 | || pg_stat_get_backend_idset() ||set of integer||現在活動中のバックエンド ID を(1からNまでで、ただし、Nは活動中バックエンド数)設定します。|| |
| 86 | || pg_stat_get_backend_pid(integer) ||integer ||バックエンドプロセスの PID。|| |
| 87 | || pg_stat_get_backend_dbid(integer) ||oid ||バックエンドプロセスのデータベース ID。|| |
| 88 | || pg_stat_get_backend_activity(integer) ||text ||バックエンドプロセスの現在の問い合わせ。(呼び出し実行者がスーパユーザではない場合は NULL。)|| |
| 89 | |
| 90 | |
| 91 | 例えば、各バックエンドプロセスが現在実行しているクエリに関する情報を得たい場合には、 |
| 92 | {{{ |
| 93 | stats_start_collector = true |
| 94 | stats_command_string = true |
| 95 | }}} |
| 96 | |
| 97 | のように設定して PostgreSQL を再起動した後、次のような SQL を実行します。 |
| 98 | {{{ |
| 99 | SELECT pg_stat_get_backend_pid(S.backendid) AS プロセスID, |
| 100 | pg_stat_get_backend_activity(S.backendid) AS 現在のクエリ |
| 101 | FROM (SELECT pg_stat_get_backend_idset() AS backendid) AS S; |
| 102 | FROM pg_statio_user_tables ; |
| 103 | }}} |
| 104 | |
| 105 | 結果は例えば以下のようになります。 |
| 106 | |
| 107 | {{{ |
| 108 | プロセスid | 現在のクエリ |
| 109 | ------------+------------------------------------------ |
| 110 | 2885 | SELECT * FROM "foo" |
| 111 | 2889 | |
| 112 | }}} |
| 113 | |
| 114 | == ●共有バッファ(shared_buffers) == |
| 115 | |
| 116 | 共有バッファは、テーブルのデータをキャッシュするために使用されます。キャッシュは、複数のバックエンドプロセスの間で共有する必要があるため、共有メモリ(shared memory)という、特別なメモリ領域に保持されます。設定shared_buffersでは、この共有メモリのサイズを設定することができます。 |
| 117 | |
| 118 | 共有バッファ上にテーブルのデータがキャッシュされると、次回同じデータを必要とする参照系クエリが発行された場合に、ディスクからデータを読むのではなく、共有メモリ上のデータを参照するようになります。共有メモリからのデータ読み出し速度はディスクと比較して非常に高速です。つまり、より高速に処理を完了させることができるのです。 |
| 119 | |
| 120 | 共有バッファのサイズを大きくしておくと、必要なデータが共有バッファ上に存在する確率が高くなります。つまり、システム全体として、速度改善の可能性が向上することになります。 |
| 121 | |
| 122 | 共有バッファのサイズは可能な限り大きくとるべきです。しかし、物理メモリの量は無限ではありません。そこで、どの程度の領域を実際に確保するかを検討することになります。「他のソフトウェアのメモリ使用に影響を及ぼさない最大値」というのが理想的なサイズです。つまり、スワップが発生しない状態を維持できる最大サイズです。このサイズがどの程度かはケースバイケースですが、空きメモリのサイズを一定期間監視すれば、どの程度のメモリの割り当てが可能か推測することができます。 |
| 123 | |
| 124 | 逆のアプローチとして、最適なパフォーマンスを得るための必要最小限のメモリ量から割り当てるメモリの量を考えるという方法もあります。最適なパフォーマンスを得るためには、「必要なテーブルが全て収まるサイズ」のメモリ量が必要です。前項「活動状況の監視(stats_*)」を行うことによって、現状の設定で足りているかどうかを知ることができます。 |
| 125 | |
| 126 | なお、設定値に 8192 Byte を掛けた数が確保されるメモリ量の概算になります(実際にはもう少し多くなります)。また、max_connectionsの値を2倍した値以上の数値を設定しなければなりません。 |
| 127 | OSの設定として、OS全体で確保できる共有メモリサイズには上限があります。共有メモリはPostgreSQL以外のソフトウェアも使用するので、システム全体の共有メモリ使用量が上限を超えないようにしなければなりません。現在の共有メモリ使用状況を確認するためには、次のコマンドを実行します。 |
| 128 | |
| 129 | {{{ |
| 130 | ipcs -m |
| 131 | }}} |
| 132 | |
| 133 | 共有メモリの上限を変更することもできますが、OS毎に設定方法は異なります。例えばLinuxでは、/proc/sys/kernel/shmmax というファイルで最大サイズを管理しています。このファイルの内容を次のようにして書き換えれば、共有メモリ最大サイズを133554432バイトに変更できます。 |
| 134 | |
| 135 | {{{ |
| 136 | echo 133554432 > /proc/sys/kernel/shmmax |
| 137 | }}} |
| 138 | |
| 139 | == ●ソートメモリ(sort_mem) == |
| 140 | |
| 141 | ソートを行うための作業領域のサイズを設定します。単位は KByte です。ソート作業で必要な領域がここでの設定値を超えると、一時ファイルへの書き出しが行われ、パフォーマンスに悪影響を及ぼします。 |
| 142 | ソートメモリは下記の処理中に使用されます。 |
| 143 | {{{ |
| 144 | * ORDER BY |
| 145 | * Merge Join |
| 146 | * CREATE INDEX |
| 147 | }}} |
| 148 | ソートメモリは、共有メモリではなく、通常のメモリ領域に確保されます。また、各 postgres プロセス毎に保持され、一つのソートに対して一つの領域が割り当てられます。従って、ここでの設定値の数倍のメモリが使われることになりますので、大きすぎる値を設定することはスワップの発生を誘発し、パフォーマンスの低下を引き起こします。 |
| 149 | |
| 150 | = 5.検証例 = |
| 151 | |
| 152 | 実際にどのようなツールを使って、どのような手順で検証を行えばよいか、簡略式ではありますが具体的な事例をご紹介します。 |
| 153 | ●検証条件 |
| 154 | |
| 155 | shared_buffersがどのように影響するかを簡単なテーブルで検証します。実際には、皆さんのアプリケーションのテーブルに適当なデータを入れ、実際に発行されるSQLで検証を行ってください。 |
| 156 | ここでの検証項目は |
| 157 | |
| 158 | {{{ |
| 159 | * 10000件の全件検索 |
| 160 | * 10000件のテーブルと100000件のテーブルを結合し、条件を指定して検索 |
| 161 | }}} |
| 162 | |
| 163 | とします。インデックスは結合項目に対してのみ張っています。また、vacuumとanalyzeも実行済みです。 |
| 164 | |
| 165 | 同時に処理の要求が来た場合の動作を検証できるように、Apache付属のベンチマークツール「ab」を使用します。また、abによるHTTPリクエストをトリガーとしてSQLを実行するため、PHPを利用します。検証は次のコマンドの結果で行います。 |
| 166 | {{{ |
| 167 | ab -n 10 -c 3 http://localhost/sqltest.php |
| 168 | }}} |
| 169 | |
| 170 | |
| 171 | キャッシュの利用具合を確認するため、postgresql.confの設定を変更しておきます。 |
| 172 | {{{ |
| 173 | stats_start_collector = true |
| 174 | stats_reset_on_server_start = true |
| 175 | stats_block_level = true |
| 176 | }}} |
| 177 | |
| 178 | テーブルは以下のように作成しました。 |
| 179 | {{{ |
| 180 | create table test1(id integer, data1 text, data2 text); |
| 181 | create table test2(id integer, id2 integer, data1 text, data2 text); |
| 182 | create index test1_idx on test1 (id); |
| 183 | create index test2_idx on test2 (id); |
| 184 | }}} |
| 185 | |
| 186 | |
| 187 | データは、次のスクリプトで作成しました。 |
| 188 | {{{ |
| 189 | #!/usr/bin/perl |
| 190 | $test1_rows = 1000; $test2_rows = 100000; |
| 191 | $data2 = "*" x 16; $data3 = "*" x 10000; |
| 192 | |
| 193 | print "begin;\n"; |
| 194 | for $i (1..$test1_rows) { |
| 195 | print "insert into test1 values($i, '$data2', '$data3');\n"; |
| 196 | } |
| 197 | print "commit;\n"; |
| 198 | |
| 199 | print "begin;\n"; |
| 200 | for $i (1..$test2_rows) { |
| 201 | print "insert into test2 values($i, ".($i%3).", '$data2', '$data3');\n"; |
| 202 | } |
| 203 | print "commit;\n"; |
| 204 | }}} |
| 205 | |
| 206 | |
| 207 | また、データベースへのクエリを発行するPHPプログラムは以下になります |
| 208 | {{{ |
| 209 | <? |
| 210 | $con = pg_connect("user=postgres host=localhost dbname=testdb"); |
| 211 | $sql = "select * from test1"; |
| 212 | // $sql = "select * from test1 join test2 using(id) where id=100"; |
| 213 | // $sql = "select * from test1 join test2 using(id) where id<5000 and id2 % 3 = 1"; |
| 214 | |
| 215 | $result = pg_exec($con, $sql); |
| 216 | // print pg_result($result, 0, 2); |
| 217 | |
| 218 | ?> |
| 219 | done. |
| 220 | }}} |
| 221 | |
| 222 | |
| 223 | = ●検証1:SQL実行なし = |
| 224 | |
| 225 | まず、SQLを実行を除いた部分のパフォーマンスを調べておきます。結果は次のようになりました(抜粋)。 |
| 226 | {{{ |
| 227 | Requests per second: 123.46 [#/sec] (mean) |
| 228 | }}} |
| 229 | 1秒間に128.87回、リクエストを処理することができます。この値が大きいほどパフォーマンスが良いということです。 |
| 230 | |
| 231 | |
| 232 | = ●検証2:10000件の全件検索 = |
| 233 | |
| 234 | 実行したSQL(SQL1)は次の通りです。 |
| 235 | {{{ |
| 236 | SELECT * FROM test1 |
| 237 | }}} |
| 238 | 結果は以下のようになりました。 |
| 239 | {{{ |
| 240 | shared_buffers #/sec test1.disk |
| 241 | test1.cache test2.disk test2.cache |
| 242 | 16 3.86 243 11757 255 11781 |
| 243 | 100 3.83 209 11791 219 11817 |
| 244 | 1000 3.96 23 11977 24 12012 |
| 245 | }}} |
| 246 | test1.diskは、テーブルtest1に関して、ディスクからデータを取得した回数、test1.cacheは、キャッシュにデータがあった回数を示します。キャッシュにあった回数が多い方が高速に処理が行われます。 |
| 247 | shared_buffersのサイズが小さすぎる場合は、ディスクからの読み込みが頻発しているのがわかります。 |
| 248 | |
| 249 | |
| 250 | |
| 251 | == ●検証3:10000件のテーブルと100000件のテーブルを結合し、条件を指定して検索 == |
| 252 | |
| 253 | 実行したSQL(SQL2)は次の通りです。 |
| 254 | {{{ |
| 255 | SELECT * FROM test1 JOIN test2 USING(id) WHERE id=100 |
| 256 | }}} |
| 257 | 結果は以下のようになりました。 |
| 258 | |
| 259 | {{{ |
| 260 | shared_buffers #/sec test1.disk |
| 261 | test1.cache test2.disk test2.cache |
| 262 | 16 53.48 12 0 1 0 |
| 263 | 100 74.63 1 11 1 11 |
| 264 | 1000 68.03 1 11 1 11 |
| 265 | }}} |
| 266 | |
| 267 | shared_buffersが16の時に、キャッシュが使われていないことがわかります。 |
| 268 | また、他のSQLに比べて、#/secの変化が大きくなっています。これは、SQLの実行結果が他と比べて少ないためだと推測できます。検索結果が多い場合には、全体の処理の中で、データの転送にかかる時間が大半を占めます。それに対して検索結果が少ない場合は全体の実行時間に対する転送時間の占める割合が小さくなります。そうするとキャッシュが使われた/使われていないの違いによる処理時間の違いが占める割合が大きくなりますので、実行時間の数字はその影響を受けやすくなります。 |
| 269 | 通常アプリケーションから実行されるSQLは、結果が少ないものが多数を占めますので、キャッシュが使われた/使われていないは、システム全体を見ても大きく影響を及ぼすことになります。 |
| 270 | |
| 271 | |
| 272 | 次に下記SQL(SQL3)を実行します。 |
| 273 | {{{ |
| 274 | SELECT * FROM test1 JOIN test2 USING(id) WHERE id<5000 |
| 275 | }}} |
| 276 | |
| 277 | 結果は以下のようになりました。 |
| 278 | |
| 279 | {{{ |
| 280 | shared_buffers #/sec test1.disk |
| 281 | test1.cache test2.disk test2.cache |
| 282 | 16 3.86 243 11757 255 11781 |
| 283 | 100 3.83 209 11791 219 11817 |
| 284 | 1000 3.96 23 11977 24 12012 |
| 285 | }}} |
| 286 | shared_buffersが1000の時だけ、キャッシュが有効に使われていることがわかります。 |
| 287 | |
| 288 | |
| 289 | = ●結論 = |
| 290 | |
| 291 | これらの検証の結果、この状況ではshared_buffersが100では不足していることがわかります。SQL1とSQL2では十分であるように見えますが、SQL3ではディスクからの読み込みが必要以上に発生しています。 |
| 292 | ここではそれほど大きなパフォーマンスの差異にはつながっていませんが、負荷がもっと高くなったり、扱うテーブルとデータの量が増えた場合には、より顕著に違いが現れると思われます。 |
| 293 | |