0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

はじめに

PostgreSQLの拡張機能pg_repackは、テーブルの再編成を行うツールです。
別件のトラブルでpg_statsinfoについて調べていると、Windows版のpg_repackバイナリ配布しているという記事を見つけました。

SRA OSSとしてPostgreSQL拡張のWindowsバイナリ提供を開始しているが、今のところはサポート加入者のみで、一般配布は計画中とのこと。

SRA OSSで継続バイナリリリース

※2025年07月時点ではまだ一般配布は行われていません。

じゃー自力でなんとかしてみようと考えたところです。
以前(2018年3月)に挑戦したがこの時は動かせなかったので、リベンジです。

環境

  • Windos 11 Home 64bit
  • Visual Studio 2022(C++ によるデスクトップ開発) インストール済み
  • PostgreSQL 14.7
  • pg_repack 1.5.2 (2025/07/11時点の最新版)

仕事の関係上、現時点では少し古めの PostgreSQL のバージョンを使用します。
PostgreSQL 14は、2021/09/30リリース

開発準備

pg_repackをビルドする上で今回 Visual Studio を使用します。

これまでのpg_hint_planpgauditと違い、pg_repackは、exe と dll の2つプロジェクト構成となっているためプロジェクト開始からの説明ではなく、重要な部分だけの説明に留めます。

pg_repackをフォーク

pg_repackをフォークして、msvcフォルダにあるpg_repack.2022.slnbin.2022.vcxprojlib.2022.vcxprojを作成しました。

環境変数

Windowsの環境変数にて、PostgreSQL64を設定します。
筆者の場合は下記の通りに設定しました。
image.png

binプロジェクト

全部は書けないのでポイント部分のみ

インクルードディレクトリ

構成プロパティの[C/C++]の[全般]にあるインクルードディレクトリ

$(PostgreSQL64)\include\server\port\win32_msvc
$(PostgreSQL64)\include\server\port\win32
$(PostgreSQL64)\include\server
$(PostgreSQL64)\include\internal
$(PostgreSQL64)\include

リンク

構成プロパティの[リンカー]の[入力]にある追加の依存ファイル

ws2_32.lib
libpq.lib
libintl.lib
libpgport.lib
libpgcommon.lib

libプロジェクト

全部は書けないのでポイント部分のみ

インクルードディレクトリ

構成プロパティの[C/C++]の[全般]にあるインクルードディレクトリ

$(PostgreSQL64)\include\server\port\win32_msvc
$(PostgreSQL64)\include\server\port\win32
$(PostgreSQL64)\include\server
$(PostgreSQL64)\include\internal
$(PostgreSQL64)\include

リンク

構成プロパティの[リンカー]の[入力]にある追加の依存ファイル

postgres.lib
ws2_32.lib
libpq.lib
libintl.lib
libpgport.lib
libpgcommon.lib

調査

ビルドできても例外エラー

2018年3月に挑戦した時は、exe側はpostgres.libをリンクしビルドは通ったが、「pg_repack --help」や「pg_repack --version」ですらメモリ違反による例外エラーとなってしまいました。これを克服する必要があります。

wingdi.hの変更

pg_repackreadme.txt内にwingdi.hを修正の指示がありますので、これを行います。

修正するファイルwingdi.hがどこにあるのか?
筆者の場合、Visual Studio 2022のbinプロジェクトのプロパティページのVC++ディレクトリにある全般のインクルードディレクトリ$(VC_IncludePath);$(WindowsSDK_IncludePath);と記載されているので、評価された値を見ます。

image.png

下記フォルダにあるwingdi.hを修正しました。
C:¥Program Files (x86)¥Windows Kits¥10¥Include¥10.0.26100.0¥um

先頭の-行を削除して、+行を追加します。

wingdi.h
 #endif // (_WIN32_WINNT >= _WIN32_WINNT_WINXP)

 /* Region Flags */
-#define ERROR               0
+#define RGN_ERROR           0
 #define NULLREGION          1
 #define SIMPLEREGION        2
 #define COMPLEXREGION       3
-#define RGN_ERROR ERROR

 /* CombineRgn() Styles */
 #define RGN_AND             1

postgres.libをリンクしない方法

pg_repackのデバッグでは、set_pglocale_pgservice(argv[0], “pgscripts”);を実行すると例外エラーとなる。
同じ exe のツールであるvaccumedbのデバッグでは、set_pglocale_pgservice(argv[0], “pgscripts”);を使用していても正常に動作する。
違いとしてvaccumedbでは、postgres.libをリンクしていない。

postgres.libをリンクしている理由は、pgwin32_selectを参照しているからです、ただpg_repackプロジェクト内をpgwin32_selectで検索しても見つからないが、selectで検索すると見つかるのは、次コードのように定義されているからです。

win32.port.h
#define select(n, r, w, e, timeout) pgwin32_select(n, r, w, e, timeout)

select関数を参照しているのはrebuild_indexes関数内の1箇所のみで、下記のコードをコメントアウトすると、postgres.libをリンクしなくてもLINKエラーが出なくなる。

ret = select(max_fd + 1, &input_mask, NULL, NULL, &timeout);

select関数の参照先の違い

pg_repackselect関数は、「win32_port.h」→「socket.c」のpgwin32_select関数を参照している。同じ exe のツールであるpgbenchpg_recvlogicalselect関数は、「winSock2.h」のselect関数を参照している。

select関数を win32_port.h か winSock2.h を参照させる違いはどこにあるのか?
pg_repackプロジェクトにあるput.hの「#include “c.h”」を消すと select関数の定義が参照されなくなるし、bool型の定義がなくなる。
pgbenchプロジェクトのpgbench.cには、bool型とselect関数があるが、参照エラーになっていない。

そこでput.hにある「#include “c.h”」から「#include “postgres_fe.h”」に切り替えたら、select関数の定義がwinSock2.hselect関数を参照するようになった。
そのため、下記のように修正した。

put.h
#ifndef WIN32
#include "c.h"
#else
#include "postgres_fe.h"
#endif

これで、postgres.libをリンクを外してもビルドが通るようになり、「pg_repack --help」や「pg_repack --version」の動作も確認できた。

versionの指定

「pg_repack –version」を実行したら、結果はunknownとなった。

これは、LinuxではMakefileにてREPACK_VERSIONにバージョン番号をセットしているが、Windowsでは REPACK_VERSIONの定義がないため

pg_repack.c
#ifdef REPACK_VERSION
/* macro trick to stringify a macro expansion */
#define xstr(s) str(s)
#define str(s) #s
const char *PROGRAM_VERSION = xstr(REPACK_VERSION);
#else
const char *PROGRAM_VERSION = "unknown";
#endif

プロジェクトのプロパティ C/C++のプリプロセッサに REPACK_VERSION=1.5.2を追加する。lib側も同様に設定すること

image.png

これで「pg_repack –version」を実行すると、結果は1.5.2となった。
pg_repack.exepg_repack.dllversionは一致させること

EnterCriticalSectionで例外エラー

簡易テストで動かそうと下記コマンドを実行するが何も反応がない

pg_repack -n -t wh_test.t1 -d wh_test -U wh_test

デバッグで追ってみると、EnterCriticalSectionを実行するとエラーが発生していた。
0x00007FFB838949E6 (ntdll.dll) で例外がスローされました (pg_repack.exe 内): 0xC0000005: 場所 0x0000000000000024 への書き込み中にアクセス違反が発生しました

put.c
#ifdef WIN32
	EnterCriticalSection(&cancelConnLock);
#endif

検索すると「YAHOO! Japan知恵袋」の質問に辿り着いた。

C++についてです。EnterCriticalSection()関数等を用いて排他制御を行おうとしていますが、EnterCriticalSectionで以下の例外が発生します。

InitializeCriticalSection() しましたか?

調査すると、InitializeCriticalSection()を参照しているinit_cancel_handler関数が、コメントアウトされており呼ばれていない。

on_before_exec関数とon_after_exec関数の2ヶ所をinit_cancel_handler関数を追記して修正すると例外エラーが発生しなくなった。

put.c
#ifdef WIN32
	init_cancel_handler();
	EnterCriticalSection(&cancelConnLock);
#endif

relhasoids列がないエラー

簡易テストで動かしてみるとrelhasoids列がない旨のエラーが表示された。

pg_repack -n -t wh_test.t1 -d wh_test -U wh_test

ERROR: pg_repack failed with error: ERROR:  column "relhasoids" does not exist
行 19:         CASE WHEN relhasoids

ISSUES にも質問が出ている。

SELECT * FROM repack.tables
SQL: COLUMN "relhasoids" does NOT exist

relhasoidsは、PostgreSQLのシステムカタログに存在するカラムであり、バージョン 11までは存在していたが、バージョン 12からは削除されている。

pg_repack.sql.inファイルにget_storage_param関数内でrelhasoidsを使用している。

pg_repack.sql.in
get_storage_param
 
    -- table oid
    SELECT 'oids = ' ||
        CASE WHEN relhasoids
            THEN 'true'
            ELSE 'false'
        END
    FROM pg_class

LinuxではMakefileファイル内にて PostgreSQL 12以降はrelhasoidsfalseに書き換えてpg_repack--1.5.2.sqlファイルとして出力しているため、エラーが発生しない。

Makefile
# It is not possible to create tables with OIDs on PostgreSQL 12 or later
ifeq ($(shell echo $$(($(INTVERSION) < 1200))),1)
    RELHASOIDS := relhasoids
else
    RELHASOIDS := false
endif
 
pg_repack--$(REPACK_VERSION).sql: pg_repack.sql.in
	sed 's,REPACK_VERSION,$(REPACK_VERSION),g' $< \
	| sed 's,relhasoids,$(RELHASOIDS),g'> $@;

PostgreSQL 12以降は最終的には下記に書き換わる。
※WindowsではMakefileを使用しないので、手動で書き換える必要がある。

pg_repack--1.5.2.sql
get_storage_param
 
    -- table oid
    SELECT 'oids = ' ||
        CASE WHEN false
            THEN 'true'
            ELSE 'false'
        END
    FROM pg_class

登録

ビルドが正常終了となり、pg_repack.exepg_repack.dllが生成できました。

pg_repackは、PostgreSQLの拡張モジュールなので、Create ExtensionでPostgreSQLに登録させせる必要があります。
msvc版は、Makefileにて自動で置換してくれるところまでは対応していないので、登録用に準備する必要があります。

準備

pg_repack.control

libディレクトリにある pg_repack.control.in ファイルを名称変更して「pg_repack.control」します。
内容の変更箇所は、REPACK_VERSIONをバージョン番号(1.5.2)に書き換えるだけです。

pg_repack.control
# pg_repack extension
comment = 'Reorganize tables in PostgreSQL databases with minimal locks'
default_version = '1.5.2'
module_pathname = '$libdir/pg_repack'
relocatable = false

pg_repack.sql

libディレクトリにある pg_repack.sql.in ファイルを名称変更して「pg_repack--1.5.2.sql」します。
内容の変更箇所は、REPACK_VERSIONをバージョン番号(1.5.2)に書き換えるだけです。

pg_repack--1.5.2.sql
CREATE FUNCTION repack.version_sql() RETURNS text AS
$$SELECT 'pg_repack 1.5.2'::text$$
LANGUAGE SQL IMMUTABLE STRICT;

上述の調査で説明した通り、PostgreSQL 12以降はrelhasoidsfalseに書き換えます。

pg_repack--1.5.2.sql
get_storage_param
 
    -- table oid
    SELECT 'oids = ' ||
        CASE WHEN false
            THEN 'true'
            ELSE 'false'
        END
    FROM pg_class

インストール

下記表の通りインストールに必要なファイルを手動でコピーします。

対象ファイル名 コピー先
pg_repack.exe <PostgreSQLのインストール先>\bin
pg_repack.dll <PostgreSQLのインストール先>\lib
pg_repack.control <PostgreSQLのインストール先>\share\extension
pg_repack--1.5.2.sql <PostgreSQLのインストール先>\share\extension

pg_repackの登録

PostgreSQLへのpg_bigmの登録はLinux環境と同じです。
公式のドキュメント pg_repack インストール

CREATE EXTENSION pg_repack;

SELECT extname, extversion
FROM pg_extension

-- extname	extversion
-- pg_repack	1.5.2

簡易テスト

下記サイトを参考に簡易テストを行います。
pg_repack (オンラインテーブル再編成ツール)

CREATE TABLE t1 (id INT PRIMARY KEY, val text);
 
SELECT pg_size_pretty(pg_total_relation_size('t1'));
 
INSERT INTO t1 VALUES(generate_series(1,10000),'AAA');
 
SELECT pg_size_pretty(pg_total_relation_size('t1'));
-- 632 kB
 
-- 5回ほど実行
UPDATE t1 SET VAL = 'BBB';
UPDATE t1 SET VAL = 'BBB';
UPDATE t1 SET VAL = 'BBB';
UPDATE t1 SET VAL = 'BBB';
UPDATE t1 SET VAL = 'BBB';
 
SELECT pg_size_pretty(pg_total_relation_size('t1'));
-- 2736 kB
 
-- バキュームしてもサイズは小さくならないことを確認
VACUUM t1;
 
SELECT pg_size_pretty(pg_total_relation_size('t1'));
-- 2736 kB

DB:wh_test、ユーザー:wh_test、スキーマ:wh_testにあるt1テーブルでpg_repackを実行、その際-eオプション(–echo)でサーバに送信するSQLを表示するようにしておきます。
pg_repack Usage(使い方 オプション)

pg_repack -n -t wh_test.t1 -d wh_test -U wh_test -e

LOG: (query) SET search_path TO pg_catalog, pg_temp, public
LOG: (query) SET search_path TO pg_catalog, pg_temp, public
LOG: (query) select repack.version(), repack.version_sql()
LOG: (query) SET statement_timeout = 0
LOG: (query) SET search_path = pg_catalog, pg_temp, public
LOG: (query) SET client_min_messages = warning
LOG: (query) SELECT r FROM (VALUES ($1, 'r')) AS given_t(r,kind) WHERE NOT EXISTS(  SELECT FROM repack.tables WHERE relid=to_regclass(given_t.r)) AND NOT EXISTS(  SELECT FROM pg_catalog.pg_class c WHERE c.oid=to_regclass(given_t.r) AND c.relkind = given_t.kind AND given_t.kind = 'p')
LOG:    (param:0) = wh_test.t1
LOG: (query) SELECT t.*, coalesce(v.tablespace, t.tablespace_orig) as tablespace_dest FROM repack.tables t,  (VALUES ($1::text)) as v (tablespace) WHERE (relid = $2::regclass) ORDER BY t.relname, t.schemaname
LOG:    (param:0) = (null)
LOG:    (param:1) = wh_test.t1
INFO: repacking table "wh_test.t1"
LOG: (query) SELECT pg_try_advisory_lock($1, CAST(-2147483648 + $2::bigint AS integer))
LOG:    (param:0) = 16185446
LOG:    (param:1) = 26443
LOG: (query) BEGIN ISOLATION LEVEL READ COMMITTED
LOG: (query) SAVEPOINT repack_sp1
LOG: (query) SET LOCAL lock_timeout = 100
LOG: (query) LOCK TABLE wh_test.t1 IN ACCESS EXCLUSIVE MODE
LOG: (query) RELEASE SAVEPOINT repack_sp1
LOG: (query) RESET lock_timeout
LOG: (query) SELECT pg_get_indexdef(indexrelid) FROM pg_index WHERE indrelid = $1 AND NOT indisvalid
LOG:    (param:0) = 26443
LOG: (query) SELECT indexrelid, repack.repack_indexdef(indexrelid, indrelid, $2, FALSE)  FROM pg_index WHERE indrelid = $1 AND indisvalid
LOG:    (param:0) = 26443
LOG:    (param:1) = (null)
LOG: (query) SELECT repack.conflicted_triggers($1)
LOG:    (param:0) = 26443
LOG: (query) SELECT repack.create_index_type(26448,26443)
LOG: (query) SELECT repack.create_log_table(26443)
LOG: (query) CREATE TRIGGER repack_trigger AFTER INSERT OR DELETE OR UPDATE ON wh_test.t1 FOR EACH ROW EXECUTE PROCEDURE repack.repack_trigger('id')
LOG: (query) ALTER TABLE wh_test.t1 ENABLE ALWAYS TRIGGER repack_trigger
LOG: (query) SELECT repack.disable_autovacuum('repack.log_26443')
LOG: (query) BEGIN ISOLATION LEVEL READ COMMITTED
LOG: (query) SELECT pg_backend_pid()
LOG: (query) SELECT pid FROM pg_locks WHERE locktype = 'relation' AND granted = false AND relation = 26443 AND mode = 'AccessExclusiveLock' AND pid <> pg_backend_pid()
LOG: (query) COMMIT
LOG: (query) BEGIN ISOLATION LEVEL SERIALIZABLE
LOG: (query) SELECT set_config('work_mem', current_setting('maintenance_work_mem'), true)
LOG: (query) SET LOCAL synchronize_seqscans = off
LOG: (query) SELECT coalesce(array_agg(l.virtualtransaction), '{}')   FROM pg_locks AS l   LEFT JOIN pg_stat_activity AS a     ON l.pid = a.pid   LEFT JOIN pg_database AS d     ON a.datid = d.oid   WHERE l.locktype = 'virtualxid'   AND l.pid NOT IN (pg_backend_pid(), $1)   AND (l.virtualxid, l.virtualtransaction) <> ('1/1', '-1/0')   AND (a.application_name IS NULL OR a.application_name <> $2)  AND a.query !~* E'^\\s*vacuum\\s+'   AND a.query !~ E'^autovacuum: '   AND ((d.datname IS NULL OR d.datname = current_database()) OR l.database = 0)
LOG:    (param:0) = 22136
LOG:    (param:1) = pg_repack
LOG: (query) DELETE FROM repack.log_26443
LOG: (query) SAVEPOINT repack_sp1
LOG: (query) SELECT pid FROM pg_locks WHERE locktype = 'relation' AND granted = false AND relation = 26443 AND mode = 'AccessExclusiveLock' AND pid <> pg_backend_pid()
LOG: (query) SET LOCAL lock_timeout = 100
LOG: (query) LOCK TABLE wh_test.t1 IN ACCESS SHARE MODE
LOG: (query) RELEASE SAVEPOINT repack_sp1
LOG: (query) RESET lock_timeout
LOG: (query) SELECT repack.create_table($1, $2)
LOG:    (param:0) = 26443
LOG:    (param:1) = pg_default
LOG: (query) INSERT INTO repack.table_26443 SELECT id,val FROM ONLY wh_test.t1
LOG: (query) SELECT repack.disable_autovacuum('repack.table_26443')
LOG: (query) COMMIT
LOG: (query) SELECT 'repack.table_26443'::regclass::oid
LOG: (query) CREATE UNIQUE INDEX index_26448 ON repack.table_26443 USING btree (id)
LOG: (query) SELECT repack.repack_apply($1, $2, $3, $4, $5, $6)
LOG:    (param:0) = SELECT * FROM repack.log_26443 ORDER BY id LIMIT $1
LOG:    (param:1) = INSERT INTO repack.table_26443 VALUES ($1.*)
LOG:    (param:2) = DELETE FROM repack.table_26443 WHERE (id) = ($1.id)
LOG:    (param:3) = UPDATE repack.table_26443 SET (id, val) = ($2.id, $2.val) WHERE (id) = ($1.id)
LOG:    (param:4) = DELETE FROM repack.log_26443 WHERE id IN (
LOG:    (param:5) = 1000
LOG: (query) SELECT pid FROM pg_locks WHERE locktype = 'virtualxid' AND pid <> pg_backend_pid() AND virtualtransaction = ANY($1)
LOG:    (param:0) = {}
LOG: (query) SAVEPOINT repack_sp1
LOG: (query) SET LOCAL lock_timeout = 100
LOG: (query) LOCK TABLE wh_test.t1 IN ACCESS EXCLUSIVE MODE
LOG: (query) RELEASE SAVEPOINT repack_sp1
LOG: (query) RESET lock_timeout
LOG: (query) SAVEPOINT repack_sp1
LOG: (query) SET LOCAL lock_timeout = 100
LOG: (query) LOCK TABLE repack.table_26443 IN ACCESS EXCLUSIVE MODE
LOG: (query) RELEASE SAVEPOINT repack_sp1
LOG: (query) RESET lock_timeout
LOG: (query) SELECT repack.repack_apply($1, $2, $3, $4, $5, $6)
LOG:    (param:0) = SELECT * FROM repack.log_26443 ORDER BY id LIMIT $1
LOG:    (param:1) = INSERT INTO repack.table_26443 VALUES ($1.*)
LOG:    (param:2) = DELETE FROM repack.table_26443 WHERE (id) = ($1.id)
LOG:    (param:3) = UPDATE repack.table_26443 SET (id, val) = ($2.id, $2.val) WHERE (id) = ($1.id)
LOG:    (param:4) = DELETE FROM repack.log_26443 WHERE id IN (
LOG:    (param:5) = 0
LOG: (query) SELECT repack.repack_swap($1)
LOG:    (param:0) = 26443
LOG: (query) COMMIT
LOG: (query) BEGIN ISOLATION LEVEL READ COMMITTED
LOG: (query) SAVEPOINT repack_sp1
LOG: (query) SET LOCAL lock_timeout = 100
LOG: (query) LOCK TABLE wh_test.t1 IN ACCESS EXCLUSIVE MODE
LOG: (query) RELEASE SAVEPOINT repack_sp1
LOG: (query) RESET lock_timeout
LOG: (query) SELECT repack.repack_drop($1, $2)
LOG:    (param:0) = 26443
LOG:    (param:1) = 4
LOG: (query) COMMIT
LOG: (query) BEGIN ISOLATION LEVEL READ COMMITTED
LOG: (query) ANALYZE wh_test.t1
LOG: (query) COMMIT
LOG: (query) SELECT pg_advisory_unlock($1, CAST(-2147483648 + $2::bigint AS integer))
LOG:    (param:0) = 16185446
LOG:    (param:1) = 26443

2736kBから632kBまでサイズが小さくなった。

結果
SELECT pg_size_pretty(pg_total_relation_size('t1'));
-- 632 kB

最後に

2018年3月に挑戦するもダメだったが、7年を経てようやく使用できるようになった。

懸念点でとして簡易テストだとデバッグ出力してみても、select関数は通っていなかった。select関数を通すためには何らかの条件が必要である。

本田技研工業が開発した二足歩行ロボットのアシモが登場する前は、二足歩行ロボットはまだ先の話と思われていましたが、アシモの成功が「できる」と示したことで、次々と二足歩行ロボットが生まれました。

同じように、SRA OSSがpg_repackの Windows版を公開しているのを見て、「必ずできる方法がある」と確信。いくつかの困難を超えて、Windows対応を実現しました。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?