PGlite splices a fixed list of -c settings into every start and never reads postgresql.conf or postgresql.auto.conf on restart (ALTER SYSTEM writes to the wrong directory and pg_reload_conf is a no-op without a postmaster), so the only way to lower max_wal_size is that list. With 128MB, a CHECKPOINT trims pg_wal from 405 MB to 134 MB locally instead of letting it grow to the 1 GB default. |
||
|---|---|---|
| .. | ||
| build.sh.diff | ||
| clear_error_portals.diff | ||
| Dockerfile-arm64.diff | ||
| max_wal_size.diff | ||
| message_context_reset.diff | ||
| README.md | ||
| upstream-build.sh.diff | ||
| wasi-c.diff | ||
| wasm-build.sh.diff | ||
Rebuilding PGlite for WASI with optimization (2026-09-04)
Source of the build: https://github.com/roastedroot/pglite4j wasm-build/ (self-contained Docker build of
electric-sql/postgres-pglite, branch REL_17_5_WASM-pglite). Clone it to /tmp/pglite4j and apply the diffs here:
Dockerfile-arm64.diff— the image downloads x86_64 toolchains; on Apple Silicon Docker (arm64) clang cannot run. Swaps wasi-sdk, binaryen, wasmtime, wizer and wasi-vfs for their arm64/aarch64 assets.wasm-build.sh.diff— upstream overwritesCC_PGLITE, and PostgreSQL's configure does not add its default-O2whenCFLAGSis given, so the stock build is-O0. Injects${PGLITE_OPT:--O2}at the front ofCC_PGLITE.build.sh.diff— passesPGLITE_OPT(andPGLITE_PIC, unused so far) into the container.wasi-c.diff— makes the forced-fPICin the compiler wrapper overridable (PGLITE_PIC). NOTE: the matching-fpicinpostgresql-src/wasm-build/build-pgcore.sh(cloned at build time) is not covered by a diff here; the-fno-picexperiment did not take effect and is unmeasured.max_wal_size.diff— goes inpatches/postgresql-pglite/assrc-include-port-wasm_common.h.zz-max-wal-size.diff(it must sort after pglite4j's ownwasm_common.hpatch). PGlite splices a fixed list of-csettings into every start (WASM_PGOPTS), never readspostgresql.conf/postgresql.auto.confon restart, andpg_reload_conf()is a no-op without a postmaster, soALTER SYSTEMcannot change anything. Addingmax_wal_size=128MBto that list capspg_walat ~130 MB instead of 1 GB once aCHECKPOINTruns.message_context_reset.diff— goes inpatches/pglite-wasm/. The fork stubsMemoryContextResetAndDeleteChildren(removed in PostgreSQL 17) as an empty macro, soMessageContextis never reset and every wire message leaks its parse/plan state (~10 KB per statement; a library scan reached 15 GB). The patch maps it toMemoryContextReset.clear_error_portals.diff— goes inpatches/pglite-wasm/. A PostgreSQL ERROR traps the wasm instance, which skips every PG_CATCH, so the active portal is never marked failed and the next statement fails with "cannot drop active portal". WrappingAbortCurrentTransaction()inclear_error()withshmem_exit_inprogress = truemakesAtAbort_Portalsmark it failed.
Build (about 5 minutes on 16 cores):
cd /tmp/pglite4j/wasm-build
CLEAN=true DEBUG=false PGLITE_OPT=-O2 ./build.sh
xz -dc output/sdk-dist/pglite-wasi.tar.xz | gzip -1 > pglite-wasi-O2-fix.tar.gz
DEBUG=false matters: DEBUG=true (the default) builds single-threaded and keeps debug output.
Output layout ("v2" in db/pglite): tmp/pglite/bin/pglite.wasi with the share files embedded through wasi-vfs and a
pre-initialized cluster in pgdata/ produced by wizer, so initdb never runs at startup (1.8 s instead of 13 s).
Preopens must be /tmp, /pgdata, /dev in that order; PGDATA=/pgdata; do not call _start/pgl_initdb/
pgl_backend; call interactive_write(0) once; on a trap check pgl_check_error() then clear_error(),
interactive_write(-1), one more interactive_one().
Result archive used by the tests and benchmark: tmp/pglite-wasi-O2-fix.tar.gz (6.7 MB; wasm 17.3 MB).
-fpic: measured, no effect (do not redo)
PGLITE_PIC must be patched into three places, and the one that matters is easy to miss:
wasm-build/build-pgcore.sh at the workspace root is the copy that runs — postgresql-src/wasm-build/build-pgcore.sh
is not. (Same for wasm-build.sh.) With all three patched, -fno-pic reaches configure and every compile.
Result: no measurable difference on any query, and the final wasm code section changes by 96 bytes out of 5.35 MB.
-fPIC does change object files, but wasm-ld relaxes the relocations back to direct addressing when producing a
static executable. Keep the upstream default.
Without the wizer snapshot (the durable build; 2026-09-04)
The snapshot makes reopening a data dir unsafe: it carries the pristine cluster's transaction counter and WAL
position, and pgl_backend traps if asked to restart on a live snapshot. Build with PGLITE_WIZER=0 instead:
cd /tmp/pglite4j/wasm-build
DEBUG=false PGLITE_OPT=-O2 PGLITE_WIZER=0 ./build.sh
# package with a marker the bridge recognizes:
tar xJf output/sdk-dist/pglite-wasi.tar.xz -C /some/dir && touch /some/dir/tmp/pglite/NOSNAPSHOT
(cd /some/dir && tar czf pglite-wasi.tar.gz tmp pgdata)
Two quirks. The initdb pre-population step (wasmtime run --env INITDB_ONLY=1) failed on a clean output/pgdata
with "cannot make new WAL entries during recovery"; restoring a pgdata/ from an earlier good archive into
output/pgdata before building sidesteps it (the build then skips pre-init). And the cluster only has template1;
the bridge connects to it explicitly. build.sh.diff now wraps only the wizer invocation in PGLITE_WIZER.