ぱと隊長日誌

ブログ運用もエンジニアとしての生き方も模索中

PDU 申請で監査対象となったときの対応(EDUCATION - Read 編)

概要

PMP 資格更新の為には PDU を申請する必要があります。PDU を申請すると、監査対象に選ばれることがあります。EDUCATION - Read(読書)で監査対象に選ばれた場合の対応を説明します。

監査通知

PDU 申請が監査対象に選ばれると、以下のようなメールが届きます。

題名:
More information is needed for your PDU claim

本文:
Thank you for your participation in PMI''s Continuing Certification Requirements (CCR) program.

We reviewed your claim for professional development units (PDUs) and determined that we need more information.

<中略>

Specifically, we need information for the following reason:
28 Feb 2023 01:21 PM: (Admin) - The amount of PDUs claimed needs to be verified. Please provide documentation verifying the hours spent in this activity or the units of credit advertised.

Review the CCR handbook to determine the type of information you should send to respond to this request.

You may upload your verification documents or provide comments regarding this PDU claim by logging into ccrs.pmi.org.
Please provide this required information within 3 days from the initial claim submission date.

Please contact the Customer Care Centre in your region if you have questions or concerns.

提出書類

提出する情報は CCR handbook を参照せよ、とあります。

Continuing Certification Requirements (CCR) Handbook

CCR handbook の EDUCATION - Read で以下の記載があります。

Documentation required for audit: Evidence supporting your reported learning, including notes from and dates of reading.

私の場合は以下の項目で資料(英語で記述)を作成しました。

  • Title(書名)
  • Study Notes(何を学んだのか?業務にどう活かせるのか?)
  • 読書記録(以下の項目を含む表形式)
    • Date(日付)
    • Read time [hour](読書時間)
    • The PMI Talent Triangle 毎の内訳
      • Ways of Working [hour]
      • Power Skills [hour]
      • Business Acumen [hour]
    • total

読書記録の表形式を示します。

Date Read time [hour] Ways of Working [hour] Power Skills [hour] Business Acumen [hour]
2023/1/1 (Sun) 2.0 1.5 0.0 1.5
2023/1/2 (Mon) 1.0 0.5 0.0 0.5
2023/1/3 (Tue) 1.0 0.5 0.0 0.5
total 4.0 2.0 0.0 2.0

提出可能なファイルフォーマットはアップロード画面で示されます。今回は PDF 形式で出力しました。

提出手続き

PMI サイトでの資料提出の流れを説明します。画面構成は現時点のものです。

(1)
Dashboard の Claims で "Needs More Infomation" リンクをクリックします。
Dashboard の "Needs More Infomation" リンクをクリックする

(2)
Dashboard > Claim History の Claim Status で対象の Activity をクリックします。
Claim Status の Activity をクリックする

(3)
Dashboard > Claim History > Claim Detail の Edit ボタンをクリックします。
Claim Detail の Edit ボタンをクリックする

(4)
Supporting Documents の "Add Supporting Document" ボタンをクリックします。
Supporting Documents の "Add Supporting Document" ボタンをクリックする

(5)
ダイアログで提出可能なファイルフォーマットが示されます。"Select File..." ボタンをクリックし、提出するファイルを選んでアップロードします。
ダイアログで提出可能なファイルフォーマットが示される

(6)
"I agree this claim is accurate." のチェックを入れ、"Submit" ボタンをクリックします。
資料を提出する

(7)
Claim Submission Results 画面が表示されたら完了です。
審査には最大5営業日かかるとの説明がありました。
提出完了

(8)
再審査になるとメールで通知が届きます。コメントを読んで対応しましょう。

困ったときは

PMI のサポートを活用しましょう。

日本向けにメールでのサポートも提供されています。日本語で問い合わせが可能です。
https://www.pmi.org/about/contact/asia-pacific
Email - Customer Care Japan を参照してください。

PostgreSQL インデックス肥大化とインデックスコストへの影響(再モデル化)

概要

以前に PostgreSQL インデックス肥大化とインデックスコストへの影響を示しました。
PostgreSQL のインデックス肥大化と実行計画のコストへの影響 - ぱと隊長日誌

この記事でインデックスコストのモデル化にも挑戦しましたが、正確性が良くありませんでした。

その後、コード解析が進み、以前のモデルの問題点が判明しました。今回は現時点の PostgreSQL 最新版でも同様の事象が起こることを示しつつ、モデルの見直しを行います。

前提

PostgreSQL のインデックスサイズは一度大きくなると、その後小さくなるタイミングが限られています。

「[改訂新版]内部構造から学ぶPostgreSQL-設計・運用計画の鉄則」でインデックスファイルサイズが小さくなるのは以下のタイミングとしています。

  • DROP INDEX でインデックス自体を削除した場合
  • TRUNCATE TABLE でテーブル全体を空にした場合
  • REINDEX でインデックスを再構成した場合

テーブルのデータの追加・更新・削除を繰り返しているうちに、インデックスファイルサイズが必要以上に大きくなった状態を、本記事では「インデックス肥大化」としました。

インデックス肥大化した状況では実行計画のコスト計算に影響することがあります。これは適切な実行計画を選択する妨げとなるかもしれません。

本記事ではインデックス肥大化がインデックスコスト計算に影響を与えるメカニズムを説明します。

検証環境

PostgreSQL 15.2

なお、PostgreSQL の日本語版マニュアルの最新版は現時点で 14 を対象としているため、記事の中ではこれを参照しています。

調査方法・結果

以下の状況を作ります。
(1) テーブルに行を 10^5 追加する。
(2) テーブルの行を 10^5 ⇒ 10^6 まで増やす。
(3) テーブルの行を 10^6 ⇒ 10^5 まで減らす(※インデックス肥大化の発生)。
(4) テーブルを REINDEX する(※インデックス肥大化の解消)。

各状況において、以下の項目を確認します。

  • インデックスの行数
  • インデックスのページ数
  • SELECT で 5000 行を Index Only Scan した時のコスト
testdb=# CREATE TABLE tab1(c1 INTEGER PRIMARY KEY);
CREATE TABLE

testdb=# \d tab1;
                Table "public.tab1"
 Column |  Type   | Collation | Nullable | Default
--------+---------+-----------+----------+---------
 c1     | integer |           | not null |
Indexes:
    "tab1_pkey" PRIMARY KEY, btree (c1)

(1) テーブルに行を 10^5 追加する。

testdb=# INSERT INTO tab1 SELECT generate_series(1, 1e+5);
INSERT 0 100000

testdb=# VACUUM ANALYZE;
VACUUM

testdb=# SELECT reltuples, relpages FROM pg_class WHERE relname = 'tab1_pkey';
 reltuples | relpages
-----------+----------
    100000 |      276
(1 row)

testdb=# EXPLAIN SELECT * FROM tab1 WHERE c1 <= 5000;
                                   QUERY PLAN
--------------------------------------------------------------------------------
 Index Only Scan using tab1_pkey on tab1  (cost=0.29..150.12 rows=5133 width=4)
   Index Cond: (c1 <= 5000)
(2 rows)

(2) テーブルの行を 10^5 ⇒ 10^6 まで増やす。

testdb=# INSERT INTO tab1 SELECT generate_series((1e+5)+1, 1e+6);
INSERT 0 900000

testdb=# VACUUM ANALYZE;
VACUUM

testdb=# SELECT reltuples, relpages FROM pg_class WHERE relname = 'tab1_pkey';
 reltuples | relpages
-----------+----------
     1e+06 |     2745
(1 row)

testdb=# EXPLAIN SELECT * FROM tab1 WHERE c1 <= 5000;
                                   QUERY PLAN
--------------------------------------------------------------------------------
 Index Only Scan using tab1_pkey on tab1  (cost=0.42..152.44 rows=5258 width=4)
   Index Cond: (c1 <= 5000)
(2 rows)

インデックスの行数が10倍になると、インデックスのページ数もほぼ10倍になりました。ただ、Index Only Scan のコストはほとんど変わりません。Index Only Scan が必要な範囲のインデックスのページだけ読み込むと想定すれば、これは妥当な結果といえそうです。

次にインデックスの行数を1/10にして、Index Only Scan へのコスト影響を確認します。

(3) テーブルの行を 10^6 ⇒ 10^5 まで減らす(※インデックス肥大化の発生)。

testdb=# DELETE FROM tab1 WHERE c1 > 1e+5;
DELETE 900000

testdb=# VACUUM ANALYZE;
VACUUM

testdb=# SELECT reltuples, relpages FROM pg_class WHERE relname = 'tab1_pkey';
 reltuples | relpages
-----------+----------
    100000 |     2745
(1 row)

testdb=# EXPLAIN SELECT * FROM tab1 WHERE c1 <= 5000;
                                   QUERY PLAN
--------------------------------------------------------------------------------
 Index Only Scan using tab1_pkey on tab1  (cost=0.42..649.14 rows=5070 width=4)
   Index Cond: (c1 <= 5000)
(2 rows)

インデックスの行数は1/10に変わりましたが、インデックスのページ数は変わりません。そして全体推定コストは4.3倍に増えました。

(4) テーブルを REINDEX する(※インデックス肥大化の解消)。

testdb=# REINDEX TABLE tab1;
REINDEX

testdb=# SELECT reltuples, relpages FROM pg_class WHERE relname = 'tab1_pkey';
 reltuples | relpages
-----------+----------
    100000 |      276
(1 row)

testdb=# EXPLAIN SELECT * FROM tab1 WHERE c1 <= 5000;
                                   QUERY PLAN
--------------------------------------------------------------------------------
 Index Only Scan using tab1_pkey on tab1  (cost=0.29..145.02 rows=5070 width=4)
   Index Cond: (c1 <= 5000)
(2 rows)

REINDEX でインデックスを再生成することで、インデックスのページ数は縮小しました。
全体推定コストも当初とほぼ変わらない数字になりました。

インデックスコストのモデル化

インデックスコストのモデル化を行います。状況は今回の実験例を想定します。

PostgreSQL のマニュアルに「インデックスコスト推定関数」の説明があります。
62.6. インデックスコスト推定関数

この説明ページには「典型的なコスト概算」の例が記載されています。この流れに従いつつ、差分を説明します。

インデックスのコスト計算は下記のソースコード・関数で実装されているようです。

src/backend/optimizer/path/costsize.c
cost_index(IndexPath *path, PlannerInfo *root, double loop_count, bool partial_path)

この関数の実装に「インデックスコスト推定関数」である amcostestimate を呼びだす箇所があります。amcostestimate は下記のソースコード・関数で実装されています。

src/backend/utils/adt/selfuncs.c
btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count, Cost *indexStartupCost, Cost *indexTotalCost, Selectivity *indexSelectivity, double *indexCorrelation, double *indexPages)

以降、必要に応じてソースコードを参照します。

計算で用いる変数名が対応するプランナコスト定数と異なる場合は併記しています。

プランナコスト定数は下記マニュアルに記載されています。
20.7. 問い合わせ計画

1. 与えられた制約条件に基づいて訪れられるメインテーブルの行の割合を概算して返します。

「対象の行数 / 全体の行数」が indexSelectivity になると仮定します。
対象の行数は 5000 です。

全体の行数が 10^5:
5*10^3 / 10^5 = 0.05

全体の行数が 10^6:
5*10^3 / 10^6 = 0.005

2. スキャン中に訪れられるインデックスの行数を概算します。多くのインデックス種類では、これは indexSelectivity とインデックスの中にある行数を掛けたものと等しいですが、それより多い場合もあります。

全てのケースで 5000 とします。

3. スキャン中に抽出されるインデックスページ数を概算します。これは単に indexSelectivity にページ内のインデックスのサイズを掛けたものになるでしょう。

「ページ内のインデックスのサイズ」は pg_class の relpages に等しいとします。

実装を見ると小数点以下の切り上げをしているので、計算でも切り上げします。
src/backend/utils/adt/selfuncs.c
genericcostestimate(PlannerInfo *root, IndexPath *path, double loop_count, GenericCosts *costs)

numIndexPages = ceil(numIndexTuples * index->pages / index->tuples);

テーブルに行を 10^5 追加した後:
ceil(0.05 * 276) = 14

テーブルの行を 10^5 ⇒ 10^6 まで増やした後:
ceil(0.005 * 2745) = 14

テーブルの行を 10^6 ⇒ 10^5 まで減らした後:
ceil(0.05 * 2745) = 138

テーブルを REINDEX した後:
ceil(0.05 * 276) = 14

4. インデックスアクセスコストを計算します。汎用的な概算においては以下のように行うでしょう。

/*
 * 一般的な仮定は、インデックスページは逐次的に読まれるので、
 * random_page_cost ではなく、それぞれ seq_page_cost が掛かるというものです。
 * 各インデックス行での indexquals の評価にもコストが掛かります。
 * コストはすべてスキャンの間に徐々に支払われると仮定します。
 */
<中略>
*indexTotalCost = seq_page_cost * numIndexPages +
    (cpu_index_tuple_cost + index_qual_cost.per_tuple) * numIndexTuples;

マニュアルでは seq_page_cost で計算しています。ですが、実装では random_page_cost で計算していました。

src/backend/utils/adt/selfuncs.c
genericcostestimate(PlannerInfo *root, IndexPath *path, double loop_count, GenericCosts *costs)

indexTotalCost = numIndexPages * spc_random_page_cost;

indexquals の評価はほぼマニュアル通りです。
src/backend/utils/adt/selfuncs.c
genericcostestimate(PlannerInfo *root, IndexPath *path, double loop_count, GenericCosts *costs)

indexTotalCost += numIndexTuples * num_sa_scans * (cpu_index_tuple_cost + qual_op_cost);

ここまでを踏まえ、マニュアル記載の式で seq_page_cost を random_page_cost に置換して再計算します。
indexTotalCost = random_page_cost * numIndexPages + (cpu_index_tuple_cost + index_qual_cost.per_tuple) * numIndexTuples;
random_page_cost = 4.0
numIndexPages = 「スキャン中に抽出されるインデックスページ数」に等しいとします。
cpu_index_tuple_cost = 0.005
index_qual_cost.per_tuple = cpu_operator_cost = 0.0025
numIndexTuples = 「スキャン中に訪れられるインデックスの行数」に等しいとします。

テーブルに行を 10^5 追加した後:
4.0 * 14 + (0.005 + 0.0025) * 5000 = 93.5

テーブルの行を 10^5 ⇒ 10^6 まで増やした後:
4.0 * 14 + (0.005 + 0.0025) * 5000 = 93.5

テーブルの行を 10^6 ⇒ 10^5 まで減らした後:
4.0 * 138 + (0.005 + 0.0025) * 5000 = 589.5

テーブルを REINDEX した後:
4.0 * 14 + (0.005 + 0.0025) * 5000 = 93.5

さらに CPU コストが加算されます。
src/backend/optimizer/path/costsize.c
cost_index(IndexPath *path, PlannerInfo *root, double loop_count, bool partial_path)

cpu_run_cost += cpu_per_tuple * tuples_fetched;

cpu_per_tuple = cpu_tuple_cost = 0.01
tuples_fetched = 「スキャン中に訪れられるインデックスの行数」に等しいとします。

テーブルに行を 10^5 追加した後:
93.5 + 0.01 * 5000 = 143.5

テーブルの行を 10^5 ⇒ 10^6 まで増やした後:
93.5 + 0.01 * 5000 = 143.5

テーブルの行を 10^6 ⇒ 10^5 まで減らした後:
589.5 + 0.01 * 5000 = 639.5

テーブルを REINDEX した後:
93.5 + 0.01 * 5000 = 143.5

モデル化により、インデックス肥大化によるインデックスコスト増大の要因は「スキャン中に抽出されるインデックスページ数」が大きいとわかります。

また、random_page_cost もインデックスコストに大きく影響しています。マニュアルでは以下の記載があります。

例えばSSDのような、シーケンシャルアクセスに比べてランダム読み込みコストがあまり大きくない記憶装置の場合も、random_page_cost に対し1.1のようにより低い値のモデル化の方が良いでしょう。

20.7. 問い合わせ計画

この指針に従うならば、インデックス肥大化によるインデックスコスト増大を抑えることができます。結果としてより最適な実行計画を得られるかもしれません。

VS Code と GDB で PostgreSQL をデバッグする

概要

VS CodeGDBPostgreSQLデバッグする手法については、わかりやすいスライドが公開されています。

ただ、この資料では WSL (Ubuntu) 上で構築していたり、VS Code に慣れていないと戸惑う箇所がありました。

そこで、本記事は他の環境でも同様の手法でデバッグできることを示し、VS Code で戸惑いやすいポイントを解説します。

環境

Windows 10 の Hyper-VRed Hat Enterprise LinuxVM を構築します。この VMPostgreSQL をインストール・動作させ、Windows 10 の VS Code からデバッグできるようにします。

構成図を示します。
VS Code からデバッグする際の構成図

なお、このイメージ図では VS Code Server を省略しています。構築手順において意識せずに済むためです。

ホスト

OS Windows 10 Pro バージョン 22H2

Hyper-V

構成バージョン 9.2
OS Red Hat Enterprise Linux release 9.1

ソフトウェア

ソフトウェア バージョン
Visual Studio Code 1.75.0
PostgreSQL 15.1
GDB 10.2-10.el9

用語

本記事においては下記の略記を用います。

略記 名称
VS Code Visual Studio Code

構築手順

(1) VM構築

今回の記事では割愛します。

VM の構成バージョンを指定して作成したい場合は下記の記事を参照してください。
Hyper-V の仮想マシンで perf の "not supported" な項目を出力できるようにする - ぱと隊長日誌

(2) PostgreSQL + GDB 構築

PostgreSQLソースコードからインストールします。
基本的な手順は PostgreSQL マニュアルを参照してください。
第17章 ソースコードからインストール
※リンク先は現時点で日本語版かつ最新のバージョン 14 です。

以下で基本的な手順からの差分を示します。

PostgreSQL ビルド時にはデバッグ用のオプションを指定します。

# ./configure --enable-debug CFLAGS=-O0

GDB をインストールします。

# dnf install gdb

(3) VS Code インストール

VS Code の公式サイトからダウンロード・インストールします。
Visual Studio Code – コード エディター | Microsoft Azure

以下の拡張機能をインストールします。

(4) VS CodeSSH リモート接続する

VS Code の該当マニュアルを示します。
Developing on Remote Machines using SSH and Visual Studio Code

以下でポイントを説明します。

Ctrl+Shift+P でコマンドパレットを開き、「Remote-SSH:ホストに接続する...」をクリックします。
VS Code で SSH リモート接続する

テキストボックスに "user@host" の形式で入力します。例えば、"root@192.168.0.15" のようになります。
VS Code で接続先ホスト名とユーザー名を入力する

新しい VS Code のウィンドウが開きます。
初回接続時は接続先 OS の種類を聞かれますので、適宜選択します。
VS Code で初回接続時に接続先 OS を聞かれる

接続先ユーザーのパスワードを要求されるので入力します。
この後の手順でもパスワードを要求されることがあるので、同様に入力します。
VS Code でパスワードを入力する

Ctrl+K Ctrl+O でフォルダー(ディレクトリ)を開きます。ここでは PostgreSQLソースコードが格納されたフォルダーを指定します。
VS Code でフォルダを開く

フォルダーを信頼するか否か問われるので、「信頼する」を選びます。
VS Code でフォルダを信頼するか否か問われるダイアログ

(5) リモートホストVS Code拡張機能をインストールする

以下の拡張機能リモートホストにインストールします。

該当の拡張機能に「SSH: 192.168.0.15 にインストールする」のようなボタンが現れています。これをクリックしてインストールします。
VS Code で拡張機能をリモートホストにインストールする際のボタン

(6) PostgreSQLデバッグ対象を選択する

PostgreSQL の構造とソースツリー(3) | Let's POSTGRES
この記事同様に、"SELECT 1;" というクエリが ExecSelect 関数に至る様子を観察することにします。

PostgreSQLpsql で接続し、バックエンドのプロセス ID を確認します。

=# SELECT pg_backend_pid();

VS Code で任意の C ファイルを開きます。

Ctrl+Shift+D で「実行とデバッグ」を開きます。

「launch.json ファイルを作成します」というリンクをクリックします。
開いたフォルダーの直下に ".vscode" というサブフォルダーが作られ、そこに launch.json ファイルが格納されます。

launch.json ファイルの例を示します。

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "postgres",
            "type": "cppdbg",
            "request": "attach",
            "program":"/usr/local/pgsql/bin/postgres",
            "processId": 3400,
            "MIMode": "gdb",
            "miDebuggerPath": "/usr/bin/gdb"
        }
    ]
}

環境に応じて適宜書き換えます。"processId" は先ほど調べたバックエンドのプロセス ID を指定します。
VS Code の launch.json 編集画面

F5 を押すとデバッグが開始されます。

デバッガー画面のブレークポイントに "ExecResult" を追加します。

psql から "SELECT 1;" を実行してブレークポイントで停止すれば成功です。
VS Code でブレークポイントにて停止した画面

落とし穴

リモートホストVS Code拡張機能をインストールしないといけないのはなぜ?

VS Code のバックエンド (VS Code Server) が拡張機能を実行するためです。

VS Code のマニュアルから SSH 接続でリモート開発する際のアーキテクチャ概要図を示します。

Developing on Remote Machines using SSH and Visual Studio Code

VS Code はフロントエンド・バックエンドのプロセスが分離されています。

Visual Studio Code」がマルチプロセスで設計されているからだ。コードを入力するフロントエンドと、それ以外のバックエンド(拡張機能、ターミナル、デバッガーなど)のプロセスは分離されており、異なるデバイスに分けて動作させることもできる。

VS Code Server」はこのバックエンド部分のみを独立させ、1つのサービスとして利用できるようにしたものだ。CLIとして動作する仕組みになっており、ローカルの開発マシン、クラウド仮想マシンなど好きなところにインストールできる。

「VS Code Server」が登場 ~VS Codeのバックエンドを単体CLIに、フロントエンドにはWeb版が使える - 窓の杜

VS Code Server は SSH 接続時に自動でインストールされています。
VS Code Remote - SSH extension で Linux のリモートマシンに接続すると VS Code Server が自動でインストールされる - ぱと隊長日誌

launch.json ファイルを作れない

任意の C ファイルを開いた状態で「launch.json ファイルを作成します」リンクをクリックすれば作成できます。

コールスタックで「不明なソース」と表示される

VS Code のコールスタックで「不明なソース」と表示される

原因の一つとして考えられるのが、PostgreSQL コンパイル時のデバッグオプションを正しく指定していないことです。PostgreSQL を再ビルドしたのであれば、make distclean を実行したか確認してください。

再ビルド時の make distclean の必要性については下記記事を参照してください。
PostgreSQL のビルドで configure オプション変更時は make distclean を実行すべき - ぱと隊長日誌

デバッガーが動作しないので問題を切り分けたい

本記事の手順を踏んでいれば、PostgreSQL をインストールしたサーバに GDB もインストールしているはずです。なので、GDBデバッグできるか否かで問題をある程度切り分け可能です。GDB でのデバッグ手順は下記記事を参照してください。
PostgreSQL の構造とソースツリー(3) | Let's POSTGRES