そーだいなるらくがき帳

そーだいが自由気侭に更新します。

PostgreSQLの監視 ~ mackerel-plugin-postgresを読み解く

この記事は Mackerel プラグインアドベントカレンダー(全部CRE) の25日目です。

qiita.com

soudai.hatenablog.com

それでは25日目は mackerel-plugin-postgres です。

mackerel-plugin-postgresはRDBMSでもMySQLと同じように広く使われているPostgreSQL専用プラグインです。

github.com

インストールと設定手順

PostgreSQLプラグインPostgreSQLの統計情報の内容を可視化してくれるプラグインです。

プラグインプラグイン集として提供しているパッケージの mackerel-agent-plugins に含まれています。 インストール名は mackerel-plugin-postgres です。

次にMackerelのプラグインはコマンドですので実行する事ができます。

※認証を設定している場合はオプションを利用して認証情報を入力しましょう

-- /usr/bin はPATHが通っているので省略出来ます
# mackerel-plugin-postgres -user=postgres -password=hogehoge
postgres.size.total_size        22330912.000000 1514040349
postgres.iotime.blk_read_time   0.000000        1514040349
postgres.iotime.blk_write_time  0.000000        1514040349
postgres.connections.active     1.000000        1514040349
postgres.connections.active_waiting     0.000000        1514040349
postgres.connections.idle       1.000000        1514040349
postgres.connections.idle_in_transaction        0.000000        1514040349
postgres.blocks.blks_read       2610.000000     1514040349
postgres.blocks.blks_hit        12600.000000    1514040349
postgres.rows.tup_returned      11340.000000    1514040349
postgres.rows.tup_fetched       11280.000000    1514040349
postgres.rows.tup_inserted      0.000000        1514040349
postgres.rows.tup_updated       0.000000        1514040349
postgres.rows.tup_deleted       0.000000        1514040349
postgres.deadlocks.deadlocks    0.000000        1514040349
postgres.tempfile.temp_bytes    0.000000        1514040349
postgres.commits.xact_commit    180.000000      1514040349
postgres.commits.xact_rollback  0.000000        1514040349

設定ファイルであるmackerel-agent.confは標準では /etc/mackerel-agent/mackerel-agent.conf にインストールされます。 こちらに下記のとおり追記しましょう。

[plugin.metrics.postgres]
command = "mackerel-plugin-postgres"

以上を行った上でmackerel-agentを再起動してください。

見れるメトリック

まずこれを見てくれるとこのあとの話はすっと理解できると思います。

soudai.hatenablog.com

各グラフ定義ごとに説明します。 それでは各グラフ定義ごとに説明します。 また表に出てくるdiffとはプラグイン上で差分値計算をするかどうかです。 となっている項目はプラグインで前回の実行時の値と差分値計算して出力しています。

Postgres Connections

メトリック名(ラベル) プラグインの出力名 diff 説明
Active postgres.connections.active 現在の接続でActive状態の数
Active waiting postgres.connections.active_waiting 現在の接続でActive waiting状態の数
Idle postgres.connections.idle 現在の接続でIdle状態の数
Idle in transaction postgres.connections.idle_in_transaction 現在の接続でIdle in transaction状態の数
fast-path function call postgres.connections.idle_in_transaction 現在の接続でfast-path function call状態の数
Disabled postgres.connections.disabled 現在の接続でDisabled状態の数

接続の状態ですがこれは統計情報の pg_stat_activity を見ています。 状態の種類ですが公式ドキュメントには次の通り書いてあります。

引用元:28.2. 統計情報コレクタ

現在のバックエンドの総体的な状態です。 以下のいずれかの値を取ることができます。 - active: バックエンドは問い合わせを実行中です。 - idle: バックエンドは新しいクライアントからのコマンドを待機しています。 - idle in transaction: バックエンドはトランザクションの内部にいますが、現在実行中の問い合わせがありません。 - idle in transaction (aborted): この状態はidle in transactionと似ていますが、トランザクション内のある文がエラーになっている点が異なります。 - fastpath function call: バックエンドは近道関数を実行中です。 - disabled: この状態は、このバックエンドでtrack_activitiesが無効である場合に報告されます。

Active、idle、idle in transactionについては皆さんの想像の通りですし、ここに書いてあるとおりです。 idle in transaction (aborted)は他のRDBMSから来た人にはわかりにくいかもしれません。 PostgreSQLトランザクションは実行時にエラーが発生するとabortedになり、その後のクエリを受け付けません。 この状態になるとロールバックするしかありません。 abortedは勿論クエリエラーでもなりますがデッドロックが発生した場合はabortedになります(コネクションはPostgreSQL側からは切られない) そのためデッドロックによってクエリが止まっている場合などもここが増えるので覚えておきましょう。 このようにidle in transaction (aborted)はとても大事な指標ですが通常時は0を目指しましょう。

fastpath function callは知らない人からすると意味不明だと思います。 近道関数はPQFnという関数で近道インターフェイスを利用する関数を指します。 基本的にPQFnは公式ドキュメントにもある通り、なかなか使われません。 ではどこで使われているのでしょうか?一般的に目にする機会があるのはラージオブジェクトを利用するときでしょう。 ラージオブジェクトとは他のデータベースではBlob(Binary large object)と呼ばれるモノと同等で大きなデータを扱う仕組みのです。 これにより、画像や動画のような大きなデータでもPostgreSQLに保存することが出来ます。 ちなみに9.3以上では4TBまで保存することが出来るので概ね保存できます。 なおラージオブジェクトは pg_largeobjectに保存されるので興味がある人はそちらもどうぞ。 少し脱線しましたがラージオブジェクトインターフェイスはPQFnを呼び出しています。 そのためラージオブジェクトを使っている仕組みの場合はfastpath function callを意識する必要があります。 逆にそれ以外の時は特に気にしなくても良いでしょう。

https://www.postgresql.jp/document/9.6/html/libpq-fastpath.html

disabledは track_activities が無効化されている場合のみ出てきます。 track_activities デフォルトは有効になっているのでお目にかかることは無いかもしれません。

Postgres Commits

メトリック名(ラベル) プラグインの出力名 diff 説明
Xact Commit postgres.commits.xact_commit 1分間あたりのデータベースでコミットされたトランザクション
Xact Rollback postgres.commits.xact_rollback 1分間あたりのデータベースでコミットされたロールバック

これは pg_stat_database の値を使っています。 データベースの総数なのですがロールバックが多い場合などは要注意しましょう。 アプリケーション側のエラーまたはデッドロックの可能性があります。 デッドロックの場合は後述の Postgres Dead Locks を見てみて、切り分けしましょう。 特にリリース直後やUPDATEが多い仕組みでの繁忙期などは要注意です。 ロールバックが増える要因の一つに制約にぶつかりまくるというのもあります。 これは制約が守ってくれているので正しい挙動ではありますがアプリケーション側は制約にぶつからない事が本来正しい振る舞いです。 普段はロールバックは0になるように調整しましょう。

Postgres Blocks

メトリック名(ラベル) プラグインの出力名 diff 説明
Blocks Read postgres.blocks.blks_read 1分間あたりのDiskから読み出したブロック数
Blocks Hit postgres.blocks.blks_hit 1分間あたりのキャッシュから読み出したブロック数

これは pg_stat_database の値を使っています。 公式ドキュメントにある通り、blks_hitはOSでのfilesystemのキャッシュは含まれません。

blks_hit : バッファキャッシュに既にあることが分かっているために読み取りが不要だったディスクブロック数です(これにはPostgreSQLのバッファキャッシュにおけるヒットのみが含まれ、オペレーティングシステムファイルシステムキャッシュは含まれません)。

f:id:Soudai:20171224130301p:plain

PostgreSQLアーキテクチャは良くも悪くもOSの振る舞いには関与しません。 そのため、PostgreSQL がディスクアクセスをしていると思っていてもOS側のキャッシュで返している事があります。 ここはアーキテクチャとして大切なところなので覚えておきましょう。

Postgres Rows

メトリック名(ラベル) プラグインの出力名 diff 説明
Returned Rows postgres.rows.tup_returned 1分間あたりのデータベース内の問い合わせで返された行数
Fetched Rows postgres.rows.tup_fetched 1分間あたりのデータベース内の問い合わせで取り出された行数
Inserted Rows postgres.rows.tup_inserted 1分間あたりのデータベース内の問い合わせで挿入された行数
Updated Rows postgres.rows.tup_updated 1分間あたりのデータベース内の問い合わせで更新された行数
Deleted Rows postgres.rows.tup_deleted 1分間あたりのデータベース内の問い合わせで削除された行数

実行されたSQLに対する行数です。 Returned RowsFetched Rows 違いの分からない人もいると思います。 ここは公式ドキュメントも説明が不親切です。 いやまぁ公式ドキュメントは基本的に不親切なんだけど。 ざっくりいうと次のとおりです。

  • Returned Rows : 表スキャンでの読み取り行数
  • Fetched Rows : インデックススキャンでの読み取り行数

その他はそれぞれ見たまま通り、INSERT/UPDATE/DELETE をされた行数です。 これらは定期的に確認しましょう。 リリース直後に想定通り振る舞っているかどうかを確認するときにも良い指標になります。 テーブルスキャンが疑われるようならその次は pg_stat_user_tables を見ましょう。

Postgres Data Size

メトリック名(ラベル) プラグインの出力名 diff 説明
Total Size postgres.size.total_size データベースのトータルサイズ(バイト)

こちらはシンプルにデータベースで使用されるディスク容量の合計値が見れます。 ただしこれはmackerel-plugin-postgresを実行しているユーザがアクセスできるデータベースのトータルサイズです。 postgresユーザのように全てアクセスできるユーザなら全てのデータベースのトータルサイズですし、一部しかアクセスできないユーザならそのアクセス出来るデータベースの合計になります。 ここは意図しない振る舞いになっていることあると思いますから実行しているユーザで一度 select datname from pg_database where has_database_privilege(datname, 'connect') を実行して確認しておきましょう。 個人的には別に積上げにしてデータベース名毎のサイズ出してもいいかもなって思っているところです。

またTipsですがpg_size_pretty()を使うとバイトをKBやGBなど人間に優しい表示に変換することができます。 SQLで確認する時は知っておくと便利です。

Postgres Dead Locks

メトリック名(ラベル) プラグインの出力名 diff 説明
Deadlocks postgres.deadlocks.deadlocks 1分間あたりの検知したデッドロックの数

その名の通り、デッドロックの数です。Postgres Commits と併せて確認すると良いでしょう。 通常時は0になるように目指しましょう。 またデッドロックを調査する時は pg_stat_activitywait_eventwait_event_type を見ると良いでしょう。 その他にも pg_locks には現状のロックの一覧があります。 下記のクエリをご活用ください。

-- ロック待ちとなっている処理内容と対象のテーブルを確認する
-- 表示の関係でqueryを6文字で切っています
SELECT l.locktype, c.relname, l.pid, l.mode,
         substring(a.current_query, 1, 6) AS query,
         (current_timestamp - xact_start)::interval(3) AS duration
   FROM   pg_locks l LEFT OUTER JOIN pg_stat_activity a
          ON l.pid = a. procpid
          LEFT OUTER JOIN pg_class c ON l.relation = c.oid
   WHERE  NOT l.granted ORDER BY l.pid;

Postgres Block I/O time

メトリック名(ラベル) プラグインの出力名 diff 説明
Block Read Time (ms) postgres.iotime.blk_read_time 1分間あたりのデータファイルブロックの読み取り時間(ms)
Block Write Time (ms) postgres.iotime.blk_write_time 1分間あたりのデータファイルブロックの書き出し時間(ms)

こちらはI/Oに関する値です。 回数と併せて時間軸で見るのはとても大切です。 Postgres Blocks と見比べながら活用しましょう。

Postgres Temporary file

メトリック名(ラベル) プラグインの出力名 diff 説明
Temporary file size (byte) postgres.tempfile.temp_bytes 1分間あたりの検知された一時ファイルのサイズ

こちらはその名の通り、一時ファイルを作った時のそのサイズです。 pg_stat_databasetemp_files で個数を見ることが出来るのですがこちらはmackerel-plugin-postgresには含まれていません。 プルリクチャンスポイントの一つです。 また一時ファイルが作られるタイミングですが以下のようなものがあります。

  • GROUP BY や ORDER BY やメモリ内で処理できなかった時
  • JOINの際にメモリ内で処理できなかった時
  • サブクエリなどでクエリの中身を処理する必要があり、それがメモリ内で処理できなかった時

代表的な例を上げましたが、つまりは PostgreSQLがめちゃめちゃ遅くなる時 に一時ファイルは出来ます。 特にORDER BYなどは実行してみないとメモリに乗るかどうかオプティマイザ側では判断出来ませんし、データの肥大化である日突然遅くなる要因の一つです。 メモリをコップだとしたらデータは中身に入れる水です。 コップの中で水を処理できるうちは問題ないのですが溢れるとこぼれ落ち、一時ファイルを必要とします。 そのためついさっきまではめちゃめちゃ早かったのに急に遅くなった時は Postgres Temporary file を疑ってみてください。 詳細が見たい場合は log_temp_files を指定することでログとして細かい情報を残すことができます。 調査の時にご活用ください。

PostgreSQLのモニタリングの勘所

前回まとめたので是非呼んで欲しいです。

soudai.hatenablog.com

mackerel-plugin-postgresでやってないこととして、キャッシュヒット率の計算やテーブル単位での集計などがあります。 つまりMackerelのプルリクチャンスです!! 僕もチャレンジしたいと思っています。 それとMackerelのプラグインで扱っていないレプリケーションの監視についても纏めました。

soudai.hatenablog.com

こちらも併せてご愛顧いただければと思います。


それでは25日目はPostgreSQLプラグインについての説明でした。 なんとか無事走りきることができたMackerel プラグイン アドベントカレンダーですが皆様如何でしたでしょうか? 今回紹介したMackerelのプラグインやモニタリングの知見はほんの一部です。 引き続き、Mackerel User Groupやイベント、そしてMackerel自身を通じて色んなモニタリングの知見を共有していきたいですね。 今度は皆さんの作ったカスタムプラグインを使ったモニタリングの知見のアウトプット、お待ちしています!

それでは皆様、良いお年を。

Mackerel User GroupのSlackに参加