====== pg_repack ====== ===== ビルドできても例外エラー ===== 2021年の質問:Windowsでpg_repackをビルドしても例外エラーになってしまう。\\ [[https://dba.stackexchange.com/questions/274372/pg-repack-binaries-under-windows|pg_repack binaries under windows]] 下記サイトは2018年のもので静的なpostgres.libを作成して動作したとの報告があったが、現在は postgres.lib が提供されており、上記サイトでも postgres.lib をリンクしても、例外エラーで動作しないことから問題は別物と考える。 * [[https://stackoverflow.com/questions/45957125/dynamic-linking-to-a-exe-instead-of-dll-cause-crash-on-windows-10|Dynamic linking to a .exe instead of .dll cause crash on windows 10]] * [[https://stackoverflow.com/questions/45819855/call-of-statically-link-function-crash-everytimes-on-windows-8-10-but-not-7/45827816|Call of statically link function crash everytimes on windows 8/10 but not 7]] この問題を解決して、Windowsでもpg_repackを使えるようにしたい。 ===== 調査 ===== pg_repack のデバッグでは、set_pglocale_pgservice(argv[0], "pgscripts"); を実行すると例外エラーとなる。\\ vaccumedb のデバッグでは、set_pglocale_pgservice(argv[0], "pgscripts"); を使用していても正常に動作する。違いとしてvaccumedb では、postgres.lib をリンクしていない。 pg_repack以外で exe形式で postgres.lib をリンクしたものは他にない。 ^関数^モジュール^ソース元^ |set_pglocale_pgservice|libpgcommon.lib| | |setlocale(pgwin32_setlocale)|libpgport.lib| | |select(pgwin32_select)|postgres.lib|backend¥port¥win32¥socket.c| |pgwin32_dispatch_queued_signals|postgres.lib|backend¥port¥win32¥signal.c| |errstart|postgres.lib|backend\utils\error\elog.c| pg_repack を pgwin32_selectで検索しても見つからないが、select で検索すると見つかる。\\ ret = select(max_fd + 1, &input_mask, NULL, NULL, &timeout); #define setlocale(a,b) pgwin32_setlocale(a,b) #define select(n, r, w, e, timeout) pgwin32_select(n, r, w, e, timeout) pg_hello を作成して postgres.lib をリンクしないで pg_repack.c のソースコードに徐々に近づけた。\\ そして、rebuild_indexes で pgwin32_select が参照している旨のLINKエラーのみになった。\\ そこで、pg_repack.c を、pg_hello.c に名前を変更してビルド、同様に rebuild_indexes で、pgwin32_select が参照している旨のLINKエラーのみになった。 下記のコードをコメントアウトすると、LINKエラーは出なくなる。 ret = select(max_fd + 1, &input_mask, NULL, NULL, &timeout); ==== 必要なobjファイルだけをリンク ==== socket.obj、singal.obj をリンクすると、7つの未解決エラーが出る。 1>socket.obj : error LNK2019: 未解決の外部シンボル errstart が関数 pgwin32_select で参照されました 1>signal.obj : error LNK2001: 外部シンボル errstart は未解決です 1>socket.obj : error LNK2019: 未解決の外部シンボル errfinish が関数 pgwin32_select で参照されました 1>signal.obj : error LNK2001: 外部シンボル errfinish は未解決です 1>socket.obj : error LNK2019: 未解決の外部シンボル errmsg_internal が関数 pgwin32_select で参照されました 1>signal.obj : error LNK2001: 外部シンボル errmsg_internal は未解決です 1>signal.obj : error LNK2019: 未解決の外部シンボル write_stderr が関数 pg_signal_thread で参照されました このぐらいならと elog.obj を追加すると多くの未解決エラーが出る。同じように追加するとどんどん別のobjファイルから未解決エラーが出る。postgres.libとリンクするのと変わらなくなるため、この方法での解決はやめた。 ^関数名^モジュール名^オブジェクト名| |ExceptionalCondition|asset.c|assert.obj| |pg_codepage_to_encoding|chklocale.c|chklocale.obj| |MemoryContextStrdup|mcxt.c|mcxt.obj| |GetTopTransactionIdIfAny|xact.c|xact.obj| |GetMessageEncoding|mbutils.c|mbutils.obj| |pgwin32_message_to_UTF16|mbutils.c|mbutils.obj| |pq_putmessage_v2|pqcomm.c|pqcomm.obj| |pq_beginmessage|pdformat.c|pdformat.obj| |pq_endmessage|pqformat.c|pqformat.obj| |pq_sendstring|pqformat.c|pqformat.obj| |pq_send_ascii_string|pdformat.c|pdformat.obj| |pg_localtime|localtime.c|localtime.obj| |pg_strftime|strftime.c|strftime.obj| |ProcessInterrupts|postgres,c|postgres,obj| |GetBackendTypeDesc|miscinit.c|miscinit.obj| |pgstat_get_my_query_id|backend_status.c|backend_status.obj| |write_syslogger_file|syslogger.c|syslogger.obj| |proc_exit|ipc.c|ipc.obj| |MemoryContextReset|mcxt.c|mcxt.obj| |get_ps_display|ps_status.c|ps_status.obj| ==== 必要な関数だけを作成 ==== socket.c と signal.c の必要関数を抜き出し、新しい socket.c を作成して、pg_repack に socket.c を追加 次項目の winSock2.h にある Select関数を使用して問題ないなら、新しい socket.c は使用しない方針とする。 ==== select関数の参照先の違い ==== pg_repack の select関数 は、win32_port.h → socket.c の pgwin32_select関数を参照している。\\ pgbench や pg_recvlogical の select関数 は、winSock2.h の select関数を参照している。 select関数 を win32_port.h か winSock2.h を参照させる違いはどこにあるのか? put.h にある #include "c.h" を消すと select関数の定義の参照されなくなるし bool型なども定義がなくなる。\\ pgbench.c には、bool型とselect関数があるが、参照エラーになっていない。 put.h にある、#include "postgres_fe.h" に切り替えたら、select関数の定義がwinSock2.h の select関数を参照するようになった。 ==== versionの指定 ==== 「pg_repack --version」を実行したら、unknown となった。 Makefileでは、コンパイル時にREPACK_VERSIONにバージョン番号をセットしているが、Windowsでは REPACK_VERSION を指定していないため、プロジェクトのプロパティ C/C++のプリプロセッサに REPACK_VERSION=1.4.7 を追加する。\\ lib側も同様に設定すること #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 ==== EnterCriticalSectionで例外エラー ==== 0x00007FFB838949E6 (ntdll.dll) で例外がスローされました (pg_repack.exe 内): 0xC0000005: 場所 0x0000000000000024 への書き込み中にアクセス違反が発生しました >[[https://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q10133861605|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列がないエラー ==== * [[https://github.com/reorg/pg_repack/issues/229|PostgreSQL 12: column "relhasoids" does not exist #229]] * [[https://github.com/reorg/pg_repack/issues/355|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 Makefileファイル内にて PostgreSQL12以降は relhasoids を false に書き換えている。 # 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 ===== 簡易テスト ===== ようやく動作させることが出来るようになったので、簡易テスト [[https://www.sraoss.co.jp/tech-blog/pgsql/pg_repack/|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')); -- 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を表示するようにしておきます。 [[https://reorg.github.io/pg_repack/|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)) AS given_t(r) WHERE NOT EXISTS( SELECT FROM repack.tables WHERE relid=to_regclass(given_t.r) ) LOG: (param:0) = wh_test.t1 LOG: (query) SELECT t.*, coalesce(v.tablespace, t.tablespace_orig) as tablespace_dest FROM repack.tables t, (VALUES (quote_ident($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) = 16738 LOG: (query) BEGIN ISOLATION LEVEL READ COMMITTED LOG: (query) SET LOCAL statement_timeout = 100 LOG: (query) LOCK TABLE wh_test.t1 IN ACCESS EXCLUSIVE MODE LOG: (query) RESET statement_timeout LOG: (query) SELECT pg_get_indexdef(indexrelid) FROM pg_index WHERE indrelid = $1 AND NOT indisvalid LOG: (param:0) = 16738 LOG: (query) SELECT indexrelid, repack.repack_indexdef(indexrelid, indrelid, $2, FALSE) FROM pg_index WHERE indrelid = $1 AND indisvalid LOG: (param:0) = 16738 LOG: (param:1) = (null) LOG: (query) SELECT repack.conflicted_triggers($1) LOG: (param:0) = 16738 LOG: (query) CREATE TYPE repack.pk_16738 AS (id integer) LOG: (query) CREATE TABLE repack.log_16738 (id bigserial PRIMARY KEY, pk repack.pk_16738, row wh_test.t1) LOG: (query) CREATE TRIGGER repack_trigger AFTER INSERT OR DELETE OR UPDATE ON wh_test.t1 FOR EACH ROW EXECUTE PROCEDURE repack.repack_trigger('INSERT INTO repack.log_16738(pk, row) VALUES( CASE WHEN $1 IS NULL THEN NULL ELSE (ROW($1.id)::repack.pk_16738) END, $2)') LOG: (query) ALTER TABLE wh_test.t1 ENABLE ALWAYS TRIGGER repack_trigger LOG: (query) SELECT repack.disable_autovacuum('repack.log_16738') 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 = 16738 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) = 6804 LOG: (param:1) = pg_repack LOG: (query) DELETE FROM repack.log_16738 LOG: (query) SELECT pid FROM pg_locks WHERE locktype = 'relation' AND granted = false AND relation = 16738 AND mode = 'AccessExclusiveLock' AND pid <> pg_backend_pid() LOG: (query) SET LOCAL statement_timeout = 100 LOG: (query) LOCK TABLE wh_test.t1 IN ACCESS SHARE MODE LOG: (query) RESET statement_timeout LOG: (query) CREATE TABLE repack.table_16738 WITH (oids = false) TABLESPACE pg_default AS SELECT id,val FROM ONLY wh_test.t1 WITH NO DATA LOG: (query) INSERT INTO repack.table_16738 SELECT id,val FROM ONLY wh_test.t1 LOG: (query) SELECT repack.disable_autovacuum('repack.table_16738') LOG: (query) COMMIT LOG: (query) CREATE UNIQUE INDEX index_16743 ON repack.table_16738 USING btree (id) LOG: (query) SELECT repack.repack_apply($1, $2, $3, $4, $5, $6) LOG: (param:0) = SELECT * FROM repack.log_16738 ORDER BY id LIMIT $1 LOG: (param:1) = INSERT INTO repack.table_16738 VALUES ($1.*) LOG: (param:2) = DELETE FROM repack.table_16738 WHERE (id) = ($1.id) LOG: (param:3) = UPDATE repack.table_16738 SET (id, val) = ($2.id, $2.val) WHERE (id) = ($1.id) LOG: (param:4) = DELETE FROM repack.log_16738 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 statement_timeout = 100 LOG: (query) LOCK TABLE wh_test.t1 IN ACCESS EXCLUSIVE MODE LOG: (query) RESET statement_timeout LOG: (query) SELECT repack.repack_apply($1, $2, $3, $4, $5, $6) LOG: (param:0) = SELECT * FROM repack.log_16738 ORDER BY id LIMIT $1 LOG: (param:1) = INSERT INTO repack.table_16738 VALUES ($1.*) LOG: (param:2) = DELETE FROM repack.table_16738 WHERE (id) = ($1.id) LOG: (param:3) = UPDATE repack.table_16738 SET (id, val) = ($2.id, $2.val) WHERE (id) = ($1.id) LOG: (param:4) = DELETE FROM repack.log_16738 WHERE id IN ( LOG: (param:5) = 0 LOG: (query) SELECT repack.repack_swap($1) LOG: (param:0) = 16738 LOG: (query) COMMIT LOG: (query) BEGIN ISOLATION LEVEL READ COMMITTED LOG: (query) SAVEPOINT repack_sp1 LOG: (query) SET LOCAL statement_timeout = 100 LOG: (query) LOCK TABLE wh_test.t1 IN ACCESS EXCLUSIVE MODE LOG: (query) RESET statement_timeout LOG: (query) SELECT repack.repack_drop($1, $2) LOG: (param:0) = 16738 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) = 16738 2736 kB から 632 kBまでサイズが小さくなった。 SELECT pg_size_pretty(pg_total_relation_size('t1')); -- 632 kB