mirror of
https://github.com/postgres/postgres.git
synced 2026-04-21 06:08:26 -04:00
In some corner cases, it is possible for the BRIN index relation to be extended by brin_getinsertbuffer but the new page not be used immediately for anything by its callers; when this happens, the page is initialized and the FSM is updated (by brin_getinsertbuffer) with the info about that page, but these actions are not WAL-logged. A later index insert/update can use the page, but since the page is already initialized, the initialization itself is not WAL-logged then either. Replay of this sequence of events causes recovery to fail altogether. There is a related corner case within brin_getinsertbuffer itself, in which we extend the relation to put a new index tuple there, but later find out that we cannot do so, and do not return the buffer; the page obtained from extension is not even initialized. The resulting page is lost forever. To fix, shuffle the code so that initialization is not the responsibility of brin_getinsertbuffer anymore, in normal cases; instead, the initialization is done by its callers (brin_doinsert and brin_doupdate) once they're certain that the page is going to be used. When either those functions determine that the new page cannot be used, before bailing out they initialize the page as an empty regular page, enter it in FSM and WAL-log all this. This way, the page is usable for future index insertions, and WAL replay doesn't find trying to insert tuples in pages whose initialization didn't make it to the WAL. The same strategy is used in brin_getinsertbuffer when it cannot return the new page. Additionally, add a new step to vacuuming so that all pages of the index are scanned; whenever an uninitialized page is found, it is initialized as empty and WAL-logged. This closes the hole that the relation is extended but the system crashes before anything is WAL-logged about it. We also take this opportunity to update the FSM, in case it has gotten out of date. Thanks to Heikki Linnakangas for finding the problem that kicked some additional analysis of BRIN page assignment code. Backpatch to 9.5, where BRIN was introduced. Discussion: https://www.postgresql.org/message-id/20150723204810.GY5596@postgresql.org
96 lines
2.7 KiB
C
96 lines
2.7 KiB
C
/*
|
|
* brin_page.h
|
|
* Prototypes and definitions for BRIN page layouts
|
|
*
|
|
* Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
*
|
|
* IDENTIFICATION
|
|
* src/include/access/brin_page.h
|
|
*
|
|
* NOTES
|
|
*
|
|
* These structs should really be private to specific BRIN files, but it's
|
|
* useful to have them here so that they can be used by pageinspect and similar
|
|
* tools.
|
|
*/
|
|
#ifndef BRIN_PAGE_H
|
|
#define BRIN_PAGE_H
|
|
|
|
#include "storage/block.h"
|
|
#include "storage/itemptr.h"
|
|
|
|
/*
|
|
* Special area of BRIN pages.
|
|
*
|
|
* We define it in this odd way so that it always occupies the last
|
|
* MAXALIGN-sized element of each page.
|
|
*/
|
|
typedef struct BrinSpecialSpace
|
|
{
|
|
uint16 vector[MAXALIGN(1) / sizeof(uint16)];
|
|
} BrinSpecialSpace;
|
|
|
|
/*
|
|
* Make the page type be the last half-word in the page, for consumption by
|
|
* pg_filedump and similar utilities. We don't really care much about the
|
|
* position of the "flags" half-word, but it's simpler to apply a consistent
|
|
* rule to both.
|
|
*
|
|
* See comments above GinPageOpaqueData.
|
|
*/
|
|
#define BrinPageType(page) \
|
|
(((BrinSpecialSpace *) \
|
|
PageGetSpecialPointer(page))->vector[MAXALIGN(1) / sizeof(uint16) - 1])
|
|
|
|
#define BrinPageFlags(page) \
|
|
(((BrinSpecialSpace *) \
|
|
PageGetSpecialPointer(page))->vector[MAXALIGN(1) / sizeof(uint16) - 2])
|
|
|
|
/* special space on all BRIN pages stores a "type" identifier */
|
|
#define BRIN_PAGETYPE_META 0xF091
|
|
#define BRIN_PAGETYPE_REVMAP 0xF092
|
|
#define BRIN_PAGETYPE_REGULAR 0xF093
|
|
|
|
#define BRIN_IS_META_PAGE(page) (BrinPageType(page) == BRIN_PAGETYPE_META)
|
|
#define BRIN_IS_REVMAP_PAGE(page) (BrinPageType(page) == BRIN_PAGETYPE_REVMAP)
|
|
#define BRIN_IS_REGULAR_PAGE(page) (BrinPageType(page) == BRIN_PAGETYPE_REGULAR)
|
|
|
|
/* flags for BrinSpecialSpace */
|
|
#define BRIN_EVACUATE_PAGE (1 << 0)
|
|
|
|
|
|
/* Metapage definitions */
|
|
typedef struct BrinMetaPageData
|
|
{
|
|
uint32 brinMagic;
|
|
uint32 brinVersion;
|
|
BlockNumber pagesPerRange;
|
|
BlockNumber lastRevmapPage;
|
|
} BrinMetaPageData;
|
|
|
|
#define BRIN_CURRENT_VERSION 1
|
|
#define BRIN_META_MAGIC 0xA8109CFA
|
|
|
|
#define BRIN_METAPAGE_BLKNO 0
|
|
|
|
/* Definitions for revmap pages */
|
|
typedef struct RevmapContents
|
|
{
|
|
/*
|
|
* This array will fill all available space on the page. It should be
|
|
* declared [FLEXIBLE_ARRAY_MEMBER], but for some reason you can't do that
|
|
* in an otherwise-empty struct.
|
|
*/
|
|
ItemPointerData rm_tids[1];
|
|
} RevmapContents;
|
|
|
|
#define REVMAP_CONTENT_SIZE \
|
|
(BLCKSZ - MAXALIGN(SizeOfPageHeaderData) - \
|
|
offsetof(RevmapContents, rm_tids) - \
|
|
MAXALIGN(sizeof(BrinSpecialSpace)))
|
|
/* max num of items in the array */
|
|
#define REVMAP_PAGE_MAXITEMS \
|
|
(REVMAP_CONTENT_SIZE / sizeof(ItemPointerData))
|
|
|
|
#endif /* BRIN_PAGE_H */
|