PostgreSQL Source Code  git master
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros
ginvacuum.c File Reference
#include "postgres.h"
#include "access/gin_private.h"
#include "access/ginxlog.h"
#include "access/xloginsert.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
#include "postmaster/autovacuum.h"
#include "storage/indexfsm.h"
#include "storage/lmgr.h"
#include "utils/memutils.h"
Include dependency graph for ginvacuum.c:

Go to the source code of this file.

Data Structures

struct  GinVacuumState
 
struct  DataPageDeleteStack
 

Typedefs

typedef struct DataPageDeleteStack DataPageDeleteStack
 

Functions

ItemPointer ginVacuumItemPointers (GinVacuumState *gvs, ItemPointerData *items, int nitem, int *nremaining)
 
static void xlogVacuumPage (Relation index, Buffer buffer)
 
static bool ginVacuumPostingTreeLeaves (GinVacuumState *gvs, BlockNumber blkno, bool isRoot, Buffer *rootBuffer)
 
static void ginDeletePage (GinVacuumState *gvs, BlockNumber deleteBlkno, BlockNumber leftBlkno, BlockNumber parentBlkno, OffsetNumber myoff, bool isParentRoot)
 
static bool ginScanToDelete (GinVacuumState *gvs, BlockNumber blkno, bool isRoot, DataPageDeleteStack *parent, OffsetNumber myoff)
 
static void ginVacuumPostingTree (GinVacuumState *gvs, BlockNumber rootBlkno)
 
static Page ginVacuumEntryPage (GinVacuumState *gvs, Buffer buffer, BlockNumber *roots, uint32 *nroot)
 
IndexBulkDeleteResultginbulkdelete (IndexVacuumInfo *info, IndexBulkDeleteResult *stats, IndexBulkDeleteCallback callback, void *callback_state)
 
IndexBulkDeleteResultginvacuumcleanup (IndexVacuumInfo *info, IndexBulkDeleteResult *stats)
 

Typedef Documentation

Function Documentation

IndexBulkDeleteResult* ginbulkdelete ( IndexVacuumInfo info,
IndexBulkDeleteResult stats,
IndexBulkDeleteCallback  callback,
void *  callback_state 
)

Definition at line 518 of file ginvacuum.c.

References ALLOCSET_DEFAULT_SIZES, AllocSetContextCreate(), Assert, DataPageDeleteStack::blkno, BufferGetPage, GinVacuumState::callback, callback(), GinVacuumState::callback_state, CurrentMemoryContext, END_CRIT_SECTION, FirstOffsetNumber, GIN_EXCLUSIVE, GIN_ROOT_BLKNO, GIN_SHARE, GIN_UNLOCK, GinGetDownlink, ginInsertCleanup(), GinPageGetOpaque, GinPageIsData, GinPageIsLeaf, GinVacuumState::ginstate, ginVacuumEntryPage(), ginVacuumPostingTree(), i, GinVacuumState::index, IndexVacuumInfo::index, initGinState(), InvalidBlockNumber, IsAutoVacuumWorkerProcess(), LockBuffer(), MAIN_FORKNUM, MarkBufferDirty(), MemoryContextDelete(), NULL, IndexBulkDeleteResult::num_index_tuples, PageGetItem, PageGetItemId, PageGetMaxOffsetNumber, PageRestoreTempPage(), palloc0(), RBM_NORMAL, ReadBufferExtended(), GinVacuumState::result, START_CRIT_SECTION, GinVacuumState::strategy, IndexVacuumInfo::strategy, GinVacuumState::tmpCxt, UnlockReleaseBuffer(), vacuum_delay_point(), and xlogVacuumPage().

Referenced by ginhandler().

520 {
521  Relation index = info->index;
522  BlockNumber blkno = GIN_ROOT_BLKNO;
523  GinVacuumState gvs;
524  Buffer buffer;
525  BlockNumber rootOfPostingTree[BLCKSZ / (sizeof(IndexTupleData) + sizeof(ItemId))];
526  uint32 nRoot;
527 
529  "Gin vacuum temporary context",
531  gvs.index = index;
532  gvs.callback = callback;
533  gvs.callback_state = callback_state;
534  gvs.strategy = info->strategy;
535  initGinState(&gvs.ginstate, index);
536 
537  /* first time through? */
538  if (stats == NULL)
539  {
540  /* Yes, so initialize stats to zeroes */
542 
543  /*
544  * and cleanup any pending inserts
545  */
547  false, stats);
548  }
549 
550  /* we'll re-count the tuples each time */
551  stats->num_index_tuples = 0;
552  gvs.result = stats;
553 
554  buffer = ReadBufferExtended(index, MAIN_FORKNUM, blkno,
555  RBM_NORMAL, info->strategy);
556 
557  /* find leaf page */
558  for (;;)
559  {
560  Page page = BufferGetPage(buffer);
561  IndexTuple itup;
562 
563  LockBuffer(buffer, GIN_SHARE);
564 
565  Assert(!GinPageIsData(page));
566 
567  if (GinPageIsLeaf(page))
568  {
569  LockBuffer(buffer, GIN_UNLOCK);
570  LockBuffer(buffer, GIN_EXCLUSIVE);
571 
572  if (blkno == GIN_ROOT_BLKNO && !GinPageIsLeaf(page))
573  {
574  LockBuffer(buffer, GIN_UNLOCK);
575  continue; /* check it one more */
576  }
577  break;
578  }
579 
581 
582  itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, FirstOffsetNumber));
583  blkno = GinGetDownlink(itup);
584  Assert(blkno != InvalidBlockNumber);
585 
586  UnlockReleaseBuffer(buffer);
587  buffer = ReadBufferExtended(index, MAIN_FORKNUM, blkno,
588  RBM_NORMAL, info->strategy);
589  }
590 
591  /* right now we found leftmost page in entry's BTree */
592 
593  for (;;)
594  {
595  Page page = BufferGetPage(buffer);
596  Page resPage;
597  uint32 i;
598 
599  Assert(!GinPageIsData(page));
600 
601  resPage = ginVacuumEntryPage(&gvs, buffer, rootOfPostingTree, &nRoot);
602 
603  blkno = GinPageGetOpaque(page)->rightlink;
604 
605  if (resPage)
606  {
608  PageRestoreTempPage(resPage, page);
609  MarkBufferDirty(buffer);
610  xlogVacuumPage(gvs.index, buffer);
611  UnlockReleaseBuffer(buffer);
613  }
614  else
615  {
616  UnlockReleaseBuffer(buffer);
617  }
618 
620 
621  for (i = 0; i < nRoot; i++)
622  {
623  ginVacuumPostingTree(&gvs, rootOfPostingTree[i]);
625  }
626 
627  if (blkno == InvalidBlockNumber) /* rightmost page */
628  break;
629 
630  buffer = ReadBufferExtended(index, MAIN_FORKNUM, blkno,
631  RBM_NORMAL, info->strategy);
632  LockBuffer(buffer, GIN_EXCLUSIVE);
633  }
634 
636 
637  return gvs.result;
638 }
#define GIN_UNLOCK
Definition: gin_private.h:43
void MemoryContextDelete(MemoryContext context)
Definition: mcxt.c:200
void PageRestoreTempPage(Page tempPage, Page oldPage)
Definition: bufpage.c:407
void MarkBufferDirty(Buffer buffer)
Definition: bufmgr.c:1445
Relation index
Definition: ginvacuum.c:29
Buffer ReadBufferExtended(Relation reln, ForkNumber forkNum, BlockNumber blockNum, ReadBufferMode mode, BufferAccessStrategy strategy)
Definition: bufmgr.c:640
BufferAccessStrategy strategy
Definition: ginvacuum.c:34
#define END_CRIT_SECTION()
Definition: miscadmin.h:132
BufferAccessStrategy strategy
Definition: genam.h:51
void ginInsertCleanup(GinState *ginstate, bool full_clean, bool fill_fsm, IndexBulkDeleteResult *stats)
Definition: ginfast.c:729
#define START_CRIT_SECTION()
Definition: miscadmin.h:130
Relation index
Definition: genam.h:46
uint32 BlockNumber
Definition: block.h:31
#define GinPageGetOpaque(page)
Definition: ginblock.h:109
#define PageGetMaxOffsetNumber(page)
Definition: bufpage.h:354
IndexBulkDeleteResult * result
Definition: ginvacuum.c:30
Definition: type.h:90
void UnlockReleaseBuffer(Buffer buffer)
Definition: bufmgr.c:3315
static void xlogVacuumPage(Relation index, Buffer buffer)
Definition: ginvacuum.c:89
static void callback(struct sockaddr *addr, struct sockaddr *mask, void *unused)
Definition: test_ifaddrs.c:49
#define ALLOCSET_DEFAULT_SIZES
Definition: memutils.h:151
static Page ginVacuumEntryPage(GinVacuumState *gvs, Buffer buffer, BlockNumber *roots, uint32 *nroot)
Definition: ginvacuum.c:409
IndexBulkDeleteCallback callback
Definition: ginvacuum.c:31
#define FirstOffsetNumber
Definition: off.h:27
IndexTupleData * IndexTuple
Definition: itup.h:53
void initGinState(GinState *state, Relation index)
Definition: ginutil.c:86
unsigned int uint32
Definition: c.h:265
MemoryContext CurrentMemoryContext
Definition: mcxt.c:37
#define GinPageIsLeaf(page)
Definition: ginblock.h:111
void * callback_state
Definition: ginvacuum.c:32
#define BufferGetPage(buffer)
Definition: bufmgr.h:160
bool IsAutoVacuumWorkerProcess(void)
Definition: autovacuum.c:2988
#define GIN_SHARE
Definition: gin_private.h:44
#define PageGetItemId(page, offsetNumber)
Definition: bufpage.h:232
MemoryContext AllocSetContextCreate(MemoryContext parent, const char *name, Size minContextSize, Size initBlockSize, Size maxBlockSize)
Definition: aset.c:329
void * palloc0(Size size)
Definition: mcxt.c:920
struct IndexTupleData IndexTupleData
#define GIN_EXCLUSIVE
Definition: gin_private.h:45
void LockBuffer(Buffer buffer, int mode)
Definition: bufmgr.c:3529
#define GinGetDownlink(itup)
Definition: ginblock.h:241
#define GinPageIsData(page)
Definition: ginblock.h:114
#define NULL
Definition: c.h:226
#define Assert(condition)
Definition: c.h:671
#define InvalidBlockNumber
Definition: block.h:33
int i
static void ginVacuumPostingTree(GinVacuumState *gvs, BlockNumber rootBlkno)
Definition: ginvacuum.c:371
void vacuum_delay_point(void)
Definition: vacuum.c:1515
MemoryContext tmpCxt
Definition: ginvacuum.c:35
double num_index_tuples
Definition: genam.h:76
int Buffer
Definition: buf.h:23
#define PageGetItem(page, itemId)
Definition: bufpage.h:337
#define GIN_ROOT_BLKNO
Definition: ginblock.h:51
Pointer Page
Definition: bufpage.h:74
GinState ginstate
Definition: ginvacuum.c:33
static void ginDeletePage ( GinVacuumState gvs,
BlockNumber  deleteBlkno,
BlockNumber  leftBlkno,
BlockNumber  parentBlkno,
OffsetNumber  myoff,
bool  isParentRoot 
)
static

Definition at line 186 of file ginvacuum.c.

References Assert, BufferGetPage, END_CRIT_SECTION, GIN_DELETED, GIN_EXCLUSIVE, GIN_UNLOCK, GinDataPageGetPostingItem, GinPageDeletePostingItem(), GinPageGetOpaque, GinVacuumState::index, LockBuffer(), MAIN_FORKNUM, MarkBufferDirty(), IndexBulkDeleteResult::pages_deleted, PageSetLSN, ginxlogDeletePage::parentOffset, PostingItemGetBlockNumber, RBM_NORMAL, ReadBufferExtended(), REGBUF_STANDARD, RelationNeedsWAL, ReleaseBuffer(), GinVacuumState::result, ginxlogDeletePage::rightLink, START_CRIT_SECTION, GinVacuumState::strategy, UnlockReleaseBuffer(), XLOG_GIN_DELETE_PAGE, XLogBeginInsert(), XLogInsert(), XLogRegisterBuffer(), and XLogRegisterData().

Referenced by ginScanToDelete().

188 {
189  Buffer dBuffer;
190  Buffer lBuffer;
191  Buffer pBuffer;
192  Page page,
193  parentPage;
194  BlockNumber rightlink;
195 
196  /*
197  * Lock the pages in the same order as an insertion would, to avoid
198  * deadlocks: left, then right, then parent.
199  */
200  lBuffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, leftBlkno,
201  RBM_NORMAL, gvs->strategy);
202  dBuffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, deleteBlkno,
203  RBM_NORMAL, gvs->strategy);
204  pBuffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, parentBlkno,
205  RBM_NORMAL, gvs->strategy);
206 
207  LockBuffer(lBuffer, GIN_EXCLUSIVE);
208  LockBuffer(dBuffer, GIN_EXCLUSIVE);
209  if (!isParentRoot) /* parent is already locked by
210  * LockBufferForCleanup() */
211  LockBuffer(pBuffer, GIN_EXCLUSIVE);
212 
214 
215  /* Unlink the page by changing left sibling's rightlink */
216  page = BufferGetPage(dBuffer);
217  rightlink = GinPageGetOpaque(page)->rightlink;
218 
219  page = BufferGetPage(lBuffer);
220  GinPageGetOpaque(page)->rightlink = rightlink;
221 
222  /* Delete downlink from parent */
223  parentPage = BufferGetPage(pBuffer);
224 #ifdef USE_ASSERT_CHECKING
225  do
226  {
227  PostingItem *tod = GinDataPageGetPostingItem(parentPage, myoff);
228 
229  Assert(PostingItemGetBlockNumber(tod) == deleteBlkno);
230  } while (0);
231 #endif
232  GinPageDeletePostingItem(parentPage, myoff);
233 
234  page = BufferGetPage(dBuffer);
235 
236  /*
237  * we shouldn't change rightlink field to save workability of running
238  * search scan
239  */
240  GinPageGetOpaque(page)->flags = GIN_DELETED;
241 
242  MarkBufferDirty(pBuffer);
243  MarkBufferDirty(lBuffer);
244  MarkBufferDirty(dBuffer);
245 
246  if (RelationNeedsWAL(gvs->index))
247  {
248  XLogRecPtr recptr;
249  ginxlogDeletePage data;
250 
251  /*
252  * We can't pass REGBUF_STANDARD for the deleted page, because we
253  * didn't set pd_lower on pre-9.4 versions. The page might've been
254  * binary-upgraded from an older version, and hence not have pd_lower
255  * set correctly. Ditto for the left page, but removing the item from
256  * the parent updated its pd_lower, so we know that's OK at this
257  * point.
258  */
259  XLogBeginInsert();
260  XLogRegisterBuffer(0, dBuffer, 0);
261  XLogRegisterBuffer(1, pBuffer, REGBUF_STANDARD);
262  XLogRegisterBuffer(2, lBuffer, 0);
263 
264  data.parentOffset = myoff;
265  data.rightLink = GinPageGetOpaque(page)->rightlink;
266 
267  XLogRegisterData((char *) &data, sizeof(ginxlogDeletePage));
268 
269  recptr = XLogInsert(RM_GIN_ID, XLOG_GIN_DELETE_PAGE);
270  PageSetLSN(page, recptr);
271  PageSetLSN(parentPage, recptr);
272  PageSetLSN(BufferGetPage(lBuffer), recptr);
273  }
274 
275  if (!isParentRoot)
276  LockBuffer(pBuffer, GIN_UNLOCK);
277  ReleaseBuffer(pBuffer);
278  UnlockReleaseBuffer(lBuffer);
279  UnlockReleaseBuffer(dBuffer);
280 
282 
283  gvs->result->pages_deleted++;
284 }
#define GIN_UNLOCK
Definition: gin_private.h:43
#define GIN_DELETED
Definition: ginblock.h:41
void MarkBufferDirty(Buffer buffer)
Definition: bufmgr.c:1445
void XLogRegisterBuffer(uint8 block_id, Buffer buffer, uint8 flags)
Definition: xloginsert.c:213
Relation index
Definition: ginvacuum.c:29
Buffer ReadBufferExtended(Relation reln, ForkNumber forkNum, BlockNumber blockNum, ReadBufferMode mode, BufferAccessStrategy strategy)
Definition: bufmgr.c:640
BlockNumber rightLink
Definition: ginxlog.h:160
BufferAccessStrategy strategy
Definition: ginvacuum.c:34
#define END_CRIT_SECTION()
Definition: miscadmin.h:132
#define START_CRIT_SECTION()
Definition: miscadmin.h:130
uint32 BlockNumber
Definition: block.h:31
void ReleaseBuffer(Buffer buffer)
Definition: bufmgr.c:3292
#define GinPageGetOpaque(page)
Definition: ginblock.h:109
OffsetNumber parentOffset
Definition: ginxlog.h:159
IndexBulkDeleteResult * result
Definition: ginvacuum.c:30
void UnlockReleaseBuffer(Buffer buffer)
Definition: bufmgr.c:3315
#define GinDataPageGetPostingItem(page, i)
Definition: ginblock.h:282
#define REGBUF_STANDARD
Definition: xloginsert.h:35
BlockNumber pages_deleted
Definition: genam.h:78
#define BufferGetPage(buffer)
Definition: bufmgr.h:160
void XLogRegisterData(char *data, int len)
Definition: xloginsert.c:323
XLogRecPtr XLogInsert(RmgrId rmid, uint8 info)
Definition: xloginsert.c:415
#define GIN_EXCLUSIVE
Definition: gin_private.h:45
void LockBuffer(Buffer buffer, int mode)
Definition: bufmgr.c:3529
void GinPageDeletePostingItem(Page page, OffsetNumber offset)
Definition: gindatapage.c:413
#define PostingItemGetBlockNumber(pointer)
Definition: ginblock.h:176
uint64 XLogRecPtr
Definition: xlogdefs.h:21
#define Assert(condition)
Definition: c.h:671
#define RelationNeedsWAL(relation)
Definition: rel.h:502
void XLogBeginInsert(void)
Definition: xloginsert.c:120
#define XLOG_GIN_DELETE_PAGE
Definition: ginxlog.h:155
#define PageSetLSN(page, lsn)
Definition: bufpage.h:365
int Buffer
Definition: buf.h:23
Pointer Page
Definition: bufpage.h:74
static bool ginScanToDelete ( GinVacuumState gvs,
BlockNumber  blkno,
bool  isRoot,
DataPageDeleteStack parent,
OffsetNumber  myoff 
)
static

Definition at line 300 of file ginvacuum.c.

References Assert, DataPageDeleteStack::blkno, BufferGetPage, DataPageDeleteStack::child, FALSE, FirstOffsetNumber, GinDataLeafPageIsEmpty, GinDataPageGetPostingItem, ginDeletePage(), GinPageGetOpaque, GinPageIsData, GinPageIsLeaf, GinPageRightMost, i, GinVacuumState::index, InvalidBlockNumber, DataPageDeleteStack::isRoot, DataPageDeleteStack::leftBlkno, MAIN_FORKNUM, palloc0(), DataPageDeleteStack::parent, PostingItemGetBlockNumber, RBM_NORMAL, ReadBufferExtended(), ReleaseBuffer(), GinVacuumState::strategy, and TRUE.

Referenced by ginVacuumPostingTree().

302 {
304  Buffer buffer;
305  Page page;
306  bool meDelete = FALSE;
307  bool isempty;
308 
309  if (isRoot)
310  {
311  me = parent;
312  }
313  else
314  {
315  if (!parent->child)
316  {
318  me->parent = parent;
319  parent->child = me;
321  }
322  else
323  me = parent->child;
324  }
325 
326  buffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, blkno,
327  RBM_NORMAL, gvs->strategy);
328  page = BufferGetPage(buffer);
329 
330  Assert(GinPageIsData(page));
331 
332  if (!GinPageIsLeaf(page))
333  {
334  OffsetNumber i;
335 
336  me->blkno = blkno;
337  for (i = FirstOffsetNumber; i <= GinPageGetOpaque(page)->maxoff; i++)
338  {
339  PostingItem *pitem = GinDataPageGetPostingItem(page, i);
340 
341  if (ginScanToDelete(gvs, PostingItemGetBlockNumber(pitem), FALSE, me, i))
342  i--;
343  }
344  }
345 
346  if (GinPageIsLeaf(page))
347  isempty = GinDataLeafPageIsEmpty(page);
348  else
349  isempty = GinPageGetOpaque(page)->maxoff < FirstOffsetNumber;
350 
351  if (isempty)
352  {
353  /* we never delete the left- or rightmost branch */
354  if (me->leftBlkno != InvalidBlockNumber && !GinPageRightMost(page))
355  {
356  Assert(!isRoot);
357  ginDeletePage(gvs, blkno, me->leftBlkno, me->parent->blkno, myoff, me->parent->isRoot);
358  meDelete = TRUE;
359  }
360  }
361 
362  ReleaseBuffer(buffer);
363 
364  if (!meDelete)
365  me->leftBlkno = blkno;
366 
367  return meDelete;
368 }
Relation index
Definition: ginvacuum.c:29
Buffer ReadBufferExtended(Relation reln, ForkNumber forkNum, BlockNumber blockNum, ReadBufferMode mode, BufferAccessStrategy strategy)
Definition: bufmgr.c:640
BufferAccessStrategy strategy
Definition: ginvacuum.c:34
void ReleaseBuffer(Buffer buffer)
Definition: bufmgr.c:3292
#define GinPageGetOpaque(page)
Definition: ginblock.h:109
static void ginDeletePage(GinVacuumState *gvs, BlockNumber deleteBlkno, BlockNumber leftBlkno, BlockNumber parentBlkno, OffsetNumber myoff, bool isParentRoot)
Definition: ginvacuum.c:186
#define GinDataLeafPageIsEmpty(page)
Definition: ginblock.h:267
uint16 OffsetNumber
Definition: off.h:24
BlockNumber leftBlkno
Definition: ginvacuum.c:292
#define GinPageRightMost(page)
Definition: ginblock.h:128
#define GinDataPageGetPostingItem(page, i)
Definition: ginblock.h:282
#define FALSE
Definition: c.h:218
BlockNumber blkno
Definition: ginvacuum.c:291
#define FirstOffsetNumber
Definition: off.h:27
#define GinPageIsLeaf(page)
Definition: ginblock.h:111
#define BufferGetPage(buffer)
Definition: bufmgr.h:160
void * palloc0(Size size)
Definition: mcxt.c:920
struct DataPageDeleteStack * child
Definition: ginvacuum.c:288
#define GinPageIsData(page)
Definition: ginblock.h:114
#define PostingItemGetBlockNumber(pointer)
Definition: ginblock.h:176
#define Assert(condition)
Definition: c.h:671
struct DataPageDeleteStack * parent
Definition: ginvacuum.c:289
#define InvalidBlockNumber
Definition: block.h:33
int i
#define TRUE
Definition: c.h:214
int Buffer
Definition: buf.h:23
Pointer Page
Definition: bufpage.h:74
static bool ginScanToDelete(GinVacuumState *gvs, BlockNumber blkno, bool isRoot, DataPageDeleteStack *parent, OffsetNumber myoff)
Definition: ginvacuum.c:300
IndexBulkDeleteResult* ginvacuumcleanup ( IndexVacuumInfo info,
IndexBulkDeleteResult stats 
)

Definition at line 641 of file ginvacuum.c.

References IndexVacuumInfo::analyze_only, Assert, DataPageDeleteStack::blkno, BufferGetPage, IndexVacuumInfo::estimated_count, IndexBulkDeleteResult::estimated_count, ExclusiveLock, GIN_ROOT_BLKNO, GIN_SHARE, ginInsertCleanup(), GinPageIsData, GinPageIsDeleted, GinPageIsLeaf, GinPageIsList, ginUpdateStats(), IndexVacuumInfo::index, IndexFreeSpaceMapVacuum(), initGinState(), IsAutoVacuumWorkerProcess(), LockBuffer(), LockRelationForExtension(), MAIN_FORKNUM, GinStatsData::nDataPages, GinStatsData::nEntries, GinStatsData::nEntryPages, GinStatsData::nTotalPages, NULL, IndexVacuumInfo::num_heap_tuples, IndexBulkDeleteResult::num_index_tuples, IndexBulkDeleteResult::num_pages, PageGetMaxOffsetNumber, PageIsNew, IndexBulkDeleteResult::pages_free, palloc0(), RBM_NORMAL, ReadBufferExtended(), RecordFreeIndexPage(), RELATION_IS_LOCAL, RelationGetNumberOfBlocks, IndexVacuumInfo::strategy, UnlockRelationForExtension(), UnlockReleaseBuffer(), and vacuum_delay_point().

Referenced by ginhandler().

642 {
643  Relation index = info->index;
644  bool needLock;
645  BlockNumber npages,
646  blkno;
647  BlockNumber totFreePages;
648  GinState ginstate;
649  GinStatsData idxStat;
650 
651  /*
652  * In an autovacuum analyze, we want to clean up pending insertions.
653  * Otherwise, an ANALYZE-only call is a no-op.
654  */
655  if (info->analyze_only)
656  {
658  {
659  initGinState(&ginstate, index);
660  ginInsertCleanup(&ginstate, false, true, stats);
661  }
662  return stats;
663  }
664 
665  /*
666  * Set up all-zero stats and cleanup pending inserts if ginbulkdelete
667  * wasn't called
668  */
669  if (stats == NULL)
670  {
672  initGinState(&ginstate, index);
674  false, stats);
675  }
676 
677  memset(&idxStat, 0, sizeof(idxStat));
678 
679  /*
680  * XXX we always report the heap tuple count as the number of index
681  * entries. This is bogus if the index is partial, but it's real hard to
682  * tell how many distinct heap entries are referenced by a GIN index.
683  */
684  stats->num_index_tuples = info->num_heap_tuples;
685  stats->estimated_count = info->estimated_count;
686 
687  /*
688  * Need lock unless it's local to this backend.
689  */
690  needLock = !RELATION_IS_LOCAL(index);
691 
692  if (needLock)
694  npages = RelationGetNumberOfBlocks(index);
695  if (needLock)
697 
698  totFreePages = 0;
699 
700  for (blkno = GIN_ROOT_BLKNO; blkno < npages; blkno++)
701  {
702  Buffer buffer;
703  Page page;
704 
706 
707  buffer = ReadBufferExtended(index, MAIN_FORKNUM, blkno,
708  RBM_NORMAL, info->strategy);
709  LockBuffer(buffer, GIN_SHARE);
710  page = (Page) BufferGetPage(buffer);
711 
712  if (PageIsNew(page) || GinPageIsDeleted(page))
713  {
714  Assert(blkno != GIN_ROOT_BLKNO);
715  RecordFreeIndexPage(index, blkno);
716  totFreePages++;
717  }
718  else if (GinPageIsData(page))
719  {
720  idxStat.nDataPages++;
721  }
722  else if (!GinPageIsList(page))
723  {
724  idxStat.nEntryPages++;
725 
726  if (GinPageIsLeaf(page))
727  idxStat.nEntries += PageGetMaxOffsetNumber(page);
728  }
729 
730  UnlockReleaseBuffer(buffer);
731  }
732 
733  /* Update the metapage with accurate page and entry counts */
734  idxStat.nTotalPages = npages;
735  ginUpdateStats(info->index, &idxStat);
736 
737  /* Finally, vacuum the FSM */
739 
740  stats->pages_free = totFreePages;
741 
742  if (needLock)
744  stats->num_pages = RelationGetNumberOfBlocks(index);
745  if (needLock)
747 
748  return stats;
749 }
BlockNumber nEntryPages
Definition: gin.h:45
#define ExclusiveLock
Definition: lockdefs.h:44
#define RELATION_IS_LOCAL(relation)
Definition: rel.h:520
bool analyze_only
Definition: genam.h:47
Buffer ReadBufferExtended(Relation reln, ForkNumber forkNum, BlockNumber blockNum, ReadBufferMode mode, BufferAccessStrategy strategy)
Definition: bufmgr.c:640
BufferAccessStrategy strategy
Definition: genam.h:51
void ginInsertCleanup(GinState *ginstate, bool full_clean, bool fill_fsm, IndexBulkDeleteResult *stats)
Definition: ginfast.c:729
Relation index
Definition: genam.h:46
int64 nEntries
Definition: gin.h:47
uint32 BlockNumber
Definition: block.h:31
#define PageGetMaxOffsetNumber(page)
Definition: bufpage.h:354
Definition: type.h:90
void UnlockReleaseBuffer(Buffer buffer)
Definition: bufmgr.c:3315
BlockNumber num_pages
Definition: genam.h:73
BlockNumber pages_free
Definition: genam.h:79
void ginUpdateStats(Relation index, const GinStatsData *stats)
Definition: ginutil.c:659
void initGinState(GinState *state, Relation index)
Definition: ginutil.c:86
#define GinPageIsLeaf(page)
Definition: ginblock.h:111
#define BufferGetPage(buffer)
Definition: bufmgr.h:160
bool IsAutoVacuumWorkerProcess(void)
Definition: autovacuum.c:2988
#define GIN_SHARE
Definition: gin_private.h:44
void LockRelationForExtension(Relation relation, LOCKMODE lockmode)
Definition: lmgr.c:332
void * palloc0(Size size)
Definition: mcxt.c:920
#define GinPageIsDeleted(page)
Definition: ginblock.h:123
void UnlockRelationForExtension(Relation relation, LOCKMODE lockmode)
Definition: lmgr.c:382
void LockBuffer(Buffer buffer, int mode)
Definition: bufmgr.c:3529
#define RelationGetNumberOfBlocks(reln)
Definition: bufmgr.h:199
BlockNumber nDataPages
Definition: gin.h:46
#define GinPageIsData(page)
Definition: ginblock.h:114
double num_heap_tuples
Definition: genam.h:50
#define NULL
Definition: c.h:226
#define Assert(condition)
Definition: c.h:671
void IndexFreeSpaceMapVacuum(Relation rel)
Definition: indexfsm.c:71
#define GinPageIsList(page)
Definition: ginblock.h:116
BlockNumber nTotalPages
Definition: gin.h:44
#define PageIsNew(page)
Definition: bufpage.h:226
void vacuum_delay_point(void)
Definition: vacuum.c:1515
void RecordFreeIndexPage(Relation rel, BlockNumber freeBlock)
Definition: indexfsm.c:52
double num_index_tuples
Definition: genam.h:76
int Buffer
Definition: buf.h:23
bool estimated_count
Definition: genam.h:75
#define GIN_ROOT_BLKNO
Definition: ginblock.h:51
Pointer Page
Definition: bufpage.h:74
bool estimated_count
Definition: genam.h:48
static Page ginVacuumEntryPage ( GinVacuumState gvs,
Buffer  buffer,
BlockNumber roots,
uint32 nroot 
)
static

Definition at line 409 of file ginvacuum.c.

References BufferGetPage, elog, ERROR, FirstOffsetNumber, ginCompressPostingList(), GinFormTuple(), GinGetDownlink, GinGetNPosting, GinGetPosting, GinIsPostingTree, GinItupIsCompressed, GinMaxItemSize, ginPostingListDecode(), GinVacuumState::ginstate, gintuple_get_attrnum(), gintuple_get_key(), ginVacuumItemPointers(), i, GinVacuumState::index, IndexTupleSize, NULL, PageAddItem, PageGetItem, PageGetItemId, PageGetMaxOffsetNumber, PageGetTempPageCopy(), PageIndexTupleDelete(), pfree(), RelationGetRelationName, and SizeOfGinPostingList.

Referenced by ginbulkdelete().

410 {
411  Page origpage = BufferGetPage(buffer),
412  tmppage;
413  OffsetNumber i,
414  maxoff = PageGetMaxOffsetNumber(origpage);
415 
416  tmppage = origpage;
417 
418  *nroot = 0;
419 
420  for (i = FirstOffsetNumber; i <= maxoff; i++)
421  {
422  IndexTuple itup = (IndexTuple) PageGetItem(tmppage, PageGetItemId(tmppage, i));
423 
424  if (GinIsPostingTree(itup))
425  {
426  /*
427  * store posting tree's roots for further processing, we can't
428  * vacuum it just now due to risk of deadlocks with scans/inserts
429  */
430  roots[*nroot] = GinGetDownlink(itup);
431  (*nroot)++;
432  }
433  else if (GinGetNPosting(itup) > 0)
434  {
435  int nitems;
436  ItemPointer items_orig;
437  bool free_items_orig;
438  ItemPointer items;
439 
440  /* Get list of item pointers from the tuple. */
441  if (GinItupIsCompressed(itup))
442  {
443  items_orig = ginPostingListDecode((GinPostingList *) GinGetPosting(itup), &nitems);
444  free_items_orig = true;
445  }
446  else
447  {
448  items_orig = (ItemPointer) GinGetPosting(itup);
449  nitems = GinGetNPosting(itup);
450  free_items_orig = false;
451  }
452 
453  /* Remove any items from the list that need to be vacuumed. */
454  items = ginVacuumItemPointers(gvs, items_orig, nitems, &nitems);
455 
456  if (free_items_orig)
457  pfree(items_orig);
458 
459  /* If any item pointers were removed, recreate the tuple. */
460  if (items)
461  {
462  OffsetNumber attnum;
463  Datum key;
464  GinNullCategory category;
465  GinPostingList *plist;
466  int plistsize;
467 
468  if (nitems > 0)
469  {
470  plist = ginCompressPostingList(items, nitems, GinMaxItemSize, NULL);
471  plistsize = SizeOfGinPostingList(plist);
472  }
473  else
474  {
475  plist = NULL;
476  plistsize = 0;
477  }
478 
479  /*
480  * if we already created a temporary page, make changes in
481  * place
482  */
483  if (tmppage == origpage)
484  {
485  /*
486  * On first difference, create a temporary copy of the
487  * page and copy the tuple's posting list to it.
488  */
489  tmppage = PageGetTempPageCopy(origpage);
490 
491  /* set itup pointer to new page */
492  itup = (IndexTuple) PageGetItem(tmppage, PageGetItemId(tmppage, i));
493  }
494 
495  attnum = gintuple_get_attrnum(&gvs->ginstate, itup);
496  key = gintuple_get_key(&gvs->ginstate, itup, &category);
497  itup = GinFormTuple(&gvs->ginstate, attnum, key, category,
498  (char *) plist, plistsize,
499  nitems, true);
500  if (plist)
501  pfree(plist);
502  PageIndexTupleDelete(tmppage, i);
503 
504  if (PageAddItem(tmppage, (Item) itup, IndexTupleSize(itup), i, false, false) != i)
505  elog(ERROR, "failed to add item to index page in \"%s\"",
507 
508  pfree(itup);
509  pfree(items);
510  }
511  }
512  }
513 
514  return (tmppage == origpage) ? NULL : tmppage;
515 }
GinPostingList * ginCompressPostingList(const ItemPointer ipd, int nipd, int maxsize, int *nwritten)
ItemPointer ginPostingListDecode(GinPostingList *plist, int *ndecoded)
void PageIndexTupleDelete(Page page, OffsetNumber offnum)
Definition: bufpage.c:727
Relation index
Definition: ginvacuum.c:29
Pointer Item
Definition: item.h:17
ItemPointer ginVacuumItemPointers(GinVacuumState *gvs, ItemPointerData *items, int nitem, int *nremaining)
Definition: ginvacuum.c:47
#define PageAddItem(page, item, size, offsetNumber, overwrite, is_heap)
Definition: bufpage.h:413
#define GinGetNPosting(itup)
Definition: ginblock.h:212
#define PageGetMaxOffsetNumber(page)
Definition: bufpage.h:354
#define GinGetPosting(itup)
Definition: ginblock.h:222
#define GinItupIsCompressed(itup)
Definition: ginblock.h:223
uint16 OffsetNumber
Definition: off.h:24
ItemPointerData * ItemPointer
Definition: itemptr.h:48
void pfree(void *pointer)
Definition: mcxt.c:992
#define ERROR
Definition: elog.h:43
signed char GinNullCategory
Definition: ginblock.h:190
OffsetNumber gintuple_get_attrnum(GinState *ginstate, IndexTuple tuple)
Definition: ginutil.c:213
#define FirstOffsetNumber
Definition: off.h:27
IndexTupleData * IndexTuple
Definition: itup.h:53
#define RelationGetRelationName(relation)
Definition: rel.h:433
Datum gintuple_get_key(GinState *ginstate, IndexTuple tuple, GinNullCategory *category)
Definition: ginutil.c:246
#define BufferGetPage(buffer)
Definition: bufmgr.h:160
#define PageGetItemId(page, offsetNumber)
Definition: bufpage.h:232
uintptr_t Datum
Definition: postgres.h:374
#define GinGetDownlink(itup)
Definition: ginblock.h:241
#define NULL
Definition: c.h:226
IndexTuple GinFormTuple(GinState *ginstate, OffsetNumber attnum, Datum key, GinNullCategory category, Pointer data, Size dataSize, int nipd, bool errorTooBig)
Definition: ginentrypage.c:45
#define GinIsPostingTree(itup)
Definition: ginblock.h:215
Page PageGetTempPageCopy(Page page)
Definition: bufpage.c:365
#define SizeOfGinPostingList(plist)
Definition: ginblock.h:326
#define GinMaxItemSize
Definition: ginblock.h:232
int i
#define elog
Definition: elog.h:219
#define PageGetItem(page, itemId)
Definition: bufpage.h:337
Pointer Page
Definition: bufpage.h:74
#define IndexTupleSize(itup)
Definition: itup.h:70
GinState ginstate
Definition: ginvacuum.c:33
ItemPointer ginVacuumItemPointers ( GinVacuumState gvs,
ItemPointerData items,
int  nitem,
int *  nremaining 
)

Definition at line 47 of file ginvacuum.c.

References GinVacuumState::callback, GinVacuumState::callback_state, i, NULL, IndexBulkDeleteResult::num_index_tuples, palloc(), remaining, GinVacuumState::result, and IndexBulkDeleteResult::tuples_removed.

Referenced by ginVacuumEntryPage(), and ginVacuumPostingTreeLeaf().

49 {
50  int i,
51  remaining = 0;
52  ItemPointer tmpitems = NULL;
53 
54  /*
55  * Iterate over TIDs array
56  */
57  for (i = 0; i < nitem; i++)
58  {
59  if (gvs->callback(items + i, gvs->callback_state))
60  {
61  gvs->result->tuples_removed += 1;
62  if (!tmpitems)
63  {
64  /*
65  * First TID to be deleted: allocate memory to hold the
66  * remaining items.
67  */
68  tmpitems = palloc(sizeof(ItemPointerData) * nitem);
69  memcpy(tmpitems, items, sizeof(ItemPointerData) * i);
70  }
71  }
72  else
73  {
74  gvs->result->num_index_tuples += 1;
75  if (tmpitems)
76  tmpitems[remaining] = items[i];
77  remaining++;
78  }
79  }
80 
81  *nremaining = remaining;
82  return tmpitems;
83 }
int remaining
Definition: informix.c:692
double tuples_removed
Definition: genam.h:77
IndexBulkDeleteResult * result
Definition: ginvacuum.c:30
IndexBulkDeleteCallback callback
Definition: ginvacuum.c:31
void * callback_state
Definition: ginvacuum.c:32
#define NULL
Definition: c.h:226
void * palloc(Size size)
Definition: mcxt.c:891
int i
double num_index_tuples
Definition: genam.h:76
static void ginVacuumPostingTree ( GinVacuumState gvs,
BlockNumber  rootBlkno 
)
static

Definition at line 371 of file ginvacuum.c.

References Assert, DataPageDeleteStack::child, FALSE, ginScanToDelete(), ginVacuumPostingTreeLeaves(), InvalidBlockNumber, InvalidBuffer, InvalidOffsetNumber, DataPageDeleteStack::isRoot, DataPageDeleteStack::leftBlkno, pfree(), TRUE, UnlockReleaseBuffer(), and vacuum_delay_point().

Referenced by ginbulkdelete().

372 {
373  Buffer rootBuffer = InvalidBuffer;
374  DataPageDeleteStack root,
375  *ptr,
376  *tmp;
377 
378  if (ginVacuumPostingTreeLeaves(gvs, rootBlkno, TRUE, &rootBuffer) == FALSE)
379  {
380  Assert(rootBuffer == InvalidBuffer);
381  return;
382  }
383 
384  memset(&root, 0, sizeof(DataPageDeleteStack));
386  root.isRoot = TRUE;
387 
389 
390  ginScanToDelete(gvs, rootBlkno, TRUE, &root, InvalidOffsetNumber);
391 
392  ptr = root.child;
393  while (ptr)
394  {
395  tmp = ptr->child;
396  pfree(ptr);
397  ptr = tmp;
398  }
399 
400  UnlockReleaseBuffer(rootBuffer);
401 }
static bool ginVacuumPostingTreeLeaves(GinVacuumState *gvs, BlockNumber blkno, bool isRoot, Buffer *rootBuffer)
Definition: ginvacuum.c:113
#define InvalidBuffer
Definition: buf.h:25
BlockNumber leftBlkno
Definition: ginvacuum.c:292
void pfree(void *pointer)
Definition: mcxt.c:992
void UnlockReleaseBuffer(Buffer buffer)
Definition: bufmgr.c:3315
#define FALSE
Definition: c.h:218
struct DataPageDeleteStack * child
Definition: ginvacuum.c:288
#define InvalidOffsetNumber
Definition: off.h:26
#define Assert(condition)
Definition: c.h:671
#define InvalidBlockNumber
Definition: block.h:33
#define TRUE
Definition: c.h:214
void vacuum_delay_point(void)
Definition: vacuum.c:1515
int Buffer
Definition: buf.h:23
static bool ginScanToDelete(GinVacuumState *gvs, BlockNumber blkno, bool isRoot, DataPageDeleteStack *parent, OffsetNumber myoff)
Definition: ginvacuum.c:300
static bool ginVacuumPostingTreeLeaves ( GinVacuumState gvs,
BlockNumber  blkno,
bool  isRoot,
Buffer rootBuffer 
)
static

Definition at line 113 of file ginvacuum.c.

References Assert, BufferGetPage, FALSE, FirstOffsetNumber, GIN_EXCLUSIVE, GinDataLeafPageIsEmpty, GinDataPageGetPostingItem, GinPageGetOpaque, GinPageIsData, GinPageIsLeaf, ginVacuumPostingTreeLeaf(), i, GinVacuumState::index, LockBuffer(), LockBufferForCleanup(), MAIN_FORKNUM, MemoryContextReset(), MemoryContextSwitchTo(), NULL, PostingItemGetBlockNumber, RBM_NORMAL, ReadBufferExtended(), GinVacuumState::strategy, GinVacuumState::tmpCxt, TRUE, and UnlockReleaseBuffer().

Referenced by ginVacuumPostingTree().

114 {
115  Buffer buffer;
116  Page page;
117  bool hasVoidPage = FALSE;
118  MemoryContext oldCxt;
119 
120  buffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, blkno,
121  RBM_NORMAL, gvs->strategy);
122  page = BufferGetPage(buffer);
123 
124  /*
125  * We should be sure that we don't concurrent with inserts, insert process
126  * never release root page until end (but it can unlock it and lock
127  * again). New scan can't start but previously started ones work
128  * concurrently.
129  */
130  if (isRoot)
131  LockBufferForCleanup(buffer);
132  else
133  LockBuffer(buffer, GIN_EXCLUSIVE);
134 
135  Assert(GinPageIsData(page));
136 
137  if (GinPageIsLeaf(page))
138  {
139  oldCxt = MemoryContextSwitchTo(gvs->tmpCxt);
140  ginVacuumPostingTreeLeaf(gvs->index, buffer, gvs);
141  MemoryContextSwitchTo(oldCxt);
143 
144  /* if root is a leaf page, we don't desire further processing */
145  if (!isRoot && !hasVoidPage && GinDataLeafPageIsEmpty(page))
146  hasVoidPage = TRUE;
147  }
148  else
149  {
150  OffsetNumber i;
151  bool isChildHasVoid = FALSE;
152 
153  for (i = FirstOffsetNumber; i <= GinPageGetOpaque(page)->maxoff; i++)
154  {
155  PostingItem *pitem = GinDataPageGetPostingItem(page, i);
156 
158  isChildHasVoid = TRUE;
159  }
160 
161  if (isChildHasVoid)
162  hasVoidPage = TRUE;
163  }
164 
165  /*
166  * if we have root and there are empty pages in tree, then we don't
167  * release lock to go further processing and guarantee that tree is unused
168  */
169  if (!(isRoot && hasVoidPage))
170  {
171  UnlockReleaseBuffer(buffer);
172  }
173  else
174  {
175  Assert(rootBuffer);
176  *rootBuffer = buffer;
177  }
178 
179  return hasVoidPage;
180 }
static bool ginVacuumPostingTreeLeaves(GinVacuumState *gvs, BlockNumber blkno, bool isRoot, Buffer *rootBuffer)
Definition: ginvacuum.c:113
void LockBufferForCleanup(Buffer buffer)
Definition: bufmgr.c:3586
Relation index
Definition: ginvacuum.c:29
Buffer ReadBufferExtended(Relation reln, ForkNumber forkNum, BlockNumber blockNum, ReadBufferMode mode, BufferAccessStrategy strategy)
Definition: bufmgr.c:640
BufferAccessStrategy strategy
Definition: ginvacuum.c:34
static MemoryContext MemoryContextSwitchTo(MemoryContext context)
Definition: palloc.h:109
void MemoryContextReset(MemoryContext context)
Definition: mcxt.c:135
#define GinPageGetOpaque(page)
Definition: ginblock.h:109
#define GinDataLeafPageIsEmpty(page)
Definition: ginblock.h:267
uint16 OffsetNumber
Definition: off.h:24
void UnlockReleaseBuffer(Buffer buffer)
Definition: bufmgr.c:3315
#define GinDataPageGetPostingItem(page, i)
Definition: ginblock.h:282
#define FALSE
Definition: c.h:218
void ginVacuumPostingTreeLeaf(Relation indexrel, Buffer buffer, GinVacuumState *gvs)
Definition: gindatapage.c:731
#define FirstOffsetNumber
Definition: off.h:27
#define GinPageIsLeaf(page)
Definition: ginblock.h:111
#define BufferGetPage(buffer)
Definition: bufmgr.h:160
#define GIN_EXCLUSIVE
Definition: gin_private.h:45
void LockBuffer(Buffer buffer, int mode)
Definition: bufmgr.c:3529
#define GinPageIsData(page)
Definition: ginblock.h:114
#define PostingItemGetBlockNumber(pointer)
Definition: ginblock.h:176
#define NULL
Definition: c.h:226
#define Assert(condition)
Definition: c.h:671
int i
#define TRUE
Definition: c.h:214
MemoryContext tmpCxt
Definition: ginvacuum.c:35
int Buffer
Definition: buf.h:23
Pointer Page
Definition: bufpage.h:74
static void xlogVacuumPage ( Relation  index,
Buffer  buffer 
)
static

Definition at line 89 of file ginvacuum.c.

References Assert, BufferGetPage, GinPageIsData, GinPageIsLeaf, PageSetLSN, REGBUF_FORCE_IMAGE, REGBUF_STANDARD, RelationNeedsWAL, XLOG_GIN_VACUUM_PAGE, XLogBeginInsert(), XLogInsert(), and XLogRegisterBuffer().

Referenced by ginbulkdelete().

90 {
91  Page page = BufferGetPage(buffer);
92  XLogRecPtr recptr;
93 
94  /* This is only used for entry tree leaf pages. */
95  Assert(!GinPageIsData(page));
96  Assert(GinPageIsLeaf(page));
97 
98  if (!RelationNeedsWAL(index))
99  return;
100 
101  /*
102  * Always create a full image, we don't track the changes on the page at
103  * any more fine-grained level. This could obviously be improved...
104  */
105  XLogBeginInsert();
107 
108  recptr = XLogInsert(RM_GIN_ID, XLOG_GIN_VACUUM_PAGE);
109  PageSetLSN(page, recptr);
110 }
void XLogRegisterBuffer(uint8 block_id, Buffer buffer, uint8 flags)
Definition: xloginsert.c:213
#define REGBUF_STANDARD
Definition: xloginsert.h:35
#define GinPageIsLeaf(page)
Definition: ginblock.h:111
#define BufferGetPage(buffer)
Definition: bufmgr.h:160
#define REGBUF_FORCE_IMAGE
Definition: xloginsert.h:30
#define XLOG_GIN_VACUUM_PAGE
Definition: ginxlog.h:137
XLogRecPtr XLogInsert(RmgrId rmid, uint8 info)
Definition: xloginsert.c:415
#define GinPageIsData(page)
Definition: ginblock.h:114
uint64 XLogRecPtr
Definition: xlogdefs.h:21
#define Assert(condition)
Definition: c.h:671
#define RelationNeedsWAL(relation)
Definition: rel.h:502
void XLogBeginInsert(void)
Definition: xloginsert.c:120
#define PageSetLSN(page, lsn)
Definition: bufpage.h:365
Pointer Page
Definition: bufpage.h:74