はじめに
PostgreSQLの拡張機能pg_repack
は、テーブルの再編成を行うツールです。
別件のトラブルでpg_statsinfo
について調べていると、Windows版のpg_repack
バイナリ配布しているという記事を見つけました。
SRA OSSとしてPostgreSQL拡張のWindowsバイナリ提供を開始しているが、今のところはサポート加入者のみで、一般配布は計画中とのこと。
SRA OSSで継続バイナリリリース
- pg_hint_plan 対応済
- pgaudit
- pg_repack
- pg_dbms_stats
※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_plan
やpgaudit
と違い、pg_repack
は、exe と dll の2つプロジェクト構成となっているためプロジェクト開始からの説明ではなく、重要な部分だけの説明に留めます。
pg_repackをフォーク
pg_repack
をフォークして、msvcフォルダにあるpg_repack.2022.sln
とbin.2022.vcxproj
とlib.2022.vcxproj
を作成しました。
環境変数
Windowsの環境変数にて、PostgreSQL64
を設定します。
筆者の場合は下記の通りに設定しました。
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_repack
のreadme.txt
内にwingdi.h
を修正の指示がありますので、これを行います。
修正するファイルwingdi.h
がどこにあるのか?
筆者の場合、Visual Studio 2022のbinプロジェクトのプロパティページのVC++ディレクトリにある全般のインクルードディレクトリ$(VC_IncludePath);$(WindowsSDK_IncludePath);
と記載されているので、評価された値を見ます。
下記フォルダにあるwingdi.h
を修正しました。
C:¥Program Files (x86)¥Windows Kits¥10¥Include¥10.0.26100.0¥um
先頭の-
行を削除して、+
行を追加します。
#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
で検索すると見つかるのは、次コードのように定義されているからです。
#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_repack
のselect
関数は、「win32_port.h」→「socket.c」のpgwin32_select
関数を参照している。同じ exe のツールであるpgbench
やpg_recvlogical
のselect
関数は、「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.h
のselect
関数を参照するようになった。
そのため、下記のように修正した。
#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
の定義がないため
#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側も同様に設定すること
これで「pg_repack –version」を実行すると、結果は1.5.2
となった。
※pg_repack.exe
とpg_repack.dll
のversion
は一致させること
EnterCriticalSectionで例外エラー
簡易テストで動かそうと下記コマンドを実行するが何も反応がない
pg_repack -n -t wh_test.t1 -d wh_test -U wh_test
デバッグで追ってみると、EnterCriticalSection
を実行するとエラーが発生していた。
0x00007FFB838949E6 (ntdll.dll) で例外がスローされました (pg_repack.exe 内): 0xC0000005: 場所 0x0000000000000024 への書き込み中にアクセス違反が発生しました
#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
関数を追記して修正すると例外エラーが発生しなくなった。
#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 にも質問が出ている。
- PostgreSQL 12: column "relhasoids" does not exist #229
- pg_repack error:column "relhasoids" does not exist #355
SELECT * FROM repack.tables
SQL: COLUMN "relhasoids" does NOT exist
relhasoids
は、PostgreSQLのシステムカタログに存在するカラムであり、バージョン 11までは存在していたが、バージョン 12からは削除されている。
pg_repack.sql.in
ファイルにget_storage_param
関数内でrelhasoids
を使用している。
get_storage_param
-- table oid
SELECT 'oids = ' ||
CASE WHEN relhasoids
THEN 'true'
ELSE 'false'
END
FROM pg_class
LinuxではMakefile
ファイル内にて PostgreSQL 12以降はrelhasoids
をfalse
に書き換えてpg_repack--1.5.2.sql
ファイルとして出力しているため、エラーが発生しない。
# 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
を使用しないので、手動で書き換える必要がある。
get_storage_param
-- table oid
SELECT 'oids = ' ||
CASE WHEN false
THEN 'true'
ELSE 'false'
END
FROM pg_class
登録
ビルドが正常終了となり、pg_repack.exe
とpg_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 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)に書き換えるだけです。
CREATE FUNCTION repack.version_sql() RETURNS text AS
$$SELECT 'pg_repack 1.5.2'::text$$
LANGUAGE SQL IMMUTABLE STRICT;
上述の調査で説明した通り、PostgreSQL 12以降はrelhasoids
をfalse
に書き換えます。
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対応を実現しました。