mirror of
https://git.postgresql.org/git/postgresql.git
synced 2024-08-28 17:47:19 +02:00
Teach contrib/amcheck's bt_index_parent_check() function to take advantage of the uniqueness property of heapkeyspace indexes in support of a new verification option: non-pivot tuples (non-highkey tuples on the leaf level) can optionally be re-found using a new search for each, that starts from the root page. If a tuple cannot be re-found, report that the index is corrupt. The new "rootdescend" verification option is exhaustive, and can therefore make a call to bt_index_parent_check() take a lot longer. Re-finding tuples during verification is mostly intended as an option for backend developers, since the corruption scenarios that it alone is uniquely capable of detecting seem fairly far-fetched. For example, "rootdescend" verification is much more likely to detect corruption of the least significant byte of a key from a pivot tuple in the root page of a B-Tree that already has at least three levels. Typically, only a few tuples on a cousin leaf page are at risk of "getting overlooked" by index scans in this scenario. The corrupt key in the root page is only slightly corrupt: corrupt enough to give wrong answers to some queries, and yet not corrupt enough to allow the problem to be detected without verifying agreement between the leaf page and the root page, skipping at least one internal page level. The existing bt_index_parent_check() checks never cross more than a single level. Author: Peter Geoghegan Reviewed-By: Heikki Linnakangas Discussion: https://postgr.es/m/CAH2-Wz=yTWnVu+HeHGKb2AGiADL9eprn-cKYAto4MkKOuiGtRQ@mail.gmail.com
174 lines
5.8 KiB
Plaintext
174 lines
5.8 KiB
Plaintext
CREATE TABLE bttest_a(id int8);
|
|
CREATE TABLE bttest_b(id int8);
|
|
CREATE TABLE bttest_multi(id int8, data int8);
|
|
CREATE TABLE delete_test_table (a bigint, b bigint, c bigint, d bigint);
|
|
-- Stabalize tests
|
|
ALTER TABLE bttest_a SET (autovacuum_enabled = false);
|
|
ALTER TABLE bttest_b SET (autovacuum_enabled = false);
|
|
ALTER TABLE bttest_multi SET (autovacuum_enabled = false);
|
|
ALTER TABLE delete_test_table SET (autovacuum_enabled = false);
|
|
INSERT INTO bttest_a SELECT * FROM generate_series(1, 100000);
|
|
INSERT INTO bttest_b SELECT * FROM generate_series(100000, 1, -1);
|
|
INSERT INTO bttest_multi SELECT i, i%2 FROM generate_series(1, 100000) as i;
|
|
CREATE INDEX bttest_a_idx ON bttest_a USING btree (id);
|
|
CREATE INDEX bttest_b_idx ON bttest_b USING btree (id);
|
|
CREATE UNIQUE INDEX bttest_multi_idx ON bttest_multi
|
|
USING btree (id) INCLUDE (data);
|
|
CREATE ROLE bttest_role;
|
|
-- verify permissions are checked (error due to function not callable)
|
|
SET ROLE bttest_role;
|
|
SELECT bt_index_check('bttest_a_idx'::regclass);
|
|
ERROR: permission denied for function bt_index_check
|
|
SELECT bt_index_parent_check('bttest_a_idx'::regclass);
|
|
ERROR: permission denied for function bt_index_parent_check
|
|
RESET ROLE;
|
|
-- we, intentionally, don't check relation permissions - it's useful
|
|
-- to run this cluster-wide with a restricted account, and as tested
|
|
-- above explicit permission has to be granted for that.
|
|
GRANT EXECUTE ON FUNCTION bt_index_check(regclass) TO bttest_role;
|
|
GRANT EXECUTE ON FUNCTION bt_index_parent_check(regclass) TO bttest_role;
|
|
GRANT EXECUTE ON FUNCTION bt_index_check(regclass, boolean) TO bttest_role;
|
|
GRANT EXECUTE ON FUNCTION bt_index_parent_check(regclass, boolean) TO bttest_role;
|
|
SET ROLE bttest_role;
|
|
SELECT bt_index_check('bttest_a_idx');
|
|
bt_index_check
|
|
----------------
|
|
|
|
(1 row)
|
|
|
|
SELECT bt_index_parent_check('bttest_a_idx');
|
|
bt_index_parent_check
|
|
-----------------------
|
|
|
|
(1 row)
|
|
|
|
RESET ROLE;
|
|
-- verify plain tables are rejected (error)
|
|
SELECT bt_index_check('bttest_a');
|
|
ERROR: "bttest_a" is not an index
|
|
SELECT bt_index_parent_check('bttest_a');
|
|
ERROR: "bttest_a" is not an index
|
|
-- verify non-existing indexes are rejected (error)
|
|
SELECT bt_index_check(17);
|
|
ERROR: could not open relation with OID 17
|
|
SELECT bt_index_parent_check(17);
|
|
ERROR: could not open relation with OID 17
|
|
-- verify wrong index types are rejected (error)
|
|
BEGIN;
|
|
CREATE INDEX bttest_a_brin_idx ON bttest_a USING brin(id);
|
|
SELECT bt_index_parent_check('bttest_a_brin_idx');
|
|
ERROR: only B-Tree indexes are supported as targets for verification
|
|
DETAIL: Relation "bttest_a_brin_idx" is not a B-Tree index.
|
|
ROLLBACK;
|
|
-- normal check outside of xact
|
|
SELECT bt_index_check('bttest_a_idx');
|
|
bt_index_check
|
|
----------------
|
|
|
|
(1 row)
|
|
|
|
-- more expansive tests
|
|
SELECT bt_index_check('bttest_a_idx', true);
|
|
bt_index_check
|
|
----------------
|
|
|
|
(1 row)
|
|
|
|
SELECT bt_index_parent_check('bttest_b_idx', true);
|
|
bt_index_parent_check
|
|
-----------------------
|
|
|
|
(1 row)
|
|
|
|
BEGIN;
|
|
SELECT bt_index_check('bttest_a_idx');
|
|
bt_index_check
|
|
----------------
|
|
|
|
(1 row)
|
|
|
|
SELECT bt_index_parent_check('bttest_b_idx');
|
|
bt_index_parent_check
|
|
-----------------------
|
|
|
|
(1 row)
|
|
|
|
-- make sure we don't have any leftover locks
|
|
SELECT * FROM pg_locks
|
|
WHERE relation = ANY(ARRAY['bttest_a', 'bttest_a_idx', 'bttest_b', 'bttest_b_idx']::regclass[])
|
|
AND pid = pg_backend_pid();
|
|
locktype | database | relation | page | tuple | virtualxid | transactionid | classid | objid | objsubid | virtualtransaction | pid | mode | granted | fastpath
|
|
----------+----------+----------+------+-------+------------+---------------+---------+-------+----------+--------------------+-----+------+---------+----------
|
|
(0 rows)
|
|
|
|
COMMIT;
|
|
-- normal check outside of xact for index with included columns
|
|
SELECT bt_index_check('bttest_multi_idx');
|
|
bt_index_check
|
|
----------------
|
|
|
|
(1 row)
|
|
|
|
-- more expansive test for index with included columns
|
|
SELECT bt_index_parent_check('bttest_multi_idx', true);
|
|
bt_index_parent_check
|
|
-----------------------
|
|
|
|
(1 row)
|
|
|
|
-- repeat expansive test for index built using insertions
|
|
TRUNCATE bttest_multi;
|
|
INSERT INTO bttest_multi SELECT i, i%2 FROM generate_series(1, 100000) as i;
|
|
SELECT bt_index_parent_check('bttest_multi_idx', true);
|
|
bt_index_parent_check
|
|
-----------------------
|
|
|
|
(1 row)
|
|
|
|
--
|
|
-- Test for multilevel page deletion/downlink present checks, and rootdescend
|
|
-- checks
|
|
--
|
|
INSERT INTO delete_test_table SELECT i, 1, 2, 3 FROM generate_series(1,80000) i;
|
|
ALTER TABLE delete_test_table ADD PRIMARY KEY (a,b,c,d);
|
|
-- Delete many entries, and vacuum. This causes page deletions.
|
|
DELETE FROM delete_test_table WHERE a > 40000;
|
|
VACUUM delete_test_table;
|
|
-- Delete most entries, and vacuum, deleting internal pages and creating "fast
|
|
-- root"
|
|
DELETE FROM delete_test_table WHERE a < 79990;
|
|
VACUUM delete_test_table;
|
|
SELECT bt_index_parent_check('delete_test_table_pkey', true, true);
|
|
bt_index_parent_check
|
|
-----------------------
|
|
|
|
(1 row)
|
|
|
|
--
|
|
-- BUG #15597: must not assume consistent input toasting state when forming
|
|
-- tuple. Bloom filter must fingerprint normalized index tuple representation.
|
|
--
|
|
CREATE TABLE toast_bug(buggy text);
|
|
ALTER TABLE toast_bug ALTER COLUMN buggy SET STORAGE plain;
|
|
-- pg_attribute entry for toasty.buggy will have plain storage:
|
|
CREATE INDEX toasty ON toast_bug(buggy);
|
|
-- Whereas pg_attribute entry for toast_bug.buggy now has extended storage:
|
|
ALTER TABLE toast_bug ALTER COLUMN buggy SET STORAGE extended;
|
|
-- Insert compressible heap tuple (comfortably exceeds TOAST_TUPLE_THRESHOLD):
|
|
INSERT INTO toast_bug SELECT repeat('a', 2200);
|
|
-- Should not get false positive report of corruption:
|
|
SELECT bt_index_check('toasty', true);
|
|
bt_index_check
|
|
----------------
|
|
|
|
(1 row)
|
|
|
|
-- cleanup
|
|
DROP TABLE bttest_a;
|
|
DROP TABLE bttest_b;
|
|
DROP TABLE bttest_multi;
|
|
DROP TABLE delete_test_table;
|
|
DROP TABLE toast_bug;
|
|
DROP OWNED BY bttest_role; -- permissions
|
|
DROP ROLE bttest_role;
|