Fix BRIN free space computations
A bug in the original free space computation made it possible to return a page which wasn't actually able to fit the item. Since the insertion code isn't prepared to deal with PageAddItem failing, a PANIC resulted ("failed to add BRIN tuple [to new page]"). Add a macro to encapsulate the correct computation, and use it in brin_getinsertbuffer's callers before calling that routine, to raise an early error. I became aware of the possiblity of a problem in this area while working on ccc4c074994d734. There's no archived discussion about it, but it's easy to reproduce a problem in the unpatched code with something like CREATE TABLE t (a text); CREATE INDEX ti ON t USING brin (a) WITH (pages_per_range=1); for length in `seq 8000 8196` do psql -f - <<EOF TRUNCATE TABLE t; INSERT INTO t VALUES ('z'), (repeat('a', $length)); EOF done Backpatch to 9.5, where BRIN was introduced.
This commit is contained in:
parent
531d21b75f
commit
21a4e4a4c9
@ -23,6 +23,16 @@
|
|||||||
#include "utils/rel.h"
|
#include "utils/rel.h"
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Maximum size of an entry in a BRIN_PAGETYPE_REGULAR page. We can tolerate
|
||||||
|
* a single item per page, unlike other index AMs.
|
||||||
|
*/
|
||||||
|
#define BrinMaxItemSize \
|
||||||
|
MAXALIGN_DOWN(BLCKSZ - \
|
||||||
|
(MAXALIGN(SizeOfPageHeaderData + \
|
||||||
|
sizeof(ItemIdData)) + \
|
||||||
|
MAXALIGN(sizeof(BrinSpecialSpace))))
|
||||||
|
|
||||||
static Buffer brin_getinsertbuffer(Relation irel, Buffer oldbuf, Size itemsz,
|
static Buffer brin_getinsertbuffer(Relation irel, Buffer oldbuf, Size itemsz,
|
||||||
bool *extended);
|
bool *extended);
|
||||||
static Size br_page_get_freespace(Page page);
|
static Size br_page_get_freespace(Page page);
|
||||||
@ -58,6 +68,18 @@ brin_doupdate(Relation idxrel, BlockNumber pagesPerRange,
|
|||||||
|
|
||||||
Assert(newsz == MAXALIGN(newsz));
|
Assert(newsz == MAXALIGN(newsz));
|
||||||
|
|
||||||
|
/* If the item is oversized, don't bother. */
|
||||||
|
if (newsz > BrinMaxItemSize)
|
||||||
|
{
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
|
||||||
|
errmsg("index row size %lu exceeds maximum %lu for index \"%s\"",
|
||||||
|
(unsigned long) newsz,
|
||||||
|
(unsigned long) BrinMaxItemSize,
|
||||||
|
RelationGetRelationName(idxrel))));
|
||||||
|
return false; /* keep compiler quiet */
|
||||||
|
}
|
||||||
|
|
||||||
/* make sure the revmap is long enough to contain the entry we need */
|
/* make sure the revmap is long enough to contain the entry we need */
|
||||||
brinRevmapExtend(revmap, heapBlk);
|
brinRevmapExtend(revmap, heapBlk);
|
||||||
|
|
||||||
@ -332,6 +354,18 @@ brin_doinsert(Relation idxrel, BlockNumber pagesPerRange,
|
|||||||
|
|
||||||
Assert(itemsz == MAXALIGN(itemsz));
|
Assert(itemsz == MAXALIGN(itemsz));
|
||||||
|
|
||||||
|
/* If the item is oversized, don't even bother. */
|
||||||
|
if (itemsz > BrinMaxItemSize)
|
||||||
|
{
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
|
||||||
|
errmsg("index row size %lu exceeds maximum %lu for index \"%s\"",
|
||||||
|
(unsigned long) itemsz,
|
||||||
|
(unsigned long) BrinMaxItemSize,
|
||||||
|
RelationGetRelationName(idxrel))));
|
||||||
|
return InvalidOffsetNumber; /* keep compiler quiet */
|
||||||
|
}
|
||||||
|
|
||||||
/* Make sure the revmap is long enough to contain the entry we need */
|
/* Make sure the revmap is long enough to contain the entry we need */
|
||||||
brinRevmapExtend(revmap, heapBlk);
|
brinRevmapExtend(revmap, heapBlk);
|
||||||
|
|
||||||
@ -360,9 +394,9 @@ brin_doinsert(Relation idxrel, BlockNumber pagesPerRange,
|
|||||||
*/
|
*/
|
||||||
if (!BufferIsValid(*buffer))
|
if (!BufferIsValid(*buffer))
|
||||||
{
|
{
|
||||||
*buffer = brin_getinsertbuffer(idxrel, InvalidBuffer, itemsz, &extended);
|
do
|
||||||
Assert(BufferIsValid(*buffer));
|
*buffer = brin_getinsertbuffer(idxrel, InvalidBuffer, itemsz, &extended);
|
||||||
Assert(extended || br_page_get_freespace(BufferGetPage(*buffer)) >= itemsz);
|
while (!BufferIsValid(*buffer));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
extended = false;
|
extended = false;
|
||||||
@ -610,8 +644,9 @@ brin_page_cleanup(Relation idxrel, Buffer buf)
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* Return a pinned and exclusively locked buffer which can be used to insert an
|
* Return a pinned and exclusively locked buffer which can be used to insert an
|
||||||
* index item of size itemsz. If oldbuf is a valid buffer, it is also locked
|
* index item of size itemsz (caller must ensure not to request sizes
|
||||||
* (in an order determined to avoid deadlocks.)
|
* impossible to fulfill). If oldbuf is a valid buffer, it is also locked (in
|
||||||
|
* an order determined to avoid deadlocks.)
|
||||||
*
|
*
|
||||||
* If we find that the old page is no longer a regular index page (because
|
* If we find that the old page is no longer a regular index page (because
|
||||||
* of a revmap extension), the old buffer is unlocked and we return
|
* of a revmap extension), the old buffer is unlocked and we return
|
||||||
@ -636,20 +671,8 @@ brin_getinsertbuffer(Relation irel, Buffer oldbuf, Size itemsz,
|
|||||||
Page page;
|
Page page;
|
||||||
int freespace;
|
int freespace;
|
||||||
|
|
||||||
/*
|
/* callers must have checked */
|
||||||
* If the item is oversized, don't bother. We have another, more precise
|
Assert(itemsz <= BrinMaxItemSize);
|
||||||
* check below.
|
|
||||||
*/
|
|
||||||
if (itemsz > BLCKSZ - sizeof(BrinSpecialSpace))
|
|
||||||
{
|
|
||||||
ereport(ERROR,
|
|
||||||
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
|
|
||||||
errmsg("index row size %lu exceeds maximum %lu for index \"%s\"",
|
|
||||||
(unsigned long) itemsz,
|
|
||||||
(unsigned long) BLCKSZ - sizeof(BrinSpecialSpace),
|
|
||||||
RelationGetRelationName(irel))));
|
|
||||||
return InvalidBuffer; /* keep compiler quiet */
|
|
||||||
}
|
|
||||||
|
|
||||||
*extended = false;
|
*extended = false;
|
||||||
|
|
||||||
@ -759,7 +782,7 @@ brin_getinsertbuffer(Relation irel, Buffer oldbuf, Size itemsz,
|
|||||||
* page that has since been repurposed for the revmap.)
|
* page that has since been repurposed for the revmap.)
|
||||||
*/
|
*/
|
||||||
freespace = *extended ?
|
freespace = *extended ?
|
||||||
BLCKSZ - sizeof(BrinSpecialSpace) : br_page_get_freespace(page);
|
BrinMaxItemSize : br_page_get_freespace(page);
|
||||||
if (freespace >= itemsz)
|
if (freespace >= itemsz)
|
||||||
{
|
{
|
||||||
RelationSetTargetBlock(irel, BufferGetBlockNumber(buf));
|
RelationSetTargetBlock(irel, BufferGetBlockNumber(buf));
|
||||||
|
Loading…
x
Reference in New Issue
Block a user