Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1k18nr-0008Tt-O6 for pgsql-hackers@arkaria.postgresql.org; Thu, 30 Jul 2020 13:41:00 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.92) (envelope-from ) id 1k18nq-0001In-5r for pgsql-hackers@arkaria.postgresql.org; Thu, 30 Jul 2020 13:40:58 +0000 Received: from makus.postgresql.org ([2001:4800:3e1:1::229]) by malur.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1k18np-0001Ig-Nw for pgsql-hackers@lists.postgresql.org; Thu, 30 Jul 2020 13:40:57 +0000 Received: from mail.postgrespro.ru ([93.174.131.139]) by makus.postgresql.org with esmtp (Exim 4.92) (envelope-from ) id 1k18ni-0007Wu-A8 for pgsql-hackers@lists.postgresql.org; Thu, 30 Jul 2020 13:40:56 +0000 Received: from localhost (localhost [127.0.0.1]) by mail.postgrespro.ru (Postfix) with ESMTP id 99A8321C2C1D; Thu, 30 Jul 2020 16:40:47 +0300 (MSK) X-Virus-Scanned: Debian amavisd-new at postgrespro.ru X-Spam-Flag: NO X-Spam-Score: 0 X-Spam-Level: X-Spam-Status: No, score=x tagged_above=-99 required=4 WHITELISTED tests=[] autolearn=unavailable Received: from [172.20.10.4] (unknown [213.87.146.250]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (Client did not present a certificate) by mail.postgrespro.ru (Postfix) with ESMTPSA id 4A87921C29B2; Thu, 30 Jul 2020 16:40:47 +0300 (MSK) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=postgrespro.ru; s=mail; t=1596116447; bh=01N9B8emBJ2NiRE4z0pdBxo/ydBGP/pWdC79KJGETm0=; h=Subject:To:Cc:References:From:Date:In-Reply-To; b=TwV1e/WOa2AEqaxg4c7h7jlbRaXETs7OfECG5dFVQMT3lPRmkakRveu6SSPcpxtgf l5vJ1pi1RXfEn4fSSCG/MRIo50Y3KrIfXqTsz2cIVySyl4Amtv20DBd2jZMkk8k5cx 5ev1sZ8WQcqO9fJh2GkgI57C5UoWgKUN6v8jTX6g= Subject: Re: [BUG] Error in BRIN summarization To: Alvaro Herrera Cc: pgsql-hackers@lists.postgresql.org References: <20200727172529.GA12531@alvherre.pgsql> From: Anastasia Lubennikova Message-ID: <2588667e-d07d-7e10-74e2-7e1e46194491@postgrespro.ru> Date: Thu, 30 Jul 2020 16:40:46 +0300 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Thunderbird/68.10.0 MIME-Version: 1.0 In-Reply-To: <20200727172529.GA12531@alvherre.pgsql> Content-Type: multipart/alternative; boundary="------------2136C76216F91538D099B3C5" Content-Language: en-US List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Precedence: bulk This is a multi-part message in MIME format. --------------2136C76216F91538D099B3C5 Content-Type: text/plain; charset=utf-8; format=flowed Content-Transfer-Encoding: 8bit On 27.07.2020 20:25, Alvaro Herrera wrote: > On 2020-Jul-27, Anastasia Lubennikova wrote: > >> Here is the updated version of the fix. >> The problem can be reproduced on all supported versions, so I suggest to >> backpatch it. >> Code slightly changed in v12, so here are two patches: one for versions 9.5 >> to 11 and another for versions from 12 to master. >> >> >> >> (I was also considering whether it needs to be a loop to reobtain root >> tuples, in case a concurrent transaction can create a new item while >> we're checking that item; but I don't think that can really happen for >> one individual tuple.) I don't think we need a recheck for a single tuple, because we only care about finding its root, which simply must exist somewhere on this page, as concurrent pruning is not allowed. We also may catch root_offsets[] for subsequent tuples, but it's okay if we don't. These tuples will do the same recheck on their turn. While testing this fix, Alexander Lakhin spotted another problem. I simplified  the test case to this: 1) prepare a table with brin index create table tbl (iint, bchar(1000)) with (fillfactor=10); insert into tbl select i, md5(i::text) from generate_series(0,1000) as i; create index idx on tbl using brin(i, b) with (pages_per_range=1); 2) run brin_desummarize_range() in a loop: echo "-- desummarize all ranges SELECT FROM generate_series(0, pg_relation_size('tbl')/8192 - 1) as i, lateral (SELECT brin_desummarize_range('idx', i)) as d; -- summarize them back VACUUM tbl" > brin_desum_test.sql pgbench postgres -f  brin_desum_test.sql -n -T 120 3) run a search that invokes bringetbitmap: set enable_seqscan to off;  explain analyze select * from tbl where i>10 and i<100; \watch 1 After a few runs, it will fail with "ERROR: corrupted BRIN index: inconsistent range map" The problem is caused by a race in page locking in brinGetTupleForHeapBlock [1]: (1) bitmapsan locks revmap->rm_currBuf and finds the address of the tuple on a regular page "page", then unlocks revmap->rm_currBuf (2) in another transaction desummarize locks both revmap->rm_currBuf and "page", cleans up the tuple and unlocks both buffers (1) bitmapscan locks buffer, containing "page", attempts to access the tuple and fails to find it At first, I tried to fix it by holding the lock on revmap->rm_currBuf until we locked the regular page, but it causes a deadlock with brinsummarize(), It can be easily reproduced with the same test as above. Is there any rule about the order of locking revmap and regular pages in brin? I haven't found anything in README. As an alternative, we can leave locks as is and add a recheck, before throwing an error. What do you think? [1] https://github.com/postgres/postgres/blob/master/src/backend/access/brin/brin_revmap.c#L269 -- Anastasia Lubennikova Postgres Professional: http://www.postgrespro.com The Russian Postgres Company --------------2136C76216F91538D099B3C5 Content-Type: text/html; charset=utf-8 Content-Transfer-Encoding: 8bit
On 27.07.2020 20:25, Alvaro Herrera wrote:
On 2020-Jul-27, Anastasia Lubennikova wrote:

Here is the updated version of the fix.
The problem can be reproduced on all supported versions, so I suggest to
backpatch it.
Code slightly changed in v12, so here are two patches: one for versions 9.5
to 11 and another for versions from 12 to master.



(I was also considering whether it needs to be a loop to reobtain root
tuples, in case a concurrent transaction can create a new item while
we're checking that item; but I don't think that can really happen for
one individual tuple.)
I don't think we need a recheck for a single tuple, because we only care about finding its root, which simply must exist somewhere on this page, as concurrent pruning is not allowed. We also may catch root_offsets[] for subsequent tuples, but it's okay if we don't. These tuples will do the same recheck on their turn.


While testing this fix, Alexander Lakhin spotted another problem. I simplified  the test case to this:

1) prepare a table with brin index

create table tbl (i int, b char(1000)) with (fillfactor=10);
insert into tbl select i, md5(i::text) from generate_series(0,1000) as i;
create index idx on tbl using brin(i, b) with (pages_per_range=1);

2) run brin_desummarize_range() in a loop:

echo "-- desummarize all ranges
 SELECT FROM generate_series(0, pg_relation_size('tbl')/8192 - 1) as i, lateral (SELECT brin_desummarize_range('idx', i)) as d; 
-- summarize them back 
VACUUM tbl" > brin_desum_test.sql

pgbench postgres -f  brin_desum_test.sql -n -T 120


3) run a search that invokes bringetbitmap:

 set enable_seqscan to off;
 explain analyze select * from tbl where i>10 and i<100; \watch 1

After a few runs, it will fail with "ERROR: corrupted BRIN index: inconsistent range map"

The problem is caused by a race in page locking in brinGetTupleForHeapBlock [1]:

(1) bitmapsan locks revmap->rm_currBuf and finds the address of the tuple on a regular page "page", then unlocks revmap->rm_currBuf
(2) in another transaction desummarize locks both revmap->rm_currBuf and "page", cleans up the tuple and unlocks both buffers
(1) bitmapscan locks buffer, containing "page", attempts to access the tuple and fails to find it


At first, I tried to fix it by holding the lock on revmap->rm_currBuf until we locked the regular page, but it causes a deadlock with brinsummarize(), It can be easily reproduced with the same test as above.
Is there any rule about the order of locking revmap and regular pages in brin? I haven't found anything in README.

As an alternative, we can leave locks as is and add a recheck, before throwing an error.

What do you think?

[1] https://github.com/postgres/postgres/blob/master/src/backend/access/brin/brin_revmap.c#L269

-- 
Anastasia Lubennikova
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
--------------2136C76216F91538D099B3C5--