Contents
- Upgrading
- 1. Preflight
- 2. Install and update
- What 4.2 changes
- 4.2 to 4.3
- What 4.3 deprecates and 4.4 removes
- 3. A newer PostgreSQL major
- 4. An older release
- Defaults changed in 3.3
- 5. First release that reads each format
- Schema sift is open to PUBLIC since 3.3
- Known issue of 3.2.0, 3.2.1 and 3.2.2: a role cannot drop anything
- Known issue of 4.0.0 to 4.1.0: aggregates could count old versions of rows
- What is tested
Upgrading
What to run, in order, to move a database to a newer pg_sift, to a newer PostgreSQL major, or back to an older pg_sift. On-disk formats and the first release that reads each: FORMATS.md.
| Change | What happens to the files | Steps |
|---|---|---|
| a newer pg_sift (patch or minor) | the new library reads the files of the older ones; it writes its own format from the next write on | preflight, install and update |
| 3.2.1 to 3.2.2 | library only: the extension SQL stays at 3.2, no format changes | install the files and restart the server (an open session keeps the old library, and pg_sift in shared_preload_libraries is read at start only); no ALTER EXTENSION pg_sift UPDATE, no REINDEX; going back to 3.2.1 needs nothing (notes) |
| 4.0.2 to 4.0.3 | the library and two functions, sift.index_choices and sift.help: the extension SQL goes from 4.0 to 4.0.3, no format changes |
install the files, restart the server (a standby too), then ALTER EXTENSION pg_sift UPDATE in each database; no REINDEX; going back: an older release (notes) |
| 4.0.3 to 4.0.4 | the library only; the extension SQL stays at 4.0.3. The log header of a sift_col index with track_changes gets one more flag (additive: an older library reads it as an incomplete log and checks the heap) |
install the files, restart the server (a standby too); no ALTER EXTENSION UPDATE, no REINDEX: an index that 4.0.0 to 4.0.3 left with wrong counts is re-baselined by the next VACUUM of its table, by the first read, or by the maintenance worker (sift.col_tracking: complete true, heap_free false until then); going back: an older release |
| 4.0.4 to 4.0.5 | library only: the extension SQL stays at 4.0.3, no format changes; two wrong answers of the aggregate scan are fixed | install the files and restart the server (a standby too; an open session keeps the old library); no ALTER EXTENSION pg_sift UPDATE, no REINDEX; going back needs nothing first (notes) |
| 4.0.3 to 4.1.0 | the library and the planner (new scans and costs; four new settings, sift.agg_probe_rewrite, sift.agg_probe_direct, sift.col_build_key_ranges, sift.col_auto_merge_pass_mb): the extension SQL goes from 4.0.3 to 4.1 with no change of its objects, no format changes |
install the files, restart the server (a standby too), then ALTER EXTENSION pg_sift UPDATE in each database; no REINDEX (a REINDEX of a key_cut index builds it by key ranges); from 4.0 to 4.0.2 the update goes through 4.0.3 by itself; going back: an older release (notes) |
| 4.1.0 to 4.1.1 | library only: the extension SQL stays at 4.1, no format changes; one new value of an option, track_changes = 'ctid' (never chosen by auto) |
install the files and restart the server (a standby too; an open session keeps the old library); no ALTER EXTENSION pg_sift UPDATE, no REINDEX; going back to 4.1.0: first ALTER INDEX <index> SET (track_changes = 'auto') on every index you set to 'ctid' (4.1.0 does not know its row trigger and fails UPDATE and DELETE of the table with sift.col_track_old_rows is the statement trigger of an index option), then nothing else (notes) |
| 4.1.1 to 4.1.2 | library only: the extension SQL stays at 4.1, no format changes; the log of a sift_col index with track_changes that an earlier release wrote is re-checked and re-baselined on its own |
install the files and restart the server (a standby too; an open session keeps the old library); no ALTER EXTENSION pg_sift UPDATE, no REINDEX, no ALTER INDEX (after pg_upgrade, once for each index: see the section below); going back to 4.1.1 needs nothing first (notes) |
| 4.1.2 to 4.1.3 | library only: the extension SQL stays at 4.1, no format changes; REINDEX INDEX CONCURRENTLY of a sift_col index with track_changes no longer takes ACCESS EXCLUSIVE on the table |
install the files and restart the server (a standby too; an open session keeps the old library); no ALTER EXTENSION pg_sift UPDATE, no REINDEX; going back to 4.1.2 needs nothing first (notes) |
| 4.1.3 to 4.1.4 | library only: the extension SQL stays at 4.1, no format changes; two wrong answers of the aggregate scan are fixed | install the files and restart the server (a standby too; an open session keeps the old library); no ALTER EXTENSION pg_sift UPDATE, no REINDEX; going back needs nothing first (notes) |
| a newer pg_sift from 4.1 to 4.2 | seven changes of files, plans, a setting and one new SQL function | what 4.2 changes |
| 4.2.0 to 4.2.1 | the library and one function, sift.col_track_convert(): the extension SQL goes from 4.2 to 4.2.1, no format changes; DROP INDEX CONCURRENTLY of a sift_col index with track_changes works (the index depends on its two triggers, where they depended on it) |
install the files, restart the server (a standby too), then ALTER EXTENSION pg_sift UPDATE in each database: it converts the indexes of the database (it takes SHARE UPDATE EXCLUSIVE on each table that has a tracked index, which waits for a DROP INDEX, REINDEX or VACUUM of the table that is running and lets the writers go on), no DDL on a tracked sift_col index needs to wait for it (a CREATE INDEX, REINDEX, ALTER INDEX or DROP INDEX before the update behaves as after it), only the DROP INDEX CONCURRENTLY of an index made by 4.2.0 or earlier does, and until it runs the concurrent drop of an index made by 4.2.0 or earlier still fails (its triggers are found, so reads, writes, REINDEX, ALTER INDEX and a plain DROP INDEX work as before; an index made before the update is dropped with its triggers); no REINDEX is needed to drop; going back to the 4.2.0 library after the update leaves such an index with triggers that library does not find (REINDEX there makes them again), and ALTER EXTENSION pg_sift UPDATE TO '4.2' does not undo the conversion (its script is empty: the index keeps depending on its triggers, and a plain DROP INDEX with the 4.2.0 library then leaves the triggers behind, which fail the writes of the table: drop them with DROP TRIGGER after the index) |
| 4.2.1 to 4.2.2 | the library and one function, sift.table_find_stale_versions(): the extension SQL goes from 4.2.1 to 4.2.2, no format changes; a VACUUM of a sift_table that waited for the maintenance lock no longer makes old versions of updated rows, or deleted rows, visible again (4.0.0 to 4.2.1) |
install the files, restart the server (a standby too), then ALTER EXTENSION pg_sift UPDATE in each database; no REINDEX; the update does not repair rows that already came back: run sift.table_find_stale_versions('<table>') on each updated sift_table and follow the notes; a database at 4.2 goes through 4.2.1 by itself; going back to 4.2.1: install its files, restart, ALTER EXTENSION pg_sift UPDATE TO '4.2.1' (the function stays, the cause returns) (notes) |
| 4.2 to 4.3 (also from 4.2.1 and 4.2.2) | a new minor: the library, the planner, connected indexes, a write buffer of references, seekable order chunks (column format minor 44, which 4.2 and older refuse), three deprecated options and new functions: the extension SQL goes from 4.2 to 4.3 | install the files, restart the server (a standby too), then ALTER EXTENSION pg_sift UPDATE in each database; no REINDEX is needed; update every node that reads an index (the standbys first) before the primary writes one; going back needs a setting, REINDEX and the 4.2 files: 4.2 to 4.3 (notes) |
| 4.3 to 4.3.1 | the library and one statement of the extension SQL (sift.table_option_values gets its replica identity: with a FOR ALL TABLES publication, 4.2.0 to 4.3.0 failed every DROP INDEX and DROP TABLE of the database, ALTER EXTENSION pg_sift UPDATE repairs it), no new object, no format changes; a read inside a statement that deletes rows (an AFTER ROW trigger, an AFTER STATEMENT trigger that fires before the index’s own, a function the statement calls) no longer counts the rows the statement deleted, through a sift_col index with track_changes (4.0.0 to 4.3.0) |
install the files, restart the server (a standby too), then ALTER EXTENSION pg_sift UPDATE in each database; no REINDEX, nothing stored is wrong; until then ALTER INDEX <index> SET (track_changes = 'off') on the indexes read that way; going back to 4.3: install its files, restart, ALTER EXTENSION pg_sift UPDATE TO '4.3' (the cause returns) (notes) |
| 4.3.1 to 4.3.2 | the library only: the extension SQL goes from 4.3.1 to 4.3.2 with no new object, no format changes; a correlated sub-select over a join with a derived table that PostgreSQL does not pull up (GROUP BY, DISTINCT, UNION, HAVING) no longer returns the answer of an earlier outer row (4.0.5, 4.1.4, 4.2.2, 4.3.0 and 4.3.1) |
install the files, restart the server (a standby too), then ALTER EXTENSION pg_sift UPDATE in each database; no REINDEX, nothing stored is wrong; until then SET sift.enable_agg = off for such a query; going back to 4.3: install its files, restart, ALTER EXTENSION pg_sift UPDATE TO '4.3' (the cause returns) (notes) |
| a newer PostgreSQL major | pg_upgrade copies (or links) the files as they are |
pg_upgrade |
| an older pg_sift (a downgrade, or a standby that stays on an older release) | an older library refuses what it cannot read | preflight with --to |
1. Preflight
scripts/upgrade-check.sh runs sift.upgrade_check() (scripts/upgrade_check.sql) in each database. It reads the catalogs and metapages and changes nothing
(the function is loaded in a transaction that is rolled back, so a database with an older extension can be checked). It needs a superuser on the primary
and the files of the new release installed:
scripts/upgrade-check.sh --all # every database that accepts connections
scripts/upgrade-check.sh app reporting # these databases (or connection strings)
scripts/upgrade-check.sh --strict app # a warning also ends with exit status 1
One row per finding: STATUS (ok, warn, blocker), OBJECT, KIND, DETECTED and ADVICE. The exit status is 0 when nothing blocks, 1 when a
database has a blocker (or a warning with --strict) and 2 when a database could not be checked.
| Kind | What it looks at | Status and advice |
|---|---|---|
version |
pg_extension against sift.version() |
warn: the library is newer than the extension (ALTER EXTENSION pg_sift UPDATE) or the new release is available; blocker: the library is older than the extension scripts |
index, table |
the metapage format and the column formats of every sift index, sift_col index and sift_table |
blocker: this library cannot open it (the error and its hint are the advice); with --to, the first release that reads it is newer than the target |
option |
options that only units written afterwards follow | warn: cube_minmax, rollup_columns, key_columns, order_columns, point_columns, point_tid_columns, key_cut or hll_columns on an index with older units, JSON paths on segments before column format 6 (the advice is the command from sift.upgrade_commands(), which an extension older than 3.3 has after its update; a sift_table below the format of its schema is a table row with VACUUM FULL), the deprecated mutable_limit |
setting |
sift.* in the server configuration, the session, ALTER DATABASE and ALTER ROLE |
warn: a setting the library does not define (removed or renamed), a feature switched off (a boolean setting that defaults on), sift.col_format_minor kept below the default |
trigger |
with --to a release before 3.3: the event triggers of 3.3 |
blocker: the older library has not their function and would fail every ALTER TABLE and CREATE TRIGGER; ALTER EVENT TRIGGER x DISABLE |
test |
test-only settings and the extension pg_sift_test |
warn: remove them; they need the test build of the library |
Run it before and after the update: the second run must have no warn the first one explained.
2. Install and update
# 1. install the files of the new release (package, or from source)
sudo apt install ./postgresql-N-pg-sift_X.Y.Z-1_amd64.deb
make USE_PGXS=1 && sudo make USE_PGXS=1 install
# 2. preflight (section 1), then in every database that has the extension
psql -d app -c "ALTER EXTENSION pg_sift UPDATE"
# library keeps it until its connection ends, so after every library install restart the server or reconnect every pool and worker)
sudo systemctl restart postgresql
# 4. check the update
psql -d app -c "SELECT extversion, sift.version() FROM pg_extension WHERE extname = 'pg_sift'"
psql -d app -c "SELECT sift.verify('docs_idx', true)" # a sift index
psql -d app -c "SELECT sift.col_verify('facts_col', true)" # a sift_col index
scripts/upgrade-check.sh app
ALTER EXTENSION pg_sift UPDATE runs the scripts from the installed version to the default one of the control file, through every release in between. Index and table
files are not rewritten: an older index stays in its format until a merge, VACUUM or REINDEX writes it again, and the preflight and the update tell which
REINDEX is worth it. A sift_table cannot be reindexed; it follows sift.col_format_minor on its next write.
The extension version of a patch release. From 4.2 a patch release X.Y.N has the extension version X.Y.N, and sift.version() (the loaded library) returns the same string, so
ALTER EXTENSION pg_sift UPDATE after installing the files moves the database to it and the two queries of step 4 agree. The step from one patch release to the next changes no object. A database at
any X.Y.N reaches the next minor with the one script of that minor, because every patch release also ships an empty script back to X.Y. The releases before 4.2 kept the extension version of
their minor (4.1.1 to 4.1.4 are 4.1, 4.0.4 and 4.0.5 are 4.0.3), so on them ALTER EXTENSION pg_sift UPDATE has nothing to do and PostgreSQL prints its own notice, version "4.1" of extension "pg_sift" is
already installed: it is harmless, the library files are the ones that changed. sift.version() of those releases is the minor too (4.1); the release that runs is in
SELECT version FROM pg_get_loaded_modules() WHERE module_name = 'pg_sift' (PostgreSQL 18 and later: 4.1.4+ and the commit), after a restart or a reconnect, as an open session keeps the library it loaded.
If sift.version() is older than extversion, the library files are older than the extension scripts: install the release or a newer one
(scripts/upgrade-check.sh reports it as a blocker).
The last step of an update to 3.3 (from any older version) prints what is left to do, as warnings with the commands (the first 20 of each, then the count of the rest), or a
notice that nothing is (PostgreSQL shows only warnings from an extension script). SELECT * FROM sift.upgrade_commands() lists the same later, for the indexes and tables the role owns or may maintain (every one for a superuser):
one row per command (kind reindex or rewrite, the object, the reason, the command), the cheapest first.
| Warning | Why | Command |
|---|---|---|
a sift (text) index needs a REINDEX |
queries miss rows: since 3.3 a word over 255 bytes is stored as its first 236 bytes and a hash; an older index holds it cut at 255 bytes, so a query for it finds nothing. The index does not record whether it holds such a word, so every text index except analyzer = keyword is listed, also after its REINDEX |
REINDEX INDEX CONCURRENTLY schema.index; |
a sift_col index would gain from a REINDEX |
an option set after the units were written (cube_minmax, JSON paths, rollup_columns, key_columns, order_columns, point_columns, point_tid_columns, key_cut, hll_columns): older units stay readable and are scanned (for order_columns, read whole by an ORDER BY .. LIMIT) until then |
REINDEX INDEX CONCURRENTLY schema.index; |
a sift_table would gain from a rewrite |
segments below the column format of its schema: no calendar cells for its date and timestamp columns (format 8), JSON kinds that may be incomplete for declared paths (format 6). A sift_table cannot be reindexed |
VACUUM FULL schema.table; |
Only the first warning means wrong results without the command. VACUUM FULL takes an ACCESS EXCLUSIVE lock and rewrites the whole table in the format of sift.col_format_minor, so
schedule it; once it ran the table leaves the list. REINDEX CONCURRENTLY cannot run in a transaction block: run the commands one at a time. The check reads the catalogs and
metapages and cannot fail the update; when it cannot answer (a relation locked by another session) it says so, and sift.upgrade_commands() can be run after reconnecting.
What 4.2 changes
Ten changes of 4.2 touch what an index holds, what an older library does with it, a plan you may have measured, or an object you may have made. Only 7, 8 and 9 ask for a step (remove a setting that is no longer accepted, before the update); ALTER EXTENSION pg_sift UPDATE
runs pg_sift--4.1--4.2.sql and the new library reads the indexes of 4.1 and older as they are. Each entry says what changes, what an older library does with the result, whether a REINDEX is needed, and the switch that turns it off.
The index keeps a presence bit for the
jsonbcolumns that may be NULL.CREATE INDEXandREINDEXof asift_colindex write one boolean leaf for eachjsonbcolumn that haspathsand noNOT NULLconstraint on the column of the table (an index expression counts as nullable; aCHECKor a domain that forbids NULL is not seen), soNOT (doc @> x),(doc @> x) IS FALSE,NOT (doc ? 'k')anddoc IS [NOT] NULL, also in aFILTERor aCASE, are answered by the index where they read the table. There is no option and no NOTICE: the layout of an index is the one it was built with (ALTER TABLE .. SET NOT NULLorDROP NOT NULLchanges nothing;REINDEXdecides again). No column format minor changes. Measured on 150,000 documents, 1% to 50% of them without a declared member:count(*), sum(id) WHERE NOT (doc @> x)84.9 M to 7.4 M cycles at 1% and 85.0 M to 9.9 M at 50%,count(*) WHERE doc IS NULL34.9 M to 1.9 M at 5%. The price, on 1,000,000 rows:CREATE INDEX+3.2% instructions, an insert +0.4%, a promotion +4.6%, a merge +5.0%, the index +2.0% in size after a merge (up to +13% right after the build of a wide index, which a merge removes).- Existing indexes are not touched: they have no leaf, those forms keep reading the table, and nothing is wrong;
REINDEXwrites the leaf. - An older library (3.2, 4.0 and 4.1, tried with 3.2.3, 4.0.5 and 4.1.4 on the files of 4.2) refuses an index that has the leaf: a read, an aggregate included, fails with
sift_col index "x" does not match its column layout(hintREINDEX the index.), a write withindex "x" was built with other column definitions(hintUse REINDEX to apply the new options.), andsift.col_verifyreportsThe metapage does not describe this column layout.The way back is toREINDEX INDEX xwith the older release, which writes the layout it reads; there is no switch.scripts/upgrade-check.sh --to 4.1lists the indexes with the leaf as blockers, with that advice.
- Existing indexes are not touched: they have no leaf, those forms keep reading the table, and nothing is wrong;
The write buffer of a BM25 (
sift) index is format 6. The full pages of the buffer have a bloom of term hashes of 8,192 bits where format 0 has 256, so a search for a term that none of the buffered documents has no longer reads every page of the buffer (220,000 documents and a buffer of 2,000: 1.96 M to 1.23 M cycles). Upgrading takes no step: the buffer that is open at the update keeps format 0 and is read as it is; the next one is format 6, and the first of them raises the metapage of the index to minor 1.6 and nothing lowers it (FORMATS.md).- Existing indexes need nothing;
sift_colindexes andsift_tabletables have no such buffer. - An older library (4.1 and older) refuses a BM25 index of 1.6 on a read, a
sift.verifyand an insert:sift index "x" has on-disk format 1.6, this build (pg_sift 4.1) reads formats 1.0 to 1.5, hintInstall the pg_sift release that wrote the index on every node that reads it, or REINDEX it.A standby or a node that stays on the older release is such a node.scripts/upgrade-check.sh --to 4.1lists the index as a blocker. - To go back: install the older release and
REINDEXthe index there (it is built again from the table). Off: none; an index that has taken no insert since the update is still format 0.
- Existing indexes need nothing;
The aggregate scan is chosen more often for a grouped aggregate of one table. For a grouped aggregate with no lookup join, no search and no top-N whose normal plan is a sequential scan and a hash of the groups, the scan’s own work per row is now priced in the units of the plan it is compared with (PostgreSQL’s estimate of that plan runs at about 210 cost units a millisecond, the scan’s price at 833), so a scan that was up to 4 times faster is no longer refused as
costwhen its rows are not few:max(ends - starts)by tenant over 150,000 rows 18.6 to 7.1 ms, aCOALESCEmeasure over 20,000 events 3.4 to 2.1 ms. A statement the scan does not win stays on the normal plan. This changes plans, not files.- An older library and the indexes: nothing; a statement is planned by the library that runs.
REINDEX: no. - Off: no switch separates this choice;
SET sift.enable_agg = off(a session, a role or a database) plans the statement as PostgreSQL does without pg_sift, andsift.explain_why(query)says why a statement did or did not take the scan.
- An older library and the indexes: nothing; a statement is planned by the library that runs.
One rule for
HAVING column IN (SELECT ..)andEXISTS (SELECT ..), and sub-selects of the groups. In a plainGROUP BY, such aHAVINGcondition is always answered as a filter of the rows, as a semi join the scan runs, where PostgreSQL keeps it above the aggregate and the sub-select ran for each group (GROUP BY kind_id HAVING kind_id IN (SELECT id FROM kinds WHERE enabled): 300,000 rows, 1,000 keys, 40.1 to 0.58 ms); the rows form was the cheaper in all 40 cells measured, at 0.09 to 0.78 of the cycles. A sub-select in the select list or theHAVINGthat reads the group keys or the aggregates is evaluated by the scan once for each group (36.8 to 1.85 ms for 300,000 rows and 1,000 groups).NOT IN,NOT EXISTS, an expression or a cast of the key, a correlatedIN, grouping sets and aHAVINGwithoutGROUP BYkeep theirHAVING. The rows are the same (agg_having_where: 46 statements equal to the rows with the scan off).- An older library,
REINDEX: nothing, no file changes. Off: no switch for this rule alone;sift.enable_agg = offas above.
- An older library,
A
sift_colindex withtrack_changesrepairs its own log in more cases. The log of an index whose triggers do not vouch for it (afterpg_upgrade, which makes the triggers again;pg_restore --disable-triggers;ALTER TABLE .. DISABLE TRIGGER ALLthenENABLE TRIGGER ALL;CREATE INDEX CONCURRENTLY;REINDEX CONCURRENTLY;DETACH PARTITION) was checked against the heap on every read untilREINDEXorALTER INDEX. Now the nextVACUUM, the first read that checks the heap or the maintenance worker that a write asks for re-baselines it (ShareUpdateExclusiveLockonly). Triggers enabled for the origin only (whatENABLE TRIGGER ALLleaves,tgenabled = 'O') are enabled for all sessions ('A') by the worker, which asks for the lock ofENABLE TRIGGERwithout waiting; triggers you disabled ('D') stay disabled and the reads keep checking the heap. 300,000 rows churned and not vacuumed: the second statement of a grouped count costs 38.1 M instructions and 918 blocks while the log is not vouched, 29.7 M and 467 when it is; after the repair it is 33.9 M and 585 (the repaired log is a little larger, aVACUUMor a merge shrinks it).- Format: none, no minor changes; answers are right before and after. An older library: tried with 4.1.1 on an index that 4.2 repaired (
ALTER TABLE .. DISABLE TRIGGER ALL,ENABLE TRIGGER ALL, aVACUUM, the worker on:sift.col_trackingtrue, true, true, true and the triggers'A'again): it reads and writes the index without an error, its aggregates equal the heap’s before and after its own writes andsift.col_verifyis clean, but it does not take the log for complete (sift.col_tracking:triggers,completeandheap_freefalse), so its reads check the heap as they did before the repair; the repair itself is the re-baseline of 4.1.2 and later.REINDEX: not needed. Off: none for the repair;ALTER INDEX x SET (track_changes = 'off')drops the log and its triggers for an index that should not have them.
- Format: none, no minor changes; answers are right before and after. An older library: tried with 4.1.1 on an index that 4.2 repaired (
sift.refusal_actions()returns what to change for each refusal slug (the statement, the index or a setting, the hint of the notice ofsift.explain_refusals):sift.explain_why(q) w JOIN sift.refusal_actions() a USING (slug). It is a new function;sift.refusal_slugs()keeps its three columns, andpg_sift--4.1--4.2.sqldrops or replaces no function, so a view, a default or a function of yours that reads the objects of 4.1 does not makeALTER EXTENSION pg_sift UPDATEfail (test/t/extension_lifecycle.shupdates a database that has a view oversift.refusal_slugs()). Files,REINDEX, off: not applicable.sift.eval_methodsno longer takeslatest_hint,snapshot_masks,unit_rewriteandskip_trigger_lookup(nor the-nameform of them);penalty_maintenanceis the only name left.skip_trigger_lookupis now always on (the statement trigger oftrack_changesleaves out the look at an UPDATE that kept the key),latest_hintandsnapshot_maskshad nothing behind them, and the writer ofunit_rewriteis gone (segments an earlier release wrote with it stay readable). What happens to a setting that names one (tried on this tree): with the library loaded, aSET,ALTER ROLE .. SET,ALTER DATABASE .. SETandALTER SYSTEM SETfail withERROR: invalid value for parameter "sift.eval_methods": "NAME"andDETAIL: Unknown evaluation method: NAME, so a script that runs one breaks; a value already stored (postgresql.conf,postgresql.auto.conf, a role or database setting) does not stop the server or a login, but every session that loads the library and the log of the server say the same text as a WARNING, and the setting is ignored. Remove them before updating:sql SELECT coalesce(r.rolname, '(all roles)') AS role, coalesce(d.datname, '(all databases)') AS database, c AS setting FROM pg_db_role_setting s LEFT JOIN pg_roles r ON r.oid = s.setrole LEFT JOIN pg_database d ON d.oid = s.setdatabase, unnest(s.setconfig) c WHERE c LIKE 'sift.eval_methods=%'; -- ALTER ROLE r [IN DATABASE d] RESET sift.eval_methods; or ALTER DATABASE d RESET sift.eval_methods SELECT name, setting, sourcefile, sourceline FROM pg_file_settings WHERE name = 'sift.eval_methods'; -- superuser; remove the line, or ALTER SYSTEM RESET sift.eval_methods, then SELECT pg_reload_conf()scripts/upgrade-check.shlists each of them as a blocker with the command (any release of the library, so it can run before the update). Files,REINDEX: no. Off: the names are gone; leave the setting empty.sift.enable_colis removed. It was declared in 3.0 and no code ever read it (sift.enable_col_scanandsift.enable_aggare the switches of the planner), so setting it changed nothing, whatever its documentation said. What happens to a setting that names it (tried on this tree): with the library loaded, aSET,ALTER ROLE .. SET,ALTER DATABASE .. SETandALTER SYSTEM SETfail withERROR: invalid configuration parameter name "sift.enable_col"andDETAIL: "sift" is a reserved prefix., so a script that runs one breaks; a value already stored (postgresql.conf,postgresql.auto.conf, a role or database setting) does not stop the server or a login, but every session that loads the library and the log of the server sayWARNING: invalid configuration parameter name "sift.enable_col", removing itwithDETAIL: "sift" is now a reserved prefix., andSHOW sift.enable_colisunrecognized configuration parameter. Remove it before updating:sql SELECT coalesce(r.rolname, '(all roles)') AS role, coalesce(d.datname, '(all databases)') AS database, c AS setting FROM pg_db_role_setting s LEFT JOIN pg_roles r ON r.oid = s.setrole LEFT JOIN pg_database d ON d.oid = s.setdatabase, unnest(s.setconfig) c WHERE c LIKE 'sift.enable_col=%'; -- ALTER ROLE r [IN DATABASE d] RESET sift.enable_col; or ALTER DATABASE d RESET sift.enable_col SELECT name, setting, sourcefile, sourceline FROM pg_file_settings WHERE name = 'sift.enable_col'; -- superuser; remove the line, or ALTER SYSTEM RESET sift.enable_col, then SELECT pg_reload_conf()scripts/upgrade-check.shlists each of them as a blocker with the command (any release of the library, so it can run before the update). Files,REINDEX: no. Off: not applicable; to compare a plan with the normal one,SET sift.enable_agg = offandSET sift.enable_col_scan = off.sift.table_auto_defrag_dead_pctis removed. It was the trigger of the background pass of asift_tablein 3.3 (a percent of tombstones); since 4.0 the pass is asked for by the reads of the table and the setting changed nothing. It is refused and dropped exactly assift.enable_colof item 8 (ERROR: invalid configuration parameter name "sift.table_auto_defrag_dead_pct",DETAIL: "sift" is a reserved prefix.; a stored value:WARNING: invalid configuration parameter name "sift.table_auto_defrag_dead_pct", removing it), and the queries and commands of item 8 with that name find and clear it;scripts/upgrade-check.shlists each as a blocker. Files,REINDEX: no. Off: not applicable (sift.table_auto_defrag = offturns the pass off).Two reloptions of
sift_colhave clearer names:rollupisrollup_leaves(the array elements and map values a unit groups by) androllup_dimsisrollup_columns(the scalar columns of the rollup of the rows). The older names stay accepted through 4.x:CREATE INDEXandALTER INDEX .. SETwith one sayNOTICE: reloption "rollup" is the older name of "rollup_leaves"("rollup_dims"and"rollup_columns"for the other) and read it as the new one; when both are given the new one wins;ALTER INDEX .. SETandRESETof the old name work; an index that carries the old name keeps it inpg_class.reloptions,pg_dumpwrites it as it is, andpg_upgradeandALTER EXTENSION UPDATEneed nothing. The build writes the new name where it decides a value itself (rollup_columns = 'auto: a, b'). The messages, the choices ofsift.index_choicesand the errors name the new spelling (invalid rollup_columns option: ..).sift.upgrade_commands()matches both spellings, so an index with either gets the same advice; a script or a view of yours that matches the text ofpg_class.reloptionsforrollup_dimsshould matchrollup_columnstoo.sift.index_choicesreports the choice asrollup_columns. The limit ids ofsift.stats()keep their names (limit.rollup_dims). Files,REINDEX: no (the files hold no option names). Off: not applicable; the old names go away in 5.0.
4.2 to 4.3
ALTER EXTENSION pg_sift UPDATE runs pg_sift--4.2--4.3.sql (4.3 is default_version = '4.3'); a database at 4.2.1 or 4.2.2 first runs the empty script back to 4.2 that every patch release ships, then that one. The script adds the functions and the view of connected indexes (sift.col_connection_state(), sift.col_connection_list(), sift.col_connection_fill(), sift.col_connections) and sift.col_arena_status(), and replaces sift.workers, sift.col_index_status and sift.upgrade_commands() (a view or a function of yours that reads them keeps working); it drops nothing. The new library reads every index and table that 4.2 and older wrote, and nothing asks for a REINDEX. Four things are worth reading before the update.
Column format minor 44, which a library before 4.3 refuses. A segment is stamped with the lowest minor that describes what it holds. A 4.3 build writes minor 44 for each of the following, and the defaults of 4.3 turn each of them on:
Written when By default Switch an order chunk that is seekable, a positional chunk of the second order by a group, or the packed table of groups: every index with order arrays chosen by the build yes, under sift.col_format_minor15 or moreSET sift.col_format_minor = 14before the buildthe tombstone log of a parent index that has a connected child (the key journal), and the segments of a connected child (a found flag for each connection) when an index is connected (below) sift.col_auto_connect = off,connect = 'off'a write buffer that holds a record that is a reference to a heap tuple ( sift.col_wbuf_ref)yes, for a heap table whose index tracks changes sift.col_wbuf_ref = off(records already written stay references until the buffer is promoted)A 4.2 library that reads such a segment fails with
sift_col index "x" has a segment of column format 1.44, this build reads 1.3 to 1.43and the hint toREINDEX(a scan,VACUUM,sift.col_verifyandsift.col_segments); it refuses the merge and the promotion of a connected index, and appends tombstones to the log of a parent without the journal records, so a database must not be written by 4.2 once a connected child exists. No answer is wrong: the tests found no wrong row.scripts/upgrade-check.sh --to 4.2lists each such index as a blocker. An index written by a pre-release build of 4.3 (which stamped minors 45 to 47) is refused by the release withthis build reads 1.3 to 1.44and needs aREINDEX; no released version is concerned.What happens to an index that exists. Nothing, until a write or a rebuild: a merge makes the choices of the build again for the segments it writes, and
REINDEXfor all of them, and the old and the new segments answer the same queries meanwhile. So an index of 4.2 gets, from its next merge orREINDEX: a serial id as an order array and no point directory (5,000,000 rows: 63.3 MB to 49.1 MB), the second order by a group column chosen from a b-tree of the table or from a foreign key, seekable order chunks (minor 44), and, atREINDEX, the automatic connection. The choices are named in theNOTICEofCREATE INDEXandREINDEXand listed bysift.index_choices('index'):sift_col index "x" is column format minor 44, which a library before 4.3 cannot read.sift.col_format_minoris kept. It is the switch for a rollback:SET sift.col_format_minor = 14before a build writes a format that an older library reads (the hint of thatNOTICE), and the build then saysdoes not track changes: sift.col_format_minor is below 15for an index that would track them. It is a superuser setting, and the background worker that rewrites an index follows the server and database settings only, so useALTER SYSTEMorALTER DATABASEwhen no write may go beyond 4.2 (the same rule as for 3.2 below).A new write path and two new plans. (a)
sift.col_wbuf_ref(on): the rows of a write buffer of a heap table with a tracked index are 16-byte references and their values live in a shared arena of up to 128 MB for the server; the first read of a buffer after a restart, on a standby or on a promoted standby reads the heap once (about 11,900 instructions a row). (b)CREATE INDEXandREINDEXof asift_colindex connect it to thesift_colindex of the table that each validated single-column integer foreign key references (sift.col_auto_connect, on), and say so in aNOTICE; aREINDEXof an index of 4.2 can therefore connect it, and the first child of a parent holds the writers of the parent table out from the end of its scan to the end of the transaction. © The planner takes the child-first plan and the found-flag answer where they cost less (sift.agg_child_first,sift.agg_connect_found;sift.enable_agg = offplans as PostgreSQL does). Only (a) and (b) change files: they are the minor above; © changes plans only.
What to do.
- Update as in section 2: install, restart (a standby too),
ALTER EXTENSION pg_sift UPDATEin each database, thenscripts/upgrade-check.sh(it warns for the three deprecated options below and for the test settingsift.test_col_legacy_options; nothing blocks). Update the standbys and every node that reads the index before the primary, or setsift.col_wbuf_ref = offandsift.col_auto_connect = off(andsift.col_format_minor = 14for the other sections of minor 44) on the primary until they are updated. REINDEX INDEX CONCURRENTLYis worth it for an index with a serial id, for an index whose table is read byWHERE col = x ORDER BY ts LIMIT nthrough a b-tree you want to drop, and for a child table whose foreign key you want connected; not for the rest. Reset the three deprecated options first (below). AREINDEXafterALTER INDEX x SET (connect = 'off')leaves an index unconnected.- The options
order_columns,point_columnsandkey_cut_rowsare accepted and ignored or replaced by 4.3 and removed in 4.4: What 4.3 deprecates and 4.4 removes.key_order = 'col'of one column no longer builds the point directory: setpoint_tid_columnson the key for lists of keys.
Going back to 4.2
Run this on the 4.3 database while the 4.3 library still runs. There is no script from 4.3 to 4.2: ALTER EXTENSION pg_sift UPDATE TO '4.2' fails with extension "pg_sift" has no update path from version "4.3" to version "4.2", the catalog stays at 4.3, and what 4.3 added to it (the functions of connected indexes and sift.col_arena_status()) is unusable with the 4.2 library, as in the section “After the downgrade” below.
# 1. what 4.2 cannot read: one row for each index, with the advice
scripts/upgrade-check.sh --to 4.2 app
# 2. every write from now on is readable by 4.2 (superuser; new sessions pick it up)
psql -d postgres -c "ALTER SYSTEM SET sift.col_wbuf_ref = off" -c "ALTER SYSTEM SET sift.col_auto_connect = off" \
-c "ALTER SYSTEM SET sift.col_format_minor = 14" -c "SELECT pg_reload_conf()"
# 3. connected indexes: switch the connection off (child and parent), then rebuild every index of step 1
psql -d app -c "ALTER INDEX public.events_col SET (connect = 'off')"
psql -d app -c "REINDEX INDEX CONCURRENTLY public.events_col"
psql -d app -c "REINDEX INDEX CONCURRENTLY public.dim_a_col"
# 4. nothing blocks any more (exit status 0; the warnings that the settings are below the default are the point)
scripts/upgrade-check.sh --to 4.2 app
# 5. stop the server, install the 4.2 files, start it; then check
psql -d app -c "SELECT sift.col_verify('events_col', true)"
- The
REINDEXundersift.col_format_minor = 14writes no seekable chunk and no section of minors 15 and above (so no minor 44), and an index built so does not track changes (itsNOTICEsays it), so its reads check the heap. After the 4.2 files run,ALTER SYSTEM RESET sift.col_format_minorandREINDEXthe index again to log its changes again; keepsift.col_format_minor = 14while any 4.3 library still writes. ALTER INDEX x SET (connect = 'off')drops the dependency on the parent index, and the parent keeps its journal (minor 44) until its ownREINDEX, which is why step 3 rebuilds the parent too.sift.col_wbuf_ref = offdoes not turn a buffer that holds references into one that does not: theREINDEXof step 3 does, or its promotion.- A standby that stays on 4.2 needs steps 2 and 3 on its primary before anything is written, and
sift.col_format_minorset there for every later write. - Settings the 4.3 library accepts and 4.2 does not know (
sift.col_wbuf_ref,sift.col_auto_connect,sift.agg_child_first,sift.agg_connect_found,sift.agg_dimmap_kb,sift.col_connect_refresh_pct): remove them frompostgresql.confand from role and database settings when the 4.2 files are installed, as for any setting a release does not define.
What 4.3 deprecates and 4.4 removes
Three sift_col options are accepted and ignored by 4.3 and are removed in 4.4, when an ALTER INDEX .. SET or a CREATE INDEX that names one is refused. A CREATE INDEX or a REINDEX that names one says so in its NOTICE, and scripts/upgrade-check.sh lists each index that carries one as a warning with the ALTER INDEX .. RESET that clears it. Indexes built before keep reading as they are; REINDEX writes the layout the defaults choose.
| Option | What 4.3 does | What to do before 4.4 |
|---|---|---|
order_columns |
writes no sorted run; the order arrays (order_array_columns, chosen by the build) answer a top-N, a range and an OFFSET |
ALTER INDEX .. RESET (order_columns); keep order_array_columns if you set it |
point_columns |
lists its columns as heap addresses, as point_tid_columns does |
set point_tid_columns to the same columns and reset point_columns |
key_cut_rows |
the units aim at the rows of an average superblock | ALTER INDEX .. RESET (key_cut_rows) |
order_layout and sift.col_format_minor stay through 4.4. The setting sift.test_col_legacy_options, which makes a build honor the three options and which the test clusters of this project set, is for tests only and goes away with them; do not set it on a server.
3. A newer PostgreSQL major
pg_upgrade copies the relation files and restores the catalogs. pg_sift needs only its library for the new major next to the server (the same release as
before: pg_upgrade does not change the extension version), and the same data checksum setting on both clusters (PostgreSQL 18 enables checksums by default; a cluster
without them is initialised with initdb --no-data-checksums).
# the library of the same pg_sift release for the new major
sudo apt install ./postgresql-N-pg-sift_X.Y.Z-1_amd64.deb
make USE_PGXS=1 PG_CONFIG=/usr/lib/postgresql/18/bin/pg_config && sudo make USE_PGXS=1 PG_CONFIG=/usr/lib/postgresql/18/bin/pg_config install # from source
# the new cluster, then the check (it starts both clusters and loads pg_sift in each), then the upgrade
/usr/lib/postgresql/18/bin/initdb -D /var/lib/postgresql/18/main
/usr/lib/postgresql/18/bin/pg_upgrade -b /usr/lib/postgresql/17/bin -B /usr/lib/postgresql/18/bin \
-d /var/lib/postgresql/17/main -D /var/lib/postgresql/18/main --check
/usr/lib/postgresql/18/bin/pg_upgrade -b /usr/lib/postgresql/17/bin -B /usr/lib/postgresql/18/bin \
-d /var/lib/postgresql/17/main -D /var/lib/postgresql/18/main
Afterwards: copy the sift.* lines of the old postgresql.conf (pg_upgrade carries settings stored in the catalogs, ALTER DATABASE and ALTER ROLE, not the file), start the new
cluster, run vacuumdb --all --analyze-in-stages, and check as in section 2 (sift.verify, sift.col_verify, scripts/upgrade-check.sh --all). BM25 results and scores, column aggregates and sift_table contents
are the same as before, and writes, VACUUM and merges work on what the old cluster wrote
(test/t/pg_upgrade.sh: PostgreSQL 17 to 18 when both are installed, otherwise 18 to 18). The triggers of a sift_col index that tracks its changes are made again by the restore, so until the
log is re-baselined (the first VACUUM of the table, which the step above runs, or the maintenance worker that a read or a write of the table asks for) its reads check the heap: the answers are right, and the
instructions of a statement over rows that were not vacuumed rise (sift.col_tracking(index) shows triggers false). Nothing to run by hand
(test/t/col_track_revouch.sh: a cluster written by 4.1.0, PostgreSQL 17 to 18).
4. An older release
A BM25 (sift) index written by 4.2 is not readable by 4.1 or older once it has taken an insert. 4.2 opens the write buffer of a BM25 index in buffer format 6 (a bloom of 8,192 bits for each full page, FORMATS.md); the first such buffer raises the metapage of the index to minor 1.6 and nothing lowers it. A 4.1 or older library refuses the index with the format error and a REINDEX hint (reads formats 1.0 to 1.5). Upgrading takes no step: the buffer that is open at the upgrade keeps format 0 and is read as it is. To go back, install the older release and REINDEX the index there (the old library does not read the index, but REINDEX builds a new one from the table); a sift_col index or a sift_table is not affected.
A database written by the 3.3 library with its default settings can no longer be read by a 3.2 library. An older library refuses a metapage minor or a column format it does not know, with a REINDEX hint
(old_lib_refusal), and 3.3 writes at sift.col_format_minor = 15 by default: a sift_col index created or rewritten after the update carries sections 3.2 does not know, and so does a sift_table with a date or timestamp
column. Only what was written with the settings below stays at minor 4 or 6, the highest a 3.2 library reads (a segment is stamped with the lowest minor that describes its content):
| Minor | Written when | Keeps the 3.2 bytes |
|---|---|---|
| 7 | a sift_col index with cube_minmax (on by default) |
WITH (cube_minmax = false), or sift.col_format_minor 6 or less |
| 8 | the calendar cells of a date, timestamp or timestamptz column: a sift_col index (calendar_columns defaults to all of them), a sift_table |
WITH (calendar_columns = 'off'); a sift_table: sift.col_format_minor 7 or less |
| 9 | rollup_columns (option, off) |
do not set it, or sift.col_format_minor 8 or less |
| 10 | key_columns naming a column that keeps a summary of its keys: text or uuid, or an int2, int4, int8 or date one whose key list or bitmap would not fit (option; auto never picks one) |
do not name it, or sift.col_format_minor 9 or less |
| 11 | a sift_table write buffer that took a row over the buffer (sift.table_wide_rows, on by default; a 3.2 library reads, writes beside and promotes it, but the preflight lists it until promoted) |
SET sift.table_wide_rows = off (a row over the buffer is refused again) |
| 12 | a sift_col index with superunit (16 by default) |
WITH (superunit = 0), or sift.col_format_minor 11 or less |
| 13 | order_columns (option, off) |
do not set it, or sift.col_format_minor 12 or less |
| 14 | a string dictionary of 1,024 strings or more (text, varchar, uuid) stored in blocks to be read in place, where that adds at most 3% to the segment (ids, hashes, uuids, long strings; short repeating strings stay compact), with the string order for a column in key_columns, point_columns or return_columns, any column with dict_lookup = on, or in a sift_table with sift.table_dict_lookup = on; point_columns (option, off) |
sift.col_format_minor 13 or less |
| 15 | key_columns: a key list or bitmap of an int2, int4, int8 or date column (the foreign key and _id columns the default auto picks, or the ones named) |
WITH (key_columns = 'off'), or sift.col_format_minor 14 or less |
| 16 | point_tid_columns (option, off): the point directory lists heap addresses |
sift.col_format_minor 14 or less (then it lists rows, minor 14) |
| 17 | key_cut (option, automatic from 3.4 for an index that benefits, or named): the units are cut by the range of a key and list heap addresses |
ALTER INDEX x SET (key_cut = 'off') and REINDEX; sift.col_format_minor 14 or less also writes superblock units |
| 18 | order_columns (option, off): the sorted runs also hold their heads (the best keys at each end of a unit with their heap addresses) |
ALTER INDEX x RESET (order_columns) and REINDEX, or sift.col_format_minor 14 or less (the runs are written without heads, minor 13) and REINDEX |
| 19 | a leaf NULL in nearly every row of a unit (a soft-delete stamp): the unit also keeps the pre-aggregates of the non-NULL rows, so leaf IS NULL is answered from them |
REINDEX (a merge writes them too), or sift.col_format_minor 14 or less (written without) and REINDEX |
| 20 | hll_columns (option, off): a HyperLogLog sketch of a column in every unit, for sift.approx_count_distinct |
ALTER INDEX x RESET (hll_columns) and REINDEX (the setting does not apply: the option stamps the minor) |
| 21 | order_array_columns (option, auto from 3.5: the build chooses the scattered columns of a table of 100,000 rows or more), order_array_group (option, off): a global order array of the keys of a segment |
ALTER INDEX x SET (order_array_columns = 'off') (and RESET (order_array_group)) and REINDEX; the setting does not apply: the option stamps the minor |
| 22 | key_table (option, on) of an index, sift.table_key_table (setting, on) of a sift_table: the key table of a leaf of key_columns, or of a foreign key or *_id column of a table, in a segment |
ALTER INDEX x SET (key_table = off) and REINDEX; for a table SET sift.table_key_table = off and VACUUM FULL t; before the older files are installed (a setting below 15 also suppresses key tables) |
| 23 | key_columns lists of runs of consecutive keys (on when it pays) |
ALTER INDEX x SET (key_columns = 'off') and REINDEX, before the older files are installed (a 23 segment makes 3.4 refuse the index) |
| 24 | count_columns (option, on for a text leaf read in place): the rows of each string of a text leaf’s dictionary |
ALTER INDEX x SET (count_columns = 'off') and REINDEX, before the older files are installed (a 24 segment may also hold key tables: key_table = off too, for 3.4) |
So WITH (cube_minmax = false, superunit = 0, calendar_columns = 'off') at creation, or sift.col_format_minor = 6 for the server (ALTER SYSTEM, as in “Going back to 3.2”) or the database, writes what 3.2 reads (4 for a schema
without JSON paths, 6 with them; 4 for 3.1, 3 for 3.0 with the setting at 4 or 3). An index with wide leaves (column format 5) is written for 3.2 and later at every setting.
A session setting (SET), or one on an application role, is not enough while sift.col_auto_merge is on (the default since 3.3): the background worker that rewrites a sift_col index runs in its own process, as the bootstrap superuser, and follows only the server and database settings, so it would write the default 15 into an index that 3.2 must read. Use the server setting when no 3.3 write may go beyond 3.2, or sift.col_auto_merge = off while the session setting is the one in force.
Going back to 3.2
Run this sequence on the 3.3 database while the 3.3 library still runs (test/t/compat_matrix.sh, “3.2 downgrade”, runs it end to end on a database holding an index of every minor above, a sift_table with calendar cells,
one with a 5 MB row and a BM25 index, then starts the 3.2.0 library on it):
# 1. what the target cannot read: one blocker row per object, with the advice
scripts/upgrade-check.sh --to 3.2 app
# 2. every write from now on is readable by 3.2 (superuser; new sessions pick it up)
psql -d postgres -c "ALTER SYSTEM SET sift.col_format_minor = 6" -c "SELECT pg_reload_conf()"
# with DISABLE (the 3.2 library has not the function of the two event triggers of 3.3, and would fail every ALTER TABLE and CREATE TRIGGER of the database)
psql -d app -c "REINDEX INDEX CONCURRENTLY public.facts_col"
psql -d app -c "VACUUM FULL public.orders"
psql -d app -c "ALTER EVENT TRIGGER sift_table_ddl_check DISABLE" -c "ALTER EVENT TRIGGER sift_table_repack_guard DISABLE"
# 4. nothing blocks any more (exit status 0; the warning that the setting is below the default is the point)
scripts/upgrade-check.sh --to 3.2 app
# 5. stop the server, install the 3.2 files, start it; then check
psql -d app -c "SELECT sift.col_verify('facts_col', true)" # each sift_col index
psql -d app -c "SELECT sift.verify('docs_idx', true)" # each sift index
- Keep
sift.col_format_minor = 6while any 3.3 library still writes: a write at the default after step 4 would put a section 3.2 cannot read back into the database. VACUUM FULLtakes anACCESS EXCLUSIVElock and rewrites the table in the format of the setting, whatever the row size (tested with a 5 MB row).ALTER TABLE t SET ACCESS METHOD heapand back rewrites it too, but fails withrow is too bigon a row that does not fit a heap page, so the preflight does not name it.- A
sift(BM25) index built by 3.7 is at segment format 4 (metapage minor 1.4), which 3.2 refuses with the format error and the hint toREINDEX(no wrong answer;scripts/upgrade-check.sh --to 3.2lists it as a blocker). A 3.3 to 3.6 index is at minor 1.3, the one 3.1 and 3.2 write (tested: built with 3.3, verified and answered the same by 3.2). There is no setting that makes 3.7 write the older format, so the way back is theREINDEXof the index by the older library after the downgrade (tested: it verifies and answers as before). A word over 255 bytes is stored as a hash since 3.3 and cut at 255 bytes by 3.2 (section 2):REINDEXsuch an index with 3.2 if a query for the long word must find it. - The catalog keeps the extension at 3.3 (below). The access methods,
sift.col_verify,sift.verify, the other functions 3.2 has, and writes, promotion, merge andREINDEXby the 3.2 library work on the downgraded database (tested). - A standby that stays on 3.2 needs steps 2 and 3 on its primary, and
sift.col_format_minorset there for every later write. point_tid_columns(minor 16). 3.2, 3.1 and 3.0 refuse minor 16 segments with a format error and theREINDEXhint (tested; no answer is ever wrong). Their planner falls back to other plans only for an index it can open, so a refused index fails the query until rewritten. The setting of step 2 (6, 4 or 3) writes no point directory, so theREINDEXof step 3 drops the heap addresses. The index keeps the option inpg_class: a 3.2 library ignores it when reading (tested), but a restore into 3.2 reportsunrecognized parameter. Roll back:ALTER INDEX x RESET (point_tid_columns)(before the dump, if any), then theREINDEXof step 3. Go forward again:ALTER INDEX x SET (point_tid_columns = 'col')andREINDEX INDEX xwith the setting reset (the option does not apply to segments written before it).REINDEX,sift.col_refreshand 3.3 merges keep the option and write minor 16 again (tested).key_cut(minor 17). Same refusal by 3.2, 3.1 and 3.0 (tested; no answer is ever wrong). The setting of step 2 (14 or less, 6, 4 or 3) writes superblock units again, so theREINDEXof step 3 drops the cut; the option stays inpg_classwith the same restore message. Roll back:ALTER INDEX x SET (key_cut = 'off')(aRESETlets the nextREINDEXdecide again) andRESET (key_cut_rows), before the dump, then theREINDEX. Go forward again:ALTER INDEX x SET (key_cut = 'col')andREINDEX INDEX xwith the setting reset.REINDEX,sift.col_refreshand merges keep the option and write minor 17 again (tested).hll_columns(minor 20). 3.3 and older refuse minor 20 segments with a format error and theREINDEXhint.sift.upgrade_check('3.3')lists the index as a blocker withALTER INDEX x RESET (hll_columns)and theREINDEX;sift.approx_count_distinctexists only from 3.4, so a query that calls it fails on an older library whatever the index. It andcount(DISTINCT)are different aggregates: no setting turns one into the other.order_array_columns(minor 21). 3.3 and older refuse minor 21 segments with a format error and theREINDEXhint.sift.upgrade_check('3.3')lists the index as a blocker withALTER INDEX x SET (order_array_columns = 'off')(andRESET (order_array_group)) and theREINDEX; from 3.5 aCREATE INDEXorREINDEXof a table of 100,000 rows or more may choose columns (auto), so a rollback to 3.3 sets it tooffbefore theREINDEX.key_table(minor 22, on by default). A 3.4 library refuses a segment of minor 22 with the format error (index "x" has a segment of column format 1.22, this build reads 1.3 to 1.21) and theREINDEXhint, in a scan,VACUUM,sift.col_verifyandsift.col_segments; anINSERTinto the table fails withinvalid directory update: unknown segment format; nothing crashes (checked with the 3.4.2 library on a database written by 3.5). The error is SQLSTATE 55000 (HINT: Use a newer pg_sift) and comes from opening the index in the planner, so every statement on the table fails, even a plainselect count(*), not only the scans of sift; the server stays up.DROP INDEX xmakes the table usable again (tested with the 3.4.2 library). AREINDEXon the 3.4 library rebuilds the index without key tables (it does not know the option), so it fixes it too; an index written withkey_table = offis read by 3.4.2 without error.sift.upgrade_check('3.4')lists the index as a blocker withALTER INDEX x SET (key_table = off)and theREINDEX(for asift_table:SET sift.table_key_table = offandVACUUM FULL t, which rewrites the segments without tables); run both on the 3.5 library, before the files of the older release are installed (an older library does not know the option, but ignores it inpg_class, as it ignores the others in this list). Plan the way back before the update: an index whose segments got key tables cannot be read by 3.4 again (segments too small for one stay at minor 15). The setting does not apply:sift.col_format_minoris 15 at most, and a table is written under 15 only. A segment written under a lower setting has none.count_columns(minor 24, on for a text leaf read in place). A 3.4 library refuses a segment of minor 24 as it does minor 22: the format error (index "x" has a segment of column format 1.24, this build reads 1.3 to 1.21) with theREINDEXhint, in a scan,VACUUM,sift.col_verifyandsift.col_segments, and anINSERTfails withinvalid directory update: unknown segment format.sift.upgrade_check('3.4')lists the index as a blocker with the adviceALTER INDEX x SET (count_columns = 'off', key_columns = 'off', key_table = off)andREINDEX. An index has such a segment when a text leaf with 1,024 strings or more is read in place (point_columns,point_tid_columns,key_columns,return_columnsordict_lookup = on) andcount_columnsis notoff.- The hash of the dictionaries (minor 25, on for a text or uuid leaf read in place). A 3.5 library refuses a segment of minor 25 with the format error (
index "x" has a segment of column format 1.25, this build reads 1.3 to 1.24) and theREINDEXhint, as it refuses minor 24 in a 3.4 library;sift.upgrade_check('3.5')lists the index as a blocker with the adviceSET sift.col_format_minor = 14, thenREINDEX INDEX x. There is no option of its own: every dictionary of 1,024 strings or more written in the lookup form (point_columns,point_tid_columns,key_columns,return_columnsordict_lookup = on) has the hash, so a rollback sets the setting for theREINDEX(it also leaves out the sections of minors 15 to 24) and an update to 3.6 leaves the older segments as they are until a merge orREINDEXwrites them again. - For 3.1 use 4 and for 3.0 use 3 in step 2, and
--to 3.1or--to 3.0in steps 1 and 4 (the matrix test checks that those releases read what the settings 4 and 3 write, not the full sequence).
After the downgrade: the catalog, pg_dump and the way forward
- The catalog stays at 3.3. There is no script from 3.3 to 3.2:
ALTER EXTENSION pg_sift UPDATE TO '3.2'fails withextension "pg_sift" has no update path from version "3.3" to version "3.2", andDROP EXTENSION .. CASCADEwould drop every index andsift_tablethat uses it. What 3.3 added to the SQL stays and is unusable with the 3.2 library: its functions (sift.upgrade_commands,sift.stats,sift.explain_why,sift.worker_state,sift.table_space, …) and the two event triggers fail withcould not find function "..." in file ".../pg_sift.so". Hence theDISABLEof step 3: enabled, they make everyALTER TABLEandCREATE TRIGGERof the database fail, and the preflight lists them as blockers for a target before 3.3. The tables and indexes,sift.version(),sift.col_verifyandsift.verifywork (tested). - pg_dump and a server that has the 3.2 files only. The dump holds
CREATE EXTENSION pg_siftwithout a version, so the restore creates the extension at the version of the files it finds (3.2); the tables are restored with their rows and the 3.2 library builds every index again in its own format (no setting is needed, no 3.3 or 3.4 section is left). ACREATE INDEXwith an option that 3.3 or 3.4 added (cube_minmax,superunit,calendar_columns,dict_lookup,rollup_columns,key_columns,order_columns,point_columns,point_tid_columns,key_cut,key_cut_rows) fails withunrecognized parameter, and the restore goes on without that index. Asift_tablewith a row too large for a write buffer page (3.3 stores it, 3.2 refuses it withrow of sift_table "t" is too large) restores no row at all: itsCOPYfails as a whole. These are the only errors (tested); a dump without them restores clean, and an index without an option is built at the 3.2 defaults. Reset the option before the dump (ALTER INDEX x RESET (option)) or edit the statement, and keep such a table out of a dump for 3.2. - pg_dump of a catalog at 4.2 under an older library. 4.2 added
sift.table_option_values, the per-table options of asift_table, whose key typesift.tblrefhas its input and output functions in the 4.2 library only:pg_dumpfails withcould not find function "sift_tblref_out"while it copies that table out (even when it is empty). Leave its data out,pg_dump --exclude-table-data=sift.table_option_values, and set the options again with 4.2 (sift.table_options()lists them before the downgrade); the options are the only thing a downgrade to a release before 4.2 loses. - Going forward again. Install the 3.3 files and restart. A database downgraded in place is at 3.3 in the catalog already:
ALTER EXTENSION pg_sift UPDATEhas nothing to do. RunALTER EVENT TRIGGER sift_table_ddl_check ENABLEandALTER EVENT TRIGGER sift_table_repack_guard ENABLE, thenALTER SYSTEM RESET sift.col_format_minorandSELECT pg_reload_conf()so writes carry the 3.3 sections again; what was rewritten at minor 6 stays at 6 until aREINDEXorVACUUM FULL(the preflight without--toandsift.upgrade_commands()list them). A database restored at 3.2 takes the update of section 2. The matrix test runs both.
Every blocker row of the preflight says how to write what the release reads:
| Object | Advice |
|---|---|
sift_col index |
SET sift.col_format_minor = N (3 for 3.0, 4 for 3.1, 6 for 3.2; 3.3 reads every minor up to 16, 3.4 up to 21, 3.5 up to 24, 3.6 up to 32, 3.7 up to 39 and 4.0 up to 40; the default setting is 15, and minors 16, 17, 18, 20 and 21 are written only for an index with point_tid_columns, key_cut or order_columns, hll_columns or order_array_columns, minor 22 for an index with key_columns leaves unless key_table = off and for a sift_table with a foreign key or *_id column unless sift.table_key_table = off, minor 24 for an index with count_columns, minor 25 for a lookup dictionary of 1,024 strings or more, and 19 only for a leaf NULL in nearly every row) and REINDEX INDEX x; an index with wide leaves (column format 5) is written for 3.2 and later at every setting: drop it for 3.1 |
sift_table |
with sift.col_format_minor set, VACUUM FULL t rewrites it in that format (ALTER TABLE t SET ACCESS METHOD heap and back fails with row is too big on a row over the heap page limit) |
sift (BM25) index |
the segment format is the library’s (3.7 writes format 4, which 3.6 and older refuse with the format error): drop the index and create it again with the older release, or keep the newer library |
Defaults changed in 3.3
An existing index or table keeps what it holds; the new default applies to units written after the update (promotion, merge, REINDEX), and sift.upgrade_commands() lists the REINDEX that gives older units the new sections. To keep the 3.2 behavior set the value in the last column. sift.table_auto_defrag is read from the library, not the extension script: it is on for every sift_table of the cluster as soon as the 3.3 library runs (after the restart), whatever the extension version, and the preflight of section 1 warns about the off that keeps 3.2, as for any feature switched off.
| Setting | 3.2 | 3.3 | Measured (200,000 rows, 53 units) | To keep the 3.2 behavior |
|---|---|---|---|---|
sift_col option cube_minmax |
off | on | GROUP BY with min/max of an integer 5.5 -> 0.34 ms; index +0.17% |
WITH (cube_minmax = false) |
sift_col option superunit |
0 | 16 | whole-table aggregates 4 to 7 times faster; index +1.0% | WITH (superunit = 0) |
sift.table_auto_defrag (server setting, with sift.async_maintenance, on in 3.2 too) |
off | on | four clients, update/insert/delete mix on 200,000 rows: 7,536 against 5,092 transactions a second, range read p99 16.8 against 34.2 ms; a table that only appends starts no pass | sift.table_auto_defrag = off |
sift.col_auto_merge (server setting, with sift.async_maintenance, on in 3.2 too) |
off | on | 1M rows, 3% of the rows changed per round: the units answerable from their pre-aggregates 32% to 51% (wide rows), 33% to 83% (mixed workload); total WAL +0.9% to +1.9% (full-page images on, a checkpoint per round), +0.0% with them off (the rewrite then waits for the WAL: none in 20 rounds); applies to the existing sift_col indexes as soon as the new library is loaded, and the CREATE INDEX of a new one says so in a NOTICE |
sift.col_auto_merge = off |
sift_col option key_columns |
none | auto: the foreign key and _id columns of type int2, int4, int8 or date that are not unique and not a cube dimension, where their key lists or bitmaps fit the 3% of a unit; CREATE INDEX and REINDEX say which in a NOTICE |
count(DISTINCT fk) 2.0 to 8.6 times faster on the foreign keys of three 200,000-row tables; index +1.1% to +1.4% |
WITH (key_columns = 'off'), or sift.col_format_minor = 14 |
sift_col option calendar_columns |
none | all date, timestamp and timestamptz columns | date_trunc / extract group-bys of a date 3.7 to 4.5 times faster; index +1.8% for two columns |
WITH (calendar_columns = 'off') |
sift.table_wide_rows |
off | on | a row over the write buffer is accepted (up to 1 GB) instead of refused; no cost for rows that fit | SET sift.table_wide_rows = off |
sift.table_bulk_groups |
off | on | COPY of 200,000 rows: CPU -25%, WAL 44 -> 1 MB |
SET sift.table_bulk_groups = off |
5. First release that reads each format
| Format | First release |
|---|---|
| BM25 metapage minor 1.2 (segment formats 1 and 2) | 3.0 |
| BM25 metapage minor 1.3 (segment format 3) | 3.1 |
| BM25 metapage minor 1.4 (segment format 4, the position stream in blocks) | 3.7 |
| column format 3 | 3.0 |
column format 4 (tags and kinds, version section of a sift_table) |
3.1 |
| column format 5 (wide leaves), 6 (complete JSON kinds) | 3.2 |
column format 7 (cube_minmax), 8 (the calendar cells of a date or timestamp in the cube of a sift_table, or of a sift_col index with calendar_columns), 9 (rollup_columns), 10 (key_columns, a summary of the keys), 11 (the write buffer of a sift_table with a row on tail pages), 12 (superunit), 13 (order_columns), 14 (a string dictionary of 1,024 strings or more, stored in blocks to be read in place), 15 (key_columns, a key list or bitmap) |
3.3 |
column format 16 (point_tid_columns) |
3.3 |
column format 17 (key_cut), 18 (heads of the sorted runs), 19 (the exceptions of a NULL-heavy leaf), 20 (hll_columns), 21 (order_array_columns) |
3.4 |
column format 22 (key_table, the key table of a segment) |
3.5 |
column format 24 (count_columns, the row counts of a text dictionary) |
3.5 |
column format 25 to 32 (the hash of the lookup dictionaries, the containment lanes of range, inet and cidr columns, the text signatures, the finer global order of order_array_columns for dates and text) |
3.6 |
column format 33 to 39 (the point directories at ceil(keys * 4 / 3) slots, the chunk table before the directory, the rollup lengths, the directory records at the end of the dictionary section, the packed order of a dictionary searched in place) |
3.7; 3.6 and older refuse them |
| column format 40 | 4.0; 3.7 and older refuse the segments; a list in order_array_columns keeps complete arrays |
column format 41 (a compact chunk in an order array; order_layout auto, the default, writes it for spread keys such as a timestamp) |
4.0; 3.7 and older refuse the segments; order_layout = 'whole' and a REINDEX, or sift.col_format_minor 14, write what they read |
| column format 42 (the order array of a text column names its strings by dictionary code) | 4.0; 3.7 and older refuse the segments; sift.col_format_minor 14 and a REINDEX write what they read |
| column format 44 (the key journal of a connected parent index and the found flags of a connected child, a write buffer of references to heap tuples, seekable and positional order chunks and the packed table of groups of the second order) | 4.3; 4.2 and older refuse the segments; sift.col_format_minor 14 and a REINDEX write what they read, with connect = 'off' and sift.col_wbuf_ref = off for the connection and the references |
scripts/upgrade_check.sql holds this table; a release that adds a format adds its row there.
Schema sift is open to PUBLIC since 3.3
Up to 3.2 a role needed GRANT USAGE ON SCHEMA sift to call any sift.* function or read a sift.* view (permission denied for schema sift). The install and ALTER EXTENSION pg_sift UPDATE to 3.3 grant it to PUBLIC: existing grants stay and are no longer needed, and nothing else is granted (the functions check the table or index they read; sift.table_paths is not readable). A database that does not want it runs REVOKE USAGE ON SCHEMA sift FROM PUBLIC after the update; the next update grants it again. PostgreSQL creates the schema named in the control file but does not make it a member of the extension (ALTER EXTENSION pg_sift ADD SCHEMA sift is refused), so DROP EXTENSION leaves it empty, as in 3.2, with the privileges it had; CREATE EXTENSION then gives the objects and privileges of a fresh install again, and DROP SCHEMA sift removes it.
Known issue of 3.2.0, 3.2.1 and 3.2.2: a role cannot drop anything
In 3.2.0, 3.2.1 and 3.2.2 the event trigger function sift.table_paths_drop() is not SECURITY DEFINER, so a DROP by a role that is neither a superuser nor the extension owner fails with permission denied for schema sift (any table, index or schema, also one without pg_sift). ALTER EXTENSION pg_sift UPDATE to 3.3 fixes it. Until then, run once in each database with the extension, as a superuser (and again after a restore from a dump, which creates the extension from the 3.2 script):
ALTER FUNCTION sift.table_paths_drop() SECURITY DEFINER SET search_path = pg_catalog, pg_temp;
Known issue of 4.0.0 to 4.1.0: aggregates could count old versions of rows
Fixed in 4.1.1 (install the files and restart the server; nothing to update in the database). In 4.0.0, 4.0.1, 4.0.2, 4.0.3 and 4.1.0 an aggregate over a sift_col index with track_changes (the default for a table with a primary key) could count the old versions of rows that a transaction had updated more than once: the first statement of a new session that needed a table the backends share (the dictionary of a text or varchar key, the statistics of the index, the choices of the planner) was a write (UPDATE, INSERT ... ON CONFLICT DO UPDATE, DELETE, COPY), the shared memory was attached inside a subtransaction, and the command id advanced under that statement. Reproduced on 4.0.0, 4.0.2, 4.0.3 and 4.1.0 (4.0.1 shares the code) with a text key; the cases with an integer key did not show it. Before 4.0.0 a read checks the heap, so 3.x does not show it.
A second form: an UPDATE, MERGE, INSERT ... ON CONFLICT DO UPDATE or a cursor UPDATE ... WHERE CURRENT OF that calls a function that runs SQL inside the statement (a plpgsql function with PERFORM or EXECUTE, a trigger that updates the table) advanced the command id inside the statement, and the entry of each version the statement ended was not removed after the first call, so the rows were counted twice. A third: a bulk UPDATE of a table that also has a sift (BM25) index, which seals its segments in a subtransaction of the statement. Confirmed on 4.1.0 and expected on every release since 4.0.0.
If you cannot update yet: ALTER INDEX <index> SET (track_changes = 'off') on each sift_col index of a table with a primary key or another tracked key. It takes a ShareUpdateExclusiveLock, rewrites nothing, needs no REINDEX (35 ms on 600,000 rows, and about 0.1 s for the SET (track_changes = 'id') that turns it back on), avoids all three routes, and repairs an index that was already wrong because every row is then checked against the heap; REINDEX alone repairs it only until the next such statement. The price: on a table that has churned and was not vacuumed, the statements after an update check more rows in the heap (the instructions of the next statement rose by 27% to 53% in the cases measured), nothing after a VACUUM; writes get cheaper; the first statement of a session is the same. After the update, ALTER INDEX <index> SET (track_changes = 'id') turns the tracking back on. For the first route only, ALTER SYSTEM SET sift.col_dict_shared_cache_kb = 0 (with sift.col_leaf_cache_kb = 0 and sift.verdict_cache_kb = 0), then SELECT pg_reload_conf() (or the same in postgresql.conf, or ALTER DATABASE .. SET; superuser settings, no restart) also avoids it: each backend then parses its own dictionaries and keeps its own statistics (what docs/OPTIONS.md gives for these settings). Putting pg_sift in shared_preload_libraries does not avoid it. See also Reading without the heap (track_changes).
The wrong counts that 4.0.0 to 4.1.0 left in an index, and 4.1.2
Installing 4.1.1 stopped new wrong counts; it did not take away the old versions that an earlier release had left in the log of an index, so such an index went on counting them after the update (ALTER EXTENSION UPDATE changes nothing there; reproduced from 4.0.3 and 4.1.0 to 4.1.1 and 4.2 with the three routes above), until a REINDEX, ALTER INDEX .. SET (track_changes = 'off') and back, or a VACUUM that vacuumed the indexes (PostgreSQL skips that when fewer than 2% of the pages hold a dead row: 30 stale entries of a 300,000-row table stayed through a plain VACUUM). 4.1.2 repairs them by itself, with the files installed and the server restarted and no other step:
- The log of a
sift_colindex withtrack_changesthat an earlier release wrote (flags 1 in its header) is not taken for complete any more: its reads check the heap, so their answers are the heap’s at once (sift.col_trackingshowscompletetrue andheap_freefalse). The cost is that of an index withtrack_changes = 'off'on a table that has churned and was not vacuumed (600,000 rows, 30,000 each updated, deleted and inserted, the second statement of a session, millions of instructions, heap-free against checked: one entity 1.5 against 2.3, a set of 75 entities 5.6 against 7.2, the full group 57.6 against 73.4), nothing on a vacuumed table. - It is re-baselined by the first of: the next
VACUUMof the table (also one that skips the indexes), the first read that checks the heap for it (the maintenance worker,sift.col_auto_mergeon, so a table nobody writes or vacuums is repaired too), and the maintenance worker that a write of the table asks for. The step writes a tombstone for every heap address that no version is live for, marks the log (flags 3), and the heap-free reads resume. 600,000 rows, 30,000 of them updated, deleted and inserted since the last VACUUM: 111 to 119 ms and 7 to 14 MB of WAL; after a VACUUM 71 ms and 3 kB. It holds aShareUpdateExclusiveLockon the table (writers go on, a VACUUM waits or skips). The re-baseline of an index that was fine changes nothing in its answers. - A standby cannot write the log: its reads check the heap until the re-baseline of the primary reaches it by replication.
- An earlier library that reads a log with flags 3 takes it for incomplete and checks the heap, which is right.
- An index created, reindexed or rebuilt by
ALTER INDEX .. SET (track_changes = ..)by 4.1.2 or later is marked at once. - After
pg_upgrade, a dump restore with triggers disabled, orREINDEX CONCURRENTLY, nothing is repaired by itself. The restore recreates the triggers with a new xmin, so the witnesses of the log no longer match: every trackedsift_colindex has flags 1,triggersfalse andheap_freefalse (damaged or not; since 4.0.0), the worker,VACUUMand writes change nothing, and the answers are right, taken from the heap. RunREINDEX INDEX <index>orALTER INDEX <index> SET (track_changes = '<value>')(the value the index had: its key column,'auto'or'id') once for every trackedsift_colindex, one by one; the indexes that are still to do:
SELECT c.oid::regclass AS index FROM pg_class c JOIN pg_am a ON a.oid = c.relam, LATERAL sift.col_tracking(c.oid) t WHERE a.amname = 'sift_col' AND t.track_changes AND NOT t.heap_free;
From 4.2 on the maintenance worker, VACUUM or a read that has to check the heap repairs it by itself (see the pg_upgrade section above): nothing to run.
What is tested
| What | Test |
|---|---|
the preflight on a clean database, with --to, with options, settings and test objects left behind, on an older extension and on an index the library refuses |
test/t/upgrade_check.sh |
pg_upgrade --check and the upgrade of sift, sift_col and sift_table objects (tablespace, wide leaf types, JSON paths, open write buffers), answers, sift.verify, sift.col_verify, writes and merges afterwards |
test/t/pg_upgrade.sh |
a same-major pg_upgrade of the BM25 indexes, ALTER EXTENSION UPDATE along every release |
test/t/extension_lifecycle.sh |
| the previous releases' libraries write, this tree reads (and the other way) | test/t/old_lib_refusal.sh, test/t/compat_31_column.sh, and scripts/release.sh |