Thread: Corruption with IMMUTABLE functions in index expression.

Corruption with IMMUTABLE functions in index expression.

From
Prabhat Sahu
Date:
Hi All,

While using IMMUTABLE functions in index expression, we are getting below corruption on HEAD.

postgres=# CREATE TABLE  tab1 (c1 numeric, c2 numeric);
CREATE TABLE

postgres=# INSERT INTO  tab1 values (10, 100);
INSERT 0 1

postgres=# CREATE OR REPLACE FUNCTION func1(var1 numeric)
RETURNS NUMERIC AS $$
DECLARE
result numeric;
BEGIN
 SELECT c2 into result FROM  tab1 WHERE c1=var1;
 RETURN result;
END;
$$ LANGUAGE plpgsql IMMUTABLE;
CREATE FUNCTION

-- When using the IMMUTABLE function in creating an index for the first time, it is working fine.
postgres=# CREATE INDEX idx1 ON  tab1(func1(c1));
CREATE INDEX

-- Executing the similar query for 2nd time, We are getting the error
postgres=# CREATE INDEX idx2 ON  tab1(func1(c1));
ERROR:  could not read block 0 in file "base/13675/16391": read only 0 of 8192 bytes
CONTEXT:  SQL statement "SELECT c2             FROM  tab1 WHERE c1=var1"
PL/pgSQL function func1(numeric) line 5 at SQL statement

--

With Regards,
Prabhat Kumar Sahu
EnterpriseDB: http://www.enterprisedb.com

Re: Corruption with IMMUTABLE functions in index expression.

From
"David G. Johnston"
Date:
On Monday, October 11, 2021, Prabhat Sahu <prabhat.sahu@enterprisedb.com> wrote:
While using IMMUTABLE functions in index expression, we are getting below corruption on HEAD.

That function is not actually immutable (the system doesn’t check whether your claim of immutability and the function definition match, its up to you to know and specify the correct label for what the function does) so not our problem.  Write a trigger instead.  

David J.

Re: Corruption with IMMUTABLE functions in index expression.

From
Andrey Borodin
Date:

> 11 окт. 2021 г., в 20:47, David G. Johnston <david.g.johnston@gmail.com> написал(а):
>
> On Monday, October 11, 2021, Prabhat Sahu <prabhat.sahu@enterprisedb.com> wrote:
> While using IMMUTABLE functions in index expression, we are getting below corruption on HEAD.
>
> That function is not actually immutable (the system doesn’t check whether your claim of immutability and the function
definitionmatch, its up to you to know and specify the correct label for what the function does) so not our problem.
Writea trigger instead.   
+1, but the error is strange. This might be a sign of some wrong assumption somewhere. My wild guess is that metapage
isread before it was written. 

Best regards, Andrey Borodin.


Re: Corruption with IMMUTABLE functions in index expression.

From
Tomas Vondra
Date:

On 10/11/21 18:08, Andrey Borodin wrote:
> 
> 
>> 11 окт. 2021 г., в 20:47, David G. Johnston <david.g.johnston@gmail.com> написал(а):
>>
>> On Monday, October 11, 2021, Prabhat Sahu <prabhat.sahu@enterprisedb.com> wrote:
>> While using IMMUTABLE functions in index expression, we are getting below corruption on HEAD.
>>
>> That function is not actually immutable (the system doesn’t check whether your claim of immutability and the
functiondefinition match, its up to you to know and specify the correct label for what the function does) so not our
problem. Write a trigger instead.
 
> +1, but the error is strange. This might be a sign of some wrong assumption somewhere. My wild guess is that metapage
isread before it was written.
 
> 

True, but I can't reproduce it. So either the build is broken in some 
way, or perhaps there's something else going on. What would be quite 
helpful is a backtrace showing why the error was triggered. i.e. set a 
breakpoint on the ereport in mdread().


regards

-- 
Tomas Vondra
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company



Re: Corruption with IMMUTABLE functions in index expression.

From
Tom Lane
Date:
"David G. Johnston" <david.g.johnston@gmail.com> writes:
> On Monday, October 11, 2021, Prabhat Sahu <prabhat.sahu@enterprisedb.com>
> wrote:
>> While using IMMUTABLE functions in index expression, we are getting below
>> corruption on HEAD.

> That function is not actually immutable (the system doesn’t check whether
> your claim of immutability and the function definition match, its up to you
> to know and specify the correct label for what the function does) so not
> our problem.  Write a trigger instead.

Yeah.  What is happening is that the function's SELECT on the subject
table is trying to examine the not-yet-valid new index.  While that could
be argued to be a bug, I share David's lack of interest in fixing it,
because I do not believe that there are any cases where a function that
accesses an index's subject table is really going to be immutable.

To prevent this access, we'd have to set pg_index.indisvalid false
initially and then update it to true after the index is built.
We do do that in CREATE INDEX CONCURRENTLY (so you can make this
example work by specifying CONCURRENTLY), but I'm unexcited about
creating bloat in pg_index for the standard case in order to support
a usage scenario that is going to cause you all sorts of other pain.
To paraphrase Henry Spencer: if you lie to the planner, it will get
its revenge.

            regards, tom lane



Re: Corruption with IMMUTABLE functions in index expression.

From
Tom Lane
Date:
Tomas Vondra <tomas.vondra@enterprisedb.com> writes:
> True, but I can't reproduce it. So either the build is broken in some 
> way, or perhaps there's something else going on. What would be quite 
> helpful is a backtrace showing why the error was triggered. i.e. set a 
> breakpoint on the ereport in mdread().

It reproduced as-described for me.  The planner sees the index as
already indisvalid, so it figures it can ask for the tree height:

#0  errfinish (filename=0xa4c15c "md.c", lineno=686, 
    funcname=0xac6a98 <__func__.13643> "mdread") at elog.c:515
#1  0x00000000004dc8fb in mdread (reln=<optimized out>, 
    forknum=<optimized out>, blocknum=0, buffer=0x7fad931b4f80 "") at md.c:682
#2  0x00000000007fd15c in ReadBuffer_common (smgr=0x1d72140, 
    relpersistence=<optimized out>, forkNum=MAIN_FORKNUM, blockNum=0, 
    mode=RBM_NORMAL, strategy=<optimized out>, hit=0x7fff63a1d7af)
    at bufmgr.c:1003
#3  0x00000000007fdb54 in ReadBufferExtended (reln=0x7fad9c4b69d8, 
    forkNum=MAIN_FORKNUM, blockNum=0, mode=<optimized out>, 
    strategy=<optimized out>) at ../../../../src/include/utils/rel.h:548
#4  0x00000000005797f5 in _bt_getbuf (rel=0x7fad9c4b69d8, 
    blkno=<optimized out>, access=1) at nbtpage.c:878
#5  0x0000000000579bc7 in _bt_getrootheight (rel=rel@entry=0x7fad9c4b69d8)
    at nbtpage.c:680
#6  0x000000000078106a in get_relation_info (root=root@entry=0x1d84b28, 
    relationObjectId=59210, inhparent=false, rel=rel@entry=0x1d85290)
    at plancat.c:419
#7  0x0000000000785451 in build_simple_rel (root=0x1d84b28, relid=1, 
    parent=0x0) at relnode.c:308
#8  0x000000000075792f in add_base_rels_to_query (root=root@entry=0x1d84b28, 
    jtnode=<optimized out>) at initsplan.c:122
#9  0x000000000075ac68 in query_planner (root=root@entry=0x1d84b28, 
--Type <RET> for more, q to quit, c to continue without paging--q

            regards, tom lane



Re: Corruption with IMMUTABLE functions in index expression.

From
Peter Geoghegan
Date:
On Mon, Oct 11, 2021 at 9:27 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:
> Yeah.  What is happening is that the function's SELECT on the subject
> table is trying to examine the not-yet-valid new index.  While that could
> be argued to be a bug, I share David's lack of interest in fixing it,
> because I do not believe that there are any cases where a function that
> accesses an index's subject table is really going to be immutable.

Right. It might be different if this was something that users
sometimes expect will work, based on some plausible-though-wrong
understanding of expression indexes. But experience suggests that they
don't.

-- 
Peter Geoghegan



Re: Corruption with IMMUTABLE functions in index expression.

From
Andres Freund
Date:
Hi,

On 2021-10-11 12:27:44 -0400, Tom Lane wrote:
> While that could be argued to be a bug, I share David's lack of interest in
> fixing it, because I do not believe that there are any cases where a
> function that accesses an index's subject table is really going to be
> immutable.

> To prevent this access, we'd have to set pg_index.indisvalid false
> initially and then update it to true after the index is built.
> We do do that in CREATE INDEX CONCURRENTLY (so you can make this
> example work by specifying CONCURRENTLY), but I'm unexcited about
> creating bloat in pg_index for the standard case in order to support
> a usage scenario that is going to cause you all sorts of other pain.
> To paraphrase Henry Spencer: if you lie to the planner, it will get
> its revenge.

I agree that there's not much point in making this really "work", but perhaps
we could try to generate a more useful error message, without incurring undue
overhead? I think there've been a few reports of this over the years,
including some internally to postgres, e.g. during catalog table index
rebuilds.

Perhaps we could set pg_index.indisvalid to false initially, and if opening an
index where pg_index.indisvalid error out with a different error message if
TransactionIdIsCurrentTransactionId(xmin). And then use an inplace update to
set indisvalid to true, to avoid the bloat?

Greetings,

Andres Freund



Re: Corruption with IMMUTABLE functions in index expression.

From
Tom Lane
Date:
Andres Freund <andres@anarazel.de> writes:
> Perhaps we could set pg_index.indisvalid to false initially, and if opening an
> index where pg_index.indisvalid error out with a different error message if
> TransactionIdIsCurrentTransactionId(xmin). And then use an inplace update to
> set indisvalid to true, to avoid the bloat?

I still can't get excited about it ... but yeah, update-in-place would
be enough to remove the bloat objection.  I doubt we need any code
changes beyond changing the indisvalid state.

            regards, tom lane



Re: Corruption with IMMUTABLE functions in index expression.

From
Andres Freund
Date:
Hi,

On 2021-10-11 14:59:22 -0400, Tom Lane wrote:
> Andres Freund <andres@anarazel.de> writes:
> > Perhaps we could set pg_index.indisvalid to false initially, and if opening an
> > index where pg_index.indisvalid error out with a different error message if
> > TransactionIdIsCurrentTransactionId(xmin). And then use an inplace update to
> > set indisvalid to true, to avoid the bloat?
> 
> I still can't get excited about it ...

Understandable, me neither...


> I doubt we need any code changes beyond changing the indisvalid state.

I was thinking we'd want to throw an error if an index that's being created is
accessed during the index build, rather than just not include it in
planning...


Greetings,

Andres Freund



Re: Corruption with IMMUTABLE functions in index expression.

From
Tom Lane
Date:
Andres Freund <andres@anarazel.de> writes:
> On 2021-10-11 14:59:22 -0400, Tom Lane wrote:
>> I doubt we need any code changes beyond changing the indisvalid state.

> I was thinking we'd want to throw an error if an index that's being created is
> accessed during the index build, rather than just not include it in
> planning...

AFAICT we *will* throw an error, just not a very intelligible one.
But until someone's shown another way to reach that error besides
the planner's path, I'm not thinking we need to expend effort on
making the error nicer.

            regards, tom lane