-- A promotion or merge that fails (injected after a few pages) gives its blocks back, never fails -- VACUUM or an insert, and backs off instead of being retried at once. CREATE EXTENSION pg_sift; CREATE EXTENSION pg_sift_test; SET sift.async_maintenance = off; SET enable_seqscan = off; SET client_min_messages = warning; CREATE TABLE mf (id serial, body text) WITH (autovacuum_enabled = off); INSERT INTO mf (body) SELECT 'seed word' || (i % 50) || ' common' FROM generate_series(1, 2000) i; CREATE INDEX mf_idx ON mf USING sift (body) WITH (write_buffer_limit = 64); -- one document is about a segment of its own: 12000 distinct terms fill 128 kB of write buffer pages CREATE FUNCTION big(n int) RETURNS text LANGUAGE sql AS $$ SELECT string_agg('t' || n || 'x' || g, ' ') FROM generate_series(1, 12000) g $$; -- an explicit promotion fails and leaves nothing behind: no orphan block, the index reads as before INSERT INTO mf (body) SELECT big(i) FROM generate_series(1, 3) i; SET sift.test_fail_pages = 3; SELECT sift.promote('mf_idx'); ERROR: sift test: injected failure after 3 pages RESET sift.test_fail_pages; SELECT sift._test_check('mf_idx') AS orphans; orphans --------- 0 (1 row) SELECT sift._test_maint_failures('mf_idx') AS failures; failures ---------- 0 (1 row) SELECT count(*) FROM mf WHERE body @@> 'common'; count ------- 2000 (1 row) SELECT count(*) FROM mf WHERE body @@> 't2x100'; count ------- 1 (1 row) SELECT sift.verify('mf_idx', true); verify -------- (1 row) -- so does a failing merge, and a successful step clears the failure count SELECT sift.promote('mf_idx') >= 0 AS promoted; promoted ---------- t (1 row) INSERT INTO mf (body) SELECT 'again ' || big(i) FROM generate_series(1, 3) i; SELECT sift.promote('mf_idx') >= 0 AS promoted; promoted ---------- t (1 row) SET sift.test_fail_pages = 3; SELECT sift.merge('mf_idx', true); ERROR: sift test: injected failure after 3 pages RESET sift.test_fail_pages; SELECT sift._test_check('mf_idx') AS orphans; orphans --------- 0 (1 row) SELECT sift.merge('mf_idx', true) >= 0 AS merged; merged -------- t (1 row) SELECT sift._test_maint_failures('mf_idx') AS failures; failures ---------- 0 (1 row) SELECT sift.verify('mf_idx', true); verify -------- (1 row) -- VACUUM does not fail with the step: the warning is the only trace, then the failure is counted INSERT INTO mf (body) SELECT 'vac ' || big(i) FROM generate_series(1, 3) i; SET sift.test_fail_pages = 3; VACUUM mf; WARNING: sift index "mf_idx": maintenance failed: sift test: injected failure after 3 pages DETAIL: It is retried later, after a growing delay. RESET sift.test_fail_pages; SELECT sift._test_maint_failures('mf_idx') AS failures; failures ---------- 1 (1 row) SELECT sift._test_check('mf_idx') AS orphans; orphans --------- 0 (1 row) -- sift.workers keeps the failure for whoever looks: what failed, when, and when the background tries again SELECT index, kind, failures, error_code, last_error, next_retry > failed_at AS retry_later, due FROM sift.workers WHERE index = 'mf_idx'::regclass; index | kind | failures | error_code | last_error | retry_later | due --------+------+----------+------------+-------------------------------------------+-------------+----- mf_idx | sift | 1 | XX000 | sift test: injected failure after 3 pages | t | (1 row) VACUUM mf; SELECT sift._test_maint_failures('mf_idx') AS failures; failures ---------- 0 (1 row) SELECT failures, error_code, last_error, next_retry FROM sift.workers WHERE index = 'mf_idx'::regclass; failures | error_code | last_error | next_retry ----------+------------+------------+------------ 0 | | | (1 row) SELECT sift.verify('mf_idx', true); verify -------- (1 row) -- a metapage written before the note reads as no note, and bytes no build wrote are never shown as text SELECT sift._test_meta_note('mf_idx', 'old'); _test_meta_note ----------------- (1 row) SELECT failures, failed_at, error_code, last_error, next_retry IS NOT NULL AS has_retry, running FROM sift.workers WHERE index = 'mf_idx'::regclass; failures | failed_at | error_code | last_error | has_retry | running ----------+-----------+------------+------------+-----------+--------- 1 | | | | t | f (1 row) SELECT sift._test_meta_note('mf_idx', 'junk'); _test_meta_note ----------------- (1 row) SELECT failures, error_code IS NOT NULL AS has_code, last_error FROM sift.workers WHERE index = 'mf_idx'::regclass; failures | has_code | last_error ----------+----------+-------------- 1 | t | (unreadable) (1 row) SELECT sift.verify('mf_idx', true); verify -------- (1 row) -- inserts reach the point where they would merge for room (112 of 128 entries) with every step -- failing: they go on, and the failing step is tried once, not at every seal CREATE TABLE mr (id serial, body text) WITH (autovacuum_enabled = off); CREATE INDEX mr_idx ON mr USING sift (body) WITH (write_buffer_limit = 64); SET sift.test_fail_pages = 1; SET client_min_messages = error; DO $$ BEGIN FOR i IN 1..118 LOOP INSERT INTO mr (body) SELECT 'room ' || big(1000 + i); END LOOP; END $$; RESET sift.test_fail_pages; SET client_min_messages = warning; SELECT count(*) >= 112 AS directory_nearly_full FROM sift.segments('mr_idx'); directory_nearly_full ----------------------- t (1 row) SELECT sift._test_maint_failures('mr_idx') BETWEEN 1 AND 3 AS backed_off; backed_off ------------ t (1 row) SELECT count(*) FROM mr WHERE body @@> 'room'; count ------- 118 (1 row) SELECT sift.promote('mr_idx') >= 0 AS promoted; promoted ---------- t (1 row) SELECT sift._test_maint_failures('mr_idx') AS failures; failures ---------- 0 (1 row) SELECT sift._test_check('mr_idx') AS orphans; orphans --------- 0 (1 row) SELECT sift.verify('mr_idx', true); verify -------- (1 row) DROP TABLE mf, mr; DROP FUNCTION big(int); DROP EXTENSION pg_sift_test; DROP EXTENSION pg_sift;