1 /*-------------------------------------------------------------------------
4 * the postgres vacuum cleaner
6 * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
7 * Portions Copyright (c) 1994, Regents of the University of California
11 * $Header: /cvsroot/pgsql/src/backend/commands/vacuum.c,v 1.183 2001/01/14 05:08:15 tgl Exp $
13 *-------------------------------------------------------------------------
17 #include <sys/types.h>
23 #ifndef HAVE_GETRUSAGE
24 #include "rusagestub.h"
27 #include <sys/resource.h>
30 #include "access/genam.h"
31 #include "access/heapam.h"
32 #include "access/xlog.h"
33 #include "catalog/catalog.h"
34 #include "catalog/catname.h"
35 #include "catalog/index.h"
36 #include "commands/vacuum.h"
37 #include "miscadmin.h"
38 #include "nodes/execnodes.h"
39 #include "storage/sinval.h"
40 #include "storage/smgr.h"
41 #include "tcop/tcopprot.h"
42 #include "utils/acl.h"
43 #include "utils/builtins.h"
44 #include "utils/fmgroids.h"
45 #include "utils/inval.h"
46 #include "utils/relcache.h"
47 #include "utils/syscache.h"
48 #include "utils/temprel.h"
50 extern XLogRecPtr log_heap_clean(Relation reln, Buffer buffer,
51 char *unused, int unlen);
52 extern XLogRecPtr log_heap_move(Relation reln,
53 Buffer oldbuf, ItemPointerData from,
54 Buffer newbuf, HeapTuple newtup);
56 static MemoryContext vac_context = NULL;
58 static int MESSAGE_LEVEL; /* message level */
60 static TransactionId XmaxRecent;
62 /* non-export function prototypes */
63 static void vacuum_init(void);
64 static void vacuum_shutdown(void);
65 static void vac_vacuum(NameData *VacRelP, bool analyze, List *anal_cols2);
66 static VRelList getrels(NameData *VacRelP);
67 static void vacuum_rel(Oid relid);
68 static void scan_heap(VRelStats *vacrelstats, Relation onerel, VacPageList vacuum_pages, VacPageList fraged_pages);
69 static void repair_frag(VRelStats *vacrelstats, Relation onerel, VacPageList vacuum_pages, VacPageList fraged_pages, int nindices, Relation *Irel);
70 static void vacuum_heap(VRelStats *vacrelstats, Relation onerel, VacPageList vacpagelist);
71 static void vacuum_page(Relation onerel, Buffer buffer, VacPage vacpage);
72 static void vacuum_index(VacPageList vacpagelist, Relation indrel, int num_tuples, int keep_tuples);
73 static void scan_index(Relation indrel, int num_tuples);
74 static void update_relstats(Oid relid, int num_pages, int num_tuples, bool hasindex, VRelStats *vacrelstats);
75 static VacPage tid_reaped(ItemPointer itemptr, VacPageList vacpagelist);
76 static void reap_page(VacPageList vacpagelist, VacPage vacpage);
77 static void vpage_insert(VacPageList vacpagelist, VacPage vpnew);
78 static void get_indices(Relation relation, int *nindices, Relation **Irel);
79 static void close_indices(int nindices, Relation *Irel);
80 static IndexInfo **get_index_desc(Relation onerel, int nindices,
82 static void *vac_find_eq(void *bot, int nelem, int size, void *elm,
83 int (*compar) (const void *, const void *));
84 static int vac_cmp_blk(const void *left, const void *right);
85 static int vac_cmp_offno(const void *left, const void *right);
86 static int vac_cmp_vtlinks(const void *left, const void *right);
87 static bool enough_space(VacPage vacpage, Size len);
88 static char *show_rusage(struct rusage * ru0);
92 vacuum(char *vacrel, bool verbose, bool analyze, List *anal_cols)
98 List *anal_cols2 = NIL;
100 if (anal_cols != NIL && !analyze)
101 elog(ERROR, "Can't vacuum columns, only tables. You can 'vacuum analyze' columns.");
104 * We cannot run VACUUM inside a user transaction block; if we were
105 * inside a transaction, then our commit- and
106 * start-transaction-command calls would not have the intended effect!
107 * Furthermore, the forced commit that occurs before truncating the
108 * relation's file would have the effect of committing the rest of the
109 * user's transaction too, which would certainly not be the desired
112 if (IsTransactionBlock())
113 elog(ERROR, "VACUUM cannot run inside a BEGIN/END block");
116 MESSAGE_LEVEL = NOTICE;
118 MESSAGE_LEVEL = DEBUG;
121 * Create special memory context for cross-transaction storage.
123 * Since it is a child of QueryContext, it will go away eventually
124 * even if we suffer an error; there's no need for special abort
127 vac_context = AllocSetContextCreate(QueryContext,
129 ALLOCSET_DEFAULT_MINSIZE,
130 ALLOCSET_DEFAULT_INITSIZE,
131 ALLOCSET_DEFAULT_MAXSIZE);
133 /* vacrel gets de-allocated on xact commit, so copy it to safe storage */
136 namestrcpy(&VacRel, vacrel);
137 VacRelName = &VacRel;
142 /* must also copy the column list, if any, to safe storage */
143 old = MemoryContextSwitchTo(vac_context);
144 foreach(le, anal_cols)
146 char *col = (char *) lfirst(le);
148 anal_cols2 = lappend(anal_cols2, pstrdup(col));
150 MemoryContextSwitchTo(old);
153 * Start up the vacuum cleaner.
155 * NOTE: since this commits the current transaction, the memory holding
156 * any passed-in parameters gets freed here. We must have already
157 * copied pass-by-reference parameters to safe storage. Don't make me
162 /* vacuum the database */
163 vac_vacuum(VacRelName, analyze, anal_cols2);
170 * vacuum_init(), vacuum_shutdown() -- start up and shut down the vacuum cleaner.
172 * Formerly, there was code here to prevent more than one VACUUM from
173 * executing concurrently in the same database. However, there's no
174 * good reason to prevent that, and manually removing lockfiles after
175 * a vacuum crash was a pain for dbadmins. So, forget about lockfiles,
176 * and just rely on the exclusive lock we grab on each target table
177 * to ensure that there aren't two VACUUMs running on the same table
180 * The strangeness with committing and starting transactions in the
181 * init and shutdown routines is due to the fact that the vacuum cleaner
182 * is invoked via an SQL command, and so is already executing inside
183 * a transaction. We need to leave ourselves in a predictable state
184 * on entry and exit to the vacuum cleaner. We commit the transaction
185 * started in PostgresMain() inside vacuum_init(), and start one in
186 * vacuum_shutdown() to match the commit waiting for us back in
192 /* matches the StartTransaction in PostgresMain() */
193 CommitTransactionCommand();
199 /* on entry, we are not in a transaction */
202 * Flush the init file that relcache.c uses to save startup time. The
203 * next backend startup will rebuild the init file with up-to-date
204 * information from pg_class. This lets the optimizer see the stats
205 * that we've collected for certain critical system indexes. See
206 * relcache.c for more details.
208 * Ignore any failure to unlink the file, since it might not be there if
209 * no backend has been started since the last vacuum...
211 unlink(RELCACHE_INIT_FILENAME);
213 /* matches the CommitTransaction in PostgresMain() */
214 StartTransactionCommand();
217 * Clean up working storage --- note we must do this after
218 * StartTransactionCommand, else we might be trying to delete
219 * the active context!
221 MemoryContextDelete(vac_context);
226 * vac_vacuum() -- vacuum the database.
228 * This routine builds a list of relations to vacuum, and then calls
229 * code that vacuums them one at a time. We are careful to vacuum each
230 * relation in a separate transaction in order to avoid holding too many
234 vac_vacuum(NameData *VacRelP, bool analyze, List *anal_cols2)
239 /* get list of relations */
240 vrl = getrels(VacRelP);
242 /* vacuum each heap relation */
243 for (cur = vrl; cur != (VRelList) NULL; cur = cur->vrl_next)
245 vacuum_rel(cur->vrl_relid);
246 /* analyze separately so locking is minimized */
248 analyze_rel(cur->vrl_relid, anal_cols2, MESSAGE_LEVEL);
253 getrels(NameData *VacRelP)
268 StartTransactionCommand();
270 if (NameStr(*VacRelP))
274 * we could use the cache here, but it is clearer to use scankeys
275 * for both vacuum cases, bjm 2000/01/19
277 char *nontemp_relname;
279 /* We must re-map temp table names bjm 2000-04-06 */
280 nontemp_relname = get_temp_rel_by_username(NameStr(*VacRelP));
281 if (nontemp_relname == NULL)
282 nontemp_relname = NameStr(*VacRelP);
284 ScanKeyEntryInitialize(&key, 0x0, Anum_pg_class_relname,
286 PointerGetDatum(nontemp_relname));
290 ScanKeyEntryInitialize(&key, 0x0, Anum_pg_class_relkind,
291 F_CHAREQ, CharGetDatum('r'));
294 vrl = cur = (VRelList) NULL;
296 rel = heap_openr(RelationRelationName, AccessShareLock);
297 tupdesc = RelationGetDescr(rel);
299 scan = heap_beginscan(rel, false, SnapshotNow, 1, &key);
301 while (HeapTupleIsValid(tuple = heap_getnext(scan, 0)))
305 d = heap_getattr(tuple, Anum_pg_class_relname, tupdesc, &n);
308 d = heap_getattr(tuple, Anum_pg_class_relkind, tupdesc, &n);
310 rkind = DatumGetChar(d);
312 if (rkind != RELKIND_RELATION)
314 elog(NOTICE, "Vacuum: can not process indices, views and certain system tables");
318 /* get a relation list entry for this guy */
319 if (vrl == (VRelList) NULL)
320 vrl = cur = (VRelList)
321 MemoryContextAlloc(vac_context, sizeof(VRelListData));
324 cur->vrl_next = (VRelList)
325 MemoryContextAlloc(vac_context, sizeof(VRelListData));
329 cur->vrl_relid = tuple->t_data->t_oid;
330 cur->vrl_next = (VRelList) NULL;
334 heap_close(rel, AccessShareLock);
337 elog(NOTICE, "Vacuum: table not found");
339 CommitTransactionCommand();
345 * vacuum_rel() -- vacuum one heap relation
347 * This routine vacuums a single heap, cleans out its indices, and
348 * updates its num_pages and num_tuples statistics.
350 * Doing one heap at a time incurs extra overhead, since we need to
351 * check that the heap exists again just before we vacuum it. The
352 * reason that we do this is so that vacuuming can be spread across
353 * many small transactions. Otherwise, two-phase locking would require
354 * us to lock the entire database during one pass of the vacuum cleaner.
356 * At entry and exit, we are not inside a transaction.
359 vacuum_rel(Oid relid)
363 VacPageListData vacuum_pages; /* List of pages to vacuum and/or clean
365 VacPageListData fraged_pages; /* List of pages with space enough for
370 VRelStats *vacrelstats;
371 bool reindex = false;
374 /* Begin a transaction for vacuuming this relation */
375 StartTransactionCommand();
378 * Check for user-requested abort. Note we want this to be inside a
379 * transaction, so xact.c doesn't issue useless NOTICE.
381 CHECK_FOR_INTERRUPTS();
384 * Race condition -- if the pg_class tuple has gone away since the
385 * last time we saw it, we don't need to vacuum it.
387 if (!SearchSysCacheExists(RELOID,
388 ObjectIdGetDatum(relid),
391 CommitTransactionCommand();
396 * Open the class, get an exclusive lock on it, and check permissions.
398 * Note we choose to treat permissions failure as a NOTICE and keep
399 * trying to vacuum the rest of the DB --- is this appropriate?
401 onerel = heap_open(relid, AccessExclusiveLock);
403 if (!pg_ownercheck(GetUserId(), RelationGetRelationName(onerel),
406 elog(NOTICE, "Skipping \"%s\" --- only table owner can VACUUM it",
407 RelationGetRelationName(onerel));
408 heap_close(onerel, AccessExclusiveLock);
409 CommitTransactionCommand();
414 * Get a session-level exclusive lock too. This will protect our
415 * exclusive access to the relation across multiple transactions,
416 * so that we can vacuum the relation's TOAST table (if any) secure
417 * in the knowledge that no one is diddling the parent relation.
419 * NOTE: this cannot block, even if someone else is waiting for access,
420 * because the lock manager knows that both lock requests are from the
423 onerelid = onerel->rd_lockInfo.lockRelId;
424 LockRelationForSession(&onerelid, AccessExclusiveLock);
427 * Remember the relation's TOAST relation for later
429 toast_relid = onerel->rd_rel->reltoastrelid;
432 * Set up statistics-gathering machinery.
434 vacrelstats = (VRelStats *) palloc(sizeof(VRelStats));
435 vacrelstats->relid = relid;
436 vacrelstats->num_pages = vacrelstats->num_tuples = 0;
437 vacrelstats->hasindex = false;
439 GetXmaxRecent(&XmaxRecent);
443 vacuum_pages.num_pages = fraged_pages.num_pages = 0;
444 scan_heap(vacrelstats, onerel, &vacuum_pages, &fraged_pages);
445 if (IsIgnoringSystemIndexes() &&
446 IsSystemRelationName(RelationGetRelationName(onerel)))
449 /* Now open indices */
451 Irel = (Relation *) NULL;
452 get_indices(onerel, &nindices, &Irel);
455 else if (!RelationGetForm(onerel)->relhasindex)
458 vacrelstats->hasindex = true;
460 vacrelstats->hasindex = false;
463 for (i = 0; i < nindices; i++)
464 index_close(Irel[i]);
465 Irel = (Relation *) NULL;
466 activate_indexes_of_a_table(relid, false);
469 /* Clean/scan index relation(s) */
470 if (Irel != (Relation *) NULL)
472 if (vacuum_pages.num_pages > 0)
474 for (i = 0; i < nindices; i++)
475 vacuum_index(&vacuum_pages, Irel[i],
476 vacrelstats->num_tuples, 0);
480 /* just scan indices to update statistic */
481 for (i = 0; i < nindices; i++)
482 scan_index(Irel[i], vacrelstats->num_tuples);
486 if (fraged_pages.num_pages > 0)
488 /* Try to shrink heap */
489 repair_frag(vacrelstats, onerel, &vacuum_pages, &fraged_pages,
494 if (Irel != (Relation *) NULL)
495 close_indices(nindices, Irel);
496 if (vacuum_pages.num_pages > 0)
498 /* Clean pages from vacuum_pages list */
499 vacuum_heap(vacrelstats, onerel, &vacuum_pages);
504 * Flush dirty pages out to disk. We must do this even if we
505 * didn't do anything else, because we want to ensure that all
506 * tuples have correct on-row commit status on disk (see
507 * bufmgr.c's comments for FlushRelationBuffers()).
509 i = FlushRelationBuffers(onerel, vacrelstats->num_pages);
511 elog(ERROR, "VACUUM (vacuum_rel): FlushRelationBuffers returned %d",
516 activate_indexes_of_a_table(relid, true);
518 /* all done with this class, but hold lock until commit */
519 heap_close(onerel, NoLock);
521 /* update statistics in pg_class */
522 update_relstats(vacrelstats->relid, vacrelstats->num_pages,
523 vacrelstats->num_tuples, vacrelstats->hasindex,
527 * Complete the transaction and free all temporary memory used.
529 CommitTransactionCommand();
532 * If the relation has a secondary toast one, vacuum that too
533 * while we still hold the session lock on the master table.
534 * We don't need to propagate "analyze" to it, because the toaster
535 * always uses hardcoded index access and statistics are
536 * totally unimportant for toast relations
538 if (toast_relid != InvalidOid)
539 vacuum_rel(toast_relid);
542 * Now release the session-level lock on the master table.
544 UnlockRelationForSession(&onerelid, AccessExclusiveLock);
548 * scan_heap() -- scan an open heap relation
550 * This routine sets commit times, constructs vacuum_pages list of
551 * empty/uninitialized pages and pages with dead tuples and
552 * ~LP_USED line pointers, constructs fraged_pages list of pages
553 * appropriate for purposes of shrinking and maintains statistics
554 * on the number of live tuples in a heap.
557 scan_heap(VRelStats *vacrelstats, Relation onerel,
558 VacPageList vacuum_pages, VacPageList fraged_pages)
576 uint32 tups_vacuumed,
587 Size min_tlen = MaxTupleSize;
590 bool do_shrinking = true;
591 VTupleLink vtlinks = (VTupleLink) palloc(100 * sizeof(VTupleLinkData));
593 int free_vtlinks = 100;
596 getrusage(RUSAGE_SELF, &ru0);
598 relname = RelationGetRelationName(onerel);
599 elog(MESSAGE_LEVEL, "--Relation %s--", relname);
601 tups_vacuumed = num_tuples = nkeep = nunused = ncrash = empty_pages =
602 new_pages = changed_pages = empty_end_pages = 0;
603 free_size = usable_free_size = 0;
605 nblocks = RelationGetNumberOfBlocks(onerel);
607 vacpage = (VacPage) palloc(sizeof(VacPageData) + MaxOffsetNumber * sizeof(OffsetNumber));
608 vacpage->offsets_used = 0;
610 for (blkno = 0; blkno < nblocks; blkno++)
612 buf = ReadBuffer(onerel, blkno);
613 page = BufferGetPage(buf);
614 vacpage->blkno = blkno;
615 vacpage->offsets_free = 0;
619 elog(NOTICE, "Rel %s: Uninitialized page %u - fixing",
621 PageInit(page, BufferGetPageSize(buf), 0);
622 vacpage->free = ((PageHeader) page)->pd_upper - ((PageHeader) page)->pd_lower;
623 free_size += (vacpage->free - sizeof(ItemIdData));
626 reap_page(vacuum_pages, vacpage);
631 if (PageIsEmpty(page))
633 vacpage->free = ((PageHeader) page)->pd_upper - ((PageHeader) page)->pd_lower;
634 free_size += (vacpage->free - sizeof(ItemIdData));
637 reap_page(vacuum_pages, vacpage);
644 maxoff = PageGetMaxOffsetNumber(page);
645 for (offnum = FirstOffsetNumber;
647 offnum = OffsetNumberNext(offnum))
649 itemid = PageGetItemId(page, offnum);
652 * Collect un-used items too - it's possible to have indices
653 * pointing here after crash.
655 if (!ItemIdIsUsed(itemid))
657 vacpage->offsets[vacpage->offsets_free++] = offnum;
662 tuple.t_datamcxt = NULL;
663 tuple.t_data = (HeapTupleHeader) PageGetItem(page, itemid);
664 tuple.t_len = ItemIdGetLength(itemid);
665 ItemPointerSet(&(tuple.t_self), blkno, offnum);
668 if (!(tuple.t_data->t_infomask & HEAP_XMIN_COMMITTED))
670 if (tuple.t_data->t_infomask & HEAP_XMIN_INVALID)
672 else if (tuple.t_data->t_infomask & HEAP_MOVED_OFF)
674 if (TransactionIdDidCommit((TransactionId)
675 tuple.t_data->t_cmin))
677 tuple.t_data->t_infomask |= HEAP_XMIN_INVALID;
683 tuple.t_data->t_infomask |= HEAP_XMIN_COMMITTED;
687 else if (tuple.t_data->t_infomask & HEAP_MOVED_IN)
689 if (!TransactionIdDidCommit((TransactionId)
690 tuple.t_data->t_cmin))
692 tuple.t_data->t_infomask |= HEAP_XMIN_INVALID;
698 tuple.t_data->t_infomask |= HEAP_XMIN_COMMITTED;
704 if (TransactionIdDidAbort(tuple.t_data->t_xmin))
706 else if (TransactionIdDidCommit(tuple.t_data->t_xmin))
708 tuple.t_data->t_infomask |= HEAP_XMIN_COMMITTED;
711 else if (!TransactionIdIsInProgress(tuple.t_data->t_xmin))
715 * Not Aborted, Not Committed, Not in Progress -
716 * so it's from crashed process. - vadim 11/26/96
723 elog(NOTICE, "Rel %s: TID %u/%u: InsertTransactionInProgress %u - can't shrink relation",
724 relname, blkno, offnum, tuple.t_data->t_xmin);
725 do_shrinking = false;
731 * here we are concerned about tuples with xmin committed and
732 * xmax unknown or committed
734 if (tuple.t_data->t_infomask & HEAP_XMIN_COMMITTED &&
735 !(tuple.t_data->t_infomask & HEAP_XMAX_INVALID))
737 if (tuple.t_data->t_infomask & HEAP_XMAX_COMMITTED)
739 if (tuple.t_data->t_infomask & HEAP_MARKED_FOR_UPDATE)
741 tuple.t_data->t_infomask |= HEAP_XMAX_INVALID;
742 tuple.t_data->t_infomask &=
743 ~(HEAP_XMAX_COMMITTED | HEAP_MARKED_FOR_UPDATE);
749 else if (TransactionIdDidAbort(tuple.t_data->t_xmax))
751 tuple.t_data->t_infomask |= HEAP_XMAX_INVALID;
754 else if (TransactionIdDidCommit(tuple.t_data->t_xmax))
756 if (tuple.t_data->t_infomask & HEAP_MARKED_FOR_UPDATE)
758 tuple.t_data->t_infomask |= HEAP_XMAX_INVALID;
759 tuple.t_data->t_infomask &=
760 ~(HEAP_XMAX_COMMITTED | HEAP_MARKED_FOR_UPDATE);
766 else if (!TransactionIdIsInProgress(tuple.t_data->t_xmax))
770 * Not Aborted, Not Committed, Not in Progress - so it
771 * from crashed process. - vadim 06/02/97
773 tuple.t_data->t_infomask |= HEAP_XMAX_INVALID;
774 tuple.t_data->t_infomask &=
775 ~(HEAP_XMAX_COMMITTED | HEAP_MARKED_FOR_UPDATE);
780 elog(NOTICE, "Rel %s: TID %u/%u: DeleteTransactionInProgress %u - can't shrink relation",
781 relname, blkno, offnum, tuple.t_data->t_xmax);
782 do_shrinking = false;
786 * If tuple is recently deleted then we must not remove it
789 if (tupgone && (tuple.t_data->t_infomask & HEAP_XMIN_INVALID) == 0 && tuple.t_data->t_xmax >= XmaxRecent)
793 if (!(tuple.t_data->t_infomask & HEAP_XMAX_COMMITTED))
795 tuple.t_data->t_infomask |= HEAP_XMAX_COMMITTED;
800 * If we do shrinking and this tuple is updated one
801 * then remember it to construct updated tuple
804 if (do_shrinking && !(ItemPointerEquals(&(tuple.t_self),
805 &(tuple.t_data->t_ctid))))
807 if (free_vtlinks == 0)
810 vtlinks = (VTupleLink) repalloc(vtlinks,
811 (free_vtlinks + num_vtlinks) *
812 sizeof(VTupleLinkData));
814 vtlinks[num_vtlinks].new_tid = tuple.t_data->t_ctid;
815 vtlinks[num_vtlinks].this_tid = tuple.t_self;
825 if (!OidIsValid(tuple.t_data->t_oid))
827 elog(NOTICE, "Rel %s: TID %u/%u: OID IS INVALID. TUPGONE %d.",
828 relname, blkno, offnum, tupgone);
836 * Here we are building a temporary copy of the page with
837 * dead tuples removed. Below we will apply
838 * PageRepairFragmentation to the copy, so that we can
839 * determine how much space will be available after
840 * removal of dead tuples. But note we are NOT changing
841 * the real page yet...
843 if (tempPage == (Page) NULL)
847 pageSize = PageGetPageSize(page);
848 tempPage = (Page) palloc(pageSize);
849 memmove(tempPage, page, pageSize);
852 /* mark it unused on the temp page */
853 lpp = &(((PageHeader) tempPage)->pd_linp[offnum - 1]);
854 lpp->lp_flags &= ~LP_USED;
856 vacpage->offsets[vacpage->offsets_free++] = offnum;
863 if (tuple.t_len < min_tlen)
864 min_tlen = tuple.t_len;
865 if (tuple.t_len > max_tlen)
866 max_tlen = tuple.t_len;
879 if (tempPage != (Page) NULL)
880 { /* Some tuples are gone */
881 PageRepairFragmentation(tempPage, NULL);
882 vacpage->free = ((PageHeader) tempPage)->pd_upper - ((PageHeader) tempPage)->pd_lower;
883 free_size += vacpage->free;
884 reap_page(vacuum_pages, vacpage);
886 tempPage = (Page) NULL;
888 else if (vacpage->offsets_free > 0)
889 { /* there are only ~LP_USED line pointers */
890 vacpage->free = ((PageHeader) page)->pd_upper - ((PageHeader) page)->pd_lower;
891 free_size += vacpage->free;
892 reap_page(vacuum_pages, vacpage);
904 /* save stats in the rel list for use later */
905 vacrelstats->num_tuples = num_tuples;
906 vacrelstats->num_pages = nblocks;
907 /* vacrelstats->natts = attr_cnt;*/
909 min_tlen = max_tlen = 0;
910 vacrelstats->min_tlen = min_tlen;
911 vacrelstats->max_tlen = max_tlen;
913 vacuum_pages->empty_end_pages = empty_end_pages;
914 fraged_pages->empty_end_pages = empty_end_pages;
917 * Try to make fraged_pages keeping in mind that we can't use free
918 * space of "empty" end-pages and last page if it reaped.
920 if (do_shrinking && vacuum_pages->num_pages - empty_end_pages > 0)
922 int nusf; /* blocks usefull for re-using */
924 nusf = vacuum_pages->num_pages - empty_end_pages;
925 if ((vacuum_pages->pagedesc[nusf - 1])->blkno == nblocks - empty_end_pages - 1)
928 for (i = 0; i < nusf; i++)
930 vp = vacuum_pages->pagedesc[i];
931 if (enough_space(vp, min_tlen))
933 vpage_insert(fraged_pages, vp);
934 usable_free_size += vp->free;
939 if (usable_free_size > 0 && num_vtlinks > 0)
941 qsort((char *) vtlinks, num_vtlinks, sizeof(VTupleLinkData),
943 vacrelstats->vtlinks = vtlinks;
944 vacrelstats->num_vtlinks = num_vtlinks;
948 vacrelstats->vtlinks = NULL;
949 vacrelstats->num_vtlinks = 0;
953 elog(MESSAGE_LEVEL, "Pages %u: Changed %u, reaped %u, Empty %u, New %u; \
954 Tup %u: Vac %u, Keep/VTL %u/%u, Crash %u, UnUsed %u, MinLen %lu, MaxLen %lu; \
955 Re-using: Free/Avail. Space %lu/%lu; EndEmpty/Avail. Pages %u/%u. %s",
956 nblocks, changed_pages, vacuum_pages->num_pages, empty_pages,
957 new_pages, num_tuples, tups_vacuumed,
958 nkeep, vacrelstats->num_vtlinks, ncrash,
959 nunused, (unsigned long)min_tlen, (unsigned long)max_tlen,
960 (unsigned long)free_size, (unsigned long)usable_free_size,
961 empty_end_pages, fraged_pages->num_pages,
968 * repair_frag() -- try to repair relation's fragmentation
970 * This routine marks dead tuples as unused and tries re-use dead space
971 * by moving tuples (and inserting indices if needed). It constructs
972 * Nvacpagelist list of free-ed pages (moved tuples) and clean indices
973 * for them after committing (in hack-manner - without losing locks
974 * and freeing memory!) current transaction. It truncates relation
975 * if some end-blocks are gone away.
978 repair_frag(VRelStats *vacrelstats, Relation onerel,
979 VacPageList vacuum_pages, VacPageList fraged_pages,
980 int nindices, Relation *Irel)
990 OffsetNumber offnum = 0,
999 IndexInfo **indexInfo = NULL;
1000 Datum idatum[INDEX_MAX_KEYS];
1001 char inulls[INDEX_MAX_KEYS];
1002 InsertIndexResult iresult;
1003 VacPageListData Nvacpagelist;
1004 VacPage cur_page = NULL,
1009 int last_move_dest_block = -1,
1024 getrusage(RUSAGE_SELF, &ru0);
1026 myXID = GetCurrentTransactionId();
1027 myCID = GetCurrentCommandId();
1029 tupdesc = RelationGetDescr(onerel);
1031 if (Irel != (Relation *) NULL) /* preparation for index' inserts */
1032 indexInfo = get_index_desc(onerel, nindices, Irel);
1034 Nvacpagelist.num_pages = 0;
1035 num_fraged_pages = fraged_pages->num_pages;
1036 Assert(vacuum_pages->num_pages > vacuum_pages->empty_end_pages);
1037 vacuumed_pages = vacuum_pages->num_pages - vacuum_pages->empty_end_pages;
1038 last_vacuum_page = vacuum_pages->pagedesc[vacuumed_pages - 1];
1039 last_vacuum_block = last_vacuum_page->blkno;
1040 cur_buffer = InvalidBuffer;
1043 vacpage = (VacPage) palloc(sizeof(VacPageData) + MaxOffsetNumber * sizeof(OffsetNumber));
1044 vacpage->offsets_used = vacpage->offsets_free = 0;
1047 * Scan pages backwards from the last nonempty page, trying to move
1048 * tuples down to lower pages. Quit when we reach a page that we have
1049 * moved any tuples onto. Note that if a page is still in the
1050 * fraged_pages list (list of candidate move-target pages) when we
1051 * reach it, we will remove it from the list. This ensures we never
1052 * move a tuple up to a higher page number.
1054 * NB: this code depends on the vacuum_pages and fraged_pages lists being
1055 * in order, and on fraged_pages being a subset of vacuum_pages.
1057 nblocks = vacrelstats->num_pages;
1058 for (blkno = nblocks - vacuum_pages->empty_end_pages - 1;
1059 blkno > last_move_dest_block;
1062 buf = ReadBuffer(onerel, blkno);
1063 page = BufferGetPage(buf);
1065 vacpage->offsets_free = 0;
1067 isempty = PageIsEmpty(page);
1070 if (blkno == last_vacuum_block) /* it's reaped page */
1072 if (last_vacuum_page->offsets_free > 0) /* there are dead tuples */
1073 { /* on this page - clean */
1075 LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
1076 vacuum_page(onerel, buf, last_vacuum_page);
1077 LockBuffer(buf, BUFFER_LOCK_UNLOCK);
1083 if (vacuumed_pages > 0)
1085 /* get prev reaped page from vacuum_pages */
1086 last_vacuum_page = vacuum_pages->pagedesc[vacuumed_pages - 1];
1087 last_vacuum_block = last_vacuum_page->blkno;
1091 last_vacuum_page = NULL;
1092 last_vacuum_block = -1;
1094 if (num_fraged_pages > 0 &&
1095 fraged_pages->pagedesc[num_fraged_pages - 1]->blkno ==
1096 (BlockNumber) blkno)
1098 /* page is in fraged_pages too; remove it */
1110 chain_tuple_moved = false; /* no one chain-tuple was moved
1111 * off this page, yet */
1112 vacpage->blkno = blkno;
1113 maxoff = PageGetMaxOffsetNumber(page);
1114 for (offnum = FirstOffsetNumber;
1116 offnum = OffsetNumberNext(offnum))
1118 itemid = PageGetItemId(page, offnum);
1120 if (!ItemIdIsUsed(itemid))
1123 tuple.t_datamcxt = NULL;
1124 tuple.t_data = (HeapTupleHeader) PageGetItem(page, itemid);
1125 tuple_len = tuple.t_len = ItemIdGetLength(itemid);
1126 ItemPointerSet(&(tuple.t_self), blkno, offnum);
1128 if (!(tuple.t_data->t_infomask & HEAP_XMIN_COMMITTED))
1130 if ((TransactionId) tuple.t_data->t_cmin != myXID)
1131 elog(ERROR, "Invalid XID in t_cmin");
1132 if (tuple.t_data->t_infomask & HEAP_MOVED_IN)
1133 elog(ERROR, "HEAP_MOVED_IN was not expected");
1136 * If this (chain) tuple is moved by me already then I
1137 * have to check is it in vacpage or not - i.e. is it moved
1138 * while cleaning this page or some previous one.
1140 if (tuple.t_data->t_infomask & HEAP_MOVED_OFF)
1142 if (keep_tuples == 0)
1144 if (chain_tuple_moved) /* some chains was moved
1146 { /* cleaning this page */
1147 Assert(vacpage->offsets_free > 0);
1148 for (i = 0; i < vacpage->offsets_free; i++)
1150 if (vacpage->offsets[i] == offnum)
1153 if (i >= vacpage->offsets_free) /* not found */
1155 vacpage->offsets[vacpage->offsets_free++] = offnum;
1161 vacpage->offsets[vacpage->offsets_free++] = offnum;
1166 elog(ERROR, "HEAP_MOVED_OFF was expected");
1170 * If this tuple is in the chain of tuples created in updates
1171 * by "recent" transactions then we have to move all chain of
1172 * tuples to another places.
1174 if ((tuple.t_data->t_infomask & HEAP_UPDATED &&
1175 tuple.t_data->t_xmin >= XmaxRecent) ||
1176 (!(tuple.t_data->t_infomask & HEAP_XMAX_INVALID) &&
1177 !(ItemPointerEquals(&(tuple.t_self), &(tuple.t_data->t_ctid)))))
1182 ItemPointerData Ctid;
1183 HeapTupleData tp = tuple;
1184 Size tlen = tuple_len;
1185 VTupleMove vtmove = (VTupleMove)
1186 palloc(100 * sizeof(VTupleMoveData));
1188 int free_vtmove = 100;
1189 VacPage to_vacpage = NULL;
1191 bool freeCbuf = false;
1194 if (vacrelstats->vtlinks == NULL)
1195 elog(ERROR, "No one parent tuple was found");
1196 if (cur_buffer != InvalidBuffer)
1198 WriteBuffer(cur_buffer);
1199 cur_buffer = InvalidBuffer;
1203 * If this tuple is in the begin/middle of the chain then
1204 * we have to move to the end of chain.
1206 while (!(tp.t_data->t_infomask & HEAP_XMAX_INVALID) &&
1207 !(ItemPointerEquals(&(tp.t_self), &(tp.t_data->t_ctid))))
1209 Ctid = tp.t_data->t_ctid;
1211 ReleaseBuffer(Cbuf);
1213 Cbuf = ReadBuffer(onerel,
1214 ItemPointerGetBlockNumber(&Ctid));
1215 Cpage = BufferGetPage(Cbuf);
1216 Citemid = PageGetItemId(Cpage,
1217 ItemPointerGetOffsetNumber(&Ctid));
1218 if (!ItemIdIsUsed(Citemid))
1222 * This means that in the middle of chain there
1223 * was tuple updated by older (than XmaxRecent)
1224 * xaction and this tuple is already deleted by
1225 * me. Actually, upper part of chain should be
1226 * removed and seems that this should be handled
1227 * in scan_heap(), but it's not implemented at
1228 * the moment and so we just stop shrinking here.
1230 ReleaseBuffer(Cbuf);
1233 elog(NOTICE, "Child itemid in update-chain marked as unused - can't continue repair_frag");
1236 tp.t_datamcxt = NULL;
1237 tp.t_data = (HeapTupleHeader) PageGetItem(Cpage, Citemid);
1239 tlen = tp.t_len = ItemIdGetLength(Citemid);
1243 /* first, can chain be moved ? */
1246 if (to_vacpage == NULL ||
1247 !enough_space(to_vacpage, tlen))
1251 * if to_vacpage no longer has enough free space to be
1252 * useful, remove it from fraged_pages list
1254 if (to_vacpage != NULL &&
1255 !enough_space(to_vacpage, vacrelstats->min_tlen))
1257 Assert(num_fraged_pages > to_item);
1258 memmove(fraged_pages->pagedesc + to_item,
1259 fraged_pages->pagedesc + to_item + 1,
1260 sizeof(VacPage) * (num_fraged_pages - to_item - 1));
1263 for (i = 0; i < num_fraged_pages; i++)
1265 if (enough_space(fraged_pages->pagedesc[i], tlen))
1269 /* can't move item anywhere */
1270 if (i == num_fraged_pages)
1272 for (i = 0; i < num_vtmove; i++)
1274 Assert(vtmove[i].vacpage->offsets_used > 0);
1275 (vtmove[i].vacpage->offsets_used)--;
1281 to_vacpage = fraged_pages->pagedesc[to_item];
1283 to_vacpage->free -= MAXALIGN(tlen);
1284 if (to_vacpage->offsets_used >= to_vacpage->offsets_free)
1285 to_vacpage->free -= MAXALIGN(sizeof(ItemIdData));
1286 (to_vacpage->offsets_used)++;
1287 if (free_vtmove == 0)
1290 vtmove = (VTupleMove) repalloc(vtmove,
1291 (free_vtmove + num_vtmove) *
1292 sizeof(VTupleMoveData));
1294 vtmove[num_vtmove].tid = tp.t_self;
1295 vtmove[num_vtmove].vacpage = to_vacpage;
1296 if (to_vacpage->offsets_used == 1)
1297 vtmove[num_vtmove].cleanVpd = true;
1299 vtmove[num_vtmove].cleanVpd = false;
1304 if (!(tp.t_data->t_infomask & HEAP_UPDATED) ||
1305 tp.t_data->t_xmin < XmaxRecent)
1308 /* Well, try to find tuple with old row version */
1315 VTupleLinkData vtld,
1318 vtld.new_tid = tp.t_self;
1320 vac_find_eq((void *) (vacrelstats->vtlinks),
1321 vacrelstats->num_vtlinks,
1322 sizeof(VTupleLinkData),
1326 elog(ERROR, "Parent tuple was not found");
1327 tp.t_self = vtlp->this_tid;
1328 Pbuf = ReadBuffer(onerel,
1329 ItemPointerGetBlockNumber(&(tp.t_self)));
1330 Ppage = BufferGetPage(Pbuf);
1331 Pitemid = PageGetItemId(Ppage,
1332 ItemPointerGetOffsetNumber(&(tp.t_self)));
1333 if (!ItemIdIsUsed(Pitemid))
1334 elog(ERROR, "Parent itemid marked as unused");
1335 Ptp.t_datamcxt = NULL;
1336 Ptp.t_data = (HeapTupleHeader) PageGetItem(Ppage, Pitemid);
1337 Assert(ItemPointerEquals(&(vtld.new_tid),
1338 &(Ptp.t_data->t_ctid)));
1341 * Read above about cases when
1342 * !ItemIdIsUsed(Citemid) (child item is
1343 * removed)... Due to the fact that at the moment
1344 * we don't remove unuseful part of update-chain,
1345 * it's possible to get too old parent row here.
1346 * Like as in the case which caused this problem,
1347 * we stop shrinking here. I could try to find
1348 * real parent row but want not to do it because
1349 * of real solution will be implemented anyway,
1350 * latter, and we are too close to 6.5 release. -
1353 if (Ptp.t_data->t_xmax != tp.t_data->t_xmin)
1356 ReleaseBuffer(Cbuf);
1358 ReleaseBuffer(Pbuf);
1359 for (i = 0; i < num_vtmove; i++)
1361 Assert(vtmove[i].vacpage->offsets_used > 0);
1362 (vtmove[i].vacpage->offsets_used)--;
1365 elog(NOTICE, "Too old parent tuple found - can't continue repair_frag");
1368 #ifdef NOT_USED /* I'm not sure that this will wotk
1372 * If this tuple is updated version of row and it
1373 * was created by the same transaction then no one
1374 * is interested in this tuple - mark it as
1377 if (Ptp.t_data->t_infomask & HEAP_UPDATED &&
1378 Ptp.t_data->t_xmin == Ptp.t_data->t_xmax)
1380 TransactionIdStore(myXID,
1381 (TransactionId *) &(Ptp.t_data->t_cmin));
1382 Ptp.t_data->t_infomask &=
1383 ~(HEAP_XMIN_COMMITTED | HEAP_XMIN_INVALID | HEAP_MOVED_IN);
1384 Ptp.t_data->t_infomask |= HEAP_MOVED_OFF;
1389 tp.t_datamcxt = Ptp.t_datamcxt;
1390 tp.t_data = Ptp.t_data;
1391 tlen = tp.t_len = ItemIdGetLength(Pitemid);
1393 ReleaseBuffer(Cbuf);
1398 if (num_vtmove == 0)
1402 ReleaseBuffer(Cbuf);
1403 if (num_vtmove == 0) /* chain can't be moved */
1408 ItemPointerSetInvalid(&Ctid);
1409 for (ti = 0; ti < num_vtmove; ti++)
1411 VacPage destvacpage = vtmove[ti].vacpage;
1413 /* Get page to move from */
1414 tuple.t_self = vtmove[ti].tid;
1415 Cbuf = ReadBuffer(onerel,
1416 ItemPointerGetBlockNumber(&(tuple.t_self)));
1418 /* Get page to move to */
1419 cur_buffer = ReadBuffer(onerel, destvacpage->blkno);
1421 LockBuffer(cur_buffer, BUFFER_LOCK_EXCLUSIVE);
1422 if (cur_buffer != Cbuf)
1423 LockBuffer(Cbuf, BUFFER_LOCK_EXCLUSIVE);
1425 ToPage = BufferGetPage(cur_buffer);
1426 Cpage = BufferGetPage(Cbuf);
1428 /* NO ELOG(ERROR) TILL CHANGES ARE LOGGED */
1429 START_CRIT_SECTION();
1431 Citemid = PageGetItemId(Cpage,
1432 ItemPointerGetOffsetNumber(&(tuple.t_self)));
1433 tuple.t_datamcxt = NULL;
1434 tuple.t_data = (HeapTupleHeader) PageGetItem(Cpage, Citemid);
1435 tuple_len = tuple.t_len = ItemIdGetLength(Citemid);
1438 * make a copy of the source tuple, and then mark the
1439 * source tuple MOVED_OFF.
1441 heap_copytuple_with_tuple(&tuple, &newtup);
1443 RelationInvalidateHeapTuple(onerel, &tuple);
1445 TransactionIdStore(myXID, (TransactionId *) &(tuple.t_data->t_cmin));
1446 tuple.t_data->t_infomask &=
1447 ~(HEAP_XMIN_COMMITTED | HEAP_XMIN_INVALID | HEAP_MOVED_IN);
1448 tuple.t_data->t_infomask |= HEAP_MOVED_OFF;
1451 * If this page was not used before - clean it.
1453 * NOTE: a nasty bug used to lurk here. It is possible
1454 * for the source and destination pages to be the same
1455 * (since this tuple-chain member can be on a page lower
1456 * than the one we're currently processing in the outer
1457 * loop). If that's true, then after vacuum_page() the
1458 * source tuple will have been moved, and tuple.t_data
1459 * will be pointing at garbage. Therefore we must do
1460 * everything that uses tuple.t_data BEFORE this step!!
1462 * This path is different from the other callers of
1463 * vacuum_page, because we have already incremented the
1464 * vacpage's offsets_used field to account for the
1465 * tuple(s) we expect to move onto the page. Therefore
1466 * vacuum_page's check for offsets_used == 0 is
1467 * wrong. But since that's a good debugging check for
1468 * all other callers, we work around it here rather
1471 if (!PageIsEmpty(ToPage) && vtmove[ti].cleanVpd)
1473 int sv_offsets_used = destvacpage->offsets_used;
1475 destvacpage->offsets_used = 0;
1476 vacuum_page(onerel, cur_buffer, destvacpage);
1477 destvacpage->offsets_used = sv_offsets_used;
1481 * Update the state of the copied tuple, and store it
1482 * on the destination page.
1484 TransactionIdStore(myXID, (TransactionId *) &(newtup.t_data->t_cmin));
1485 newtup.t_data->t_infomask &=
1486 ~(HEAP_XMIN_COMMITTED | HEAP_XMIN_INVALID | HEAP_MOVED_OFF);
1487 newtup.t_data->t_infomask |= HEAP_MOVED_IN;
1488 newoff = PageAddItem(ToPage, (Item) newtup.t_data, tuple_len,
1489 InvalidOffsetNumber, LP_USED);
1490 if (newoff == InvalidOffsetNumber)
1492 elog(STOP, "moving chain: failed to add item with len = %lu to page %u",
1493 (unsigned long)tuple_len, destvacpage->blkno);
1495 newitemid = PageGetItemId(ToPage, newoff);
1496 pfree(newtup.t_data);
1497 newtup.t_datamcxt = NULL;
1498 newtup.t_data = (HeapTupleHeader) PageGetItem(ToPage, newitemid);
1499 ItemPointerSet(&(newtup.t_self), destvacpage->blkno, newoff);
1503 log_heap_move(onerel, Cbuf, tuple.t_self,
1504 cur_buffer, &newtup);
1506 if (Cbuf != cur_buffer)
1508 PageSetLSN(Cpage, recptr);
1509 PageSetSUI(Cpage, ThisStartUpID);
1511 PageSetLSN(ToPage, recptr);
1512 PageSetSUI(ToPage, ThisStartUpID);
1516 if (((int) destvacpage->blkno) > last_move_dest_block)
1517 last_move_dest_block = destvacpage->blkno;
1520 * Set new tuple's t_ctid pointing to itself for last
1521 * tuple in chain, and to next tuple in chain otherwise.
1523 if (!ItemPointerIsValid(&Ctid))
1524 newtup.t_data->t_ctid = newtup.t_self;
1526 newtup.t_data->t_ctid = Ctid;
1527 Ctid = newtup.t_self;
1532 * Remember that we moved tuple from the current page
1533 * (corresponding index tuple will be cleaned).
1536 vacpage->offsets[vacpage->offsets_free++] =
1537 ItemPointerGetOffsetNumber(&(tuple.t_self));
1541 LockBuffer(cur_buffer, BUFFER_LOCK_UNLOCK);
1542 if (cur_buffer != Cbuf)
1543 LockBuffer(Cbuf, BUFFER_LOCK_UNLOCK);
1545 if (Irel != (Relation *) NULL)
1548 * XXX using CurrentMemoryContext here means
1549 * intra-vacuum memory leak for functional indexes.
1550 * Should fix someday.
1552 * XXX This code fails to handle partial indexes!
1553 * Probably should change it to use ExecOpenIndices.
1555 for (i = 0; i < nindices; i++)
1557 FormIndexDatum(indexInfo[i],
1560 CurrentMemoryContext,
1563 iresult = index_insert(Irel[i],
1572 WriteBuffer(cur_buffer);
1575 cur_buffer = InvalidBuffer;
1577 chain_tuple_moved = true;
1581 /* try to find new page for this tuple */
1582 if (cur_buffer == InvalidBuffer ||
1583 !enough_space(cur_page, tuple_len))
1585 if (cur_buffer != InvalidBuffer)
1587 WriteBuffer(cur_buffer);
1588 cur_buffer = InvalidBuffer;
1591 * If previous target page is now too full to add *any*
1592 * tuple to it, remove it from fraged_pages.
1594 if (!enough_space(cur_page, vacrelstats->min_tlen))
1596 Assert(num_fraged_pages > cur_item);
1597 memmove(fraged_pages->pagedesc + cur_item,
1598 fraged_pages->pagedesc + cur_item + 1,
1599 sizeof(VacPage) * (num_fraged_pages - cur_item - 1));
1603 for (i = 0; i < num_fraged_pages; i++)
1605 if (enough_space(fraged_pages->pagedesc[i], tuple_len))
1608 if (i == num_fraged_pages)
1609 break; /* can't move item anywhere */
1611 cur_page = fraged_pages->pagedesc[cur_item];
1612 cur_buffer = ReadBuffer(onerel, cur_page->blkno);
1613 LockBuffer(cur_buffer, BUFFER_LOCK_EXCLUSIVE);
1614 ToPage = BufferGetPage(cur_buffer);
1615 /* if this page was not used before - clean it */
1616 if (!PageIsEmpty(ToPage) && cur_page->offsets_used == 0)
1617 vacuum_page(onerel, cur_buffer, cur_page);
1620 LockBuffer(cur_buffer, BUFFER_LOCK_EXCLUSIVE);
1622 LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
1625 heap_copytuple_with_tuple(&tuple, &newtup);
1627 RelationInvalidateHeapTuple(onerel, &tuple);
1630 * Mark new tuple as moved_in by vacuum and store vacuum XID
1633 TransactionIdStore(myXID, (TransactionId *) &(newtup.t_data->t_cmin));
1634 newtup.t_data->t_infomask &=
1635 ~(HEAP_XMIN_COMMITTED | HEAP_XMIN_INVALID | HEAP_MOVED_OFF);
1636 newtup.t_data->t_infomask |= HEAP_MOVED_IN;
1638 /* NO ELOG(ERROR) TILL CHANGES ARE LOGGED */
1639 START_CRIT_SECTION();
1641 /* add tuple to the page */
1642 newoff = PageAddItem(ToPage, (Item) newtup.t_data, tuple_len,
1643 InvalidOffsetNumber, LP_USED);
1644 if (newoff == InvalidOffsetNumber)
1647 failed to add item with len = %lu to page %u (free space %lu, nusd %u, noff %u)",
1648 (unsigned long)tuple_len, cur_page->blkno, (unsigned long)cur_page->free,
1649 cur_page->offsets_used, cur_page->offsets_free);
1651 newitemid = PageGetItemId(ToPage, newoff);
1652 pfree(newtup.t_data);
1653 newtup.t_datamcxt = NULL;
1654 newtup.t_data = (HeapTupleHeader) PageGetItem(ToPage, newitemid);
1655 ItemPointerSet(&(newtup.t_data->t_ctid), cur_page->blkno, newoff);
1656 newtup.t_self = newtup.t_data->t_ctid;
1659 * Mark old tuple as moved_off by vacuum and store vacuum XID
1662 TransactionIdStore(myXID, (TransactionId *) &(tuple.t_data->t_cmin));
1663 tuple.t_data->t_infomask &=
1664 ~(HEAP_XMIN_COMMITTED | HEAP_XMIN_INVALID | HEAP_MOVED_IN);
1665 tuple.t_data->t_infomask |= HEAP_MOVED_OFF;
1669 log_heap_move(onerel, buf, tuple.t_self,
1670 cur_buffer, &newtup);
1672 PageSetLSN(page, recptr);
1673 PageSetSUI(page, ThisStartUpID);
1674 PageSetLSN(ToPage, recptr);
1675 PageSetSUI(ToPage, ThisStartUpID);
1679 cur_page->offsets_used++;
1681 cur_page->free = ((PageHeader) ToPage)->pd_upper - ((PageHeader) ToPage)->pd_lower;
1682 if (((int) cur_page->blkno) > last_move_dest_block)
1683 last_move_dest_block = cur_page->blkno;
1685 vacpage->offsets[vacpage->offsets_free++] = offnum;
1687 LockBuffer(cur_buffer, BUFFER_LOCK_UNLOCK);
1688 LockBuffer(buf, BUFFER_LOCK_UNLOCK);
1690 /* insert index' tuples if needed */
1691 if (Irel != (Relation *) NULL)
1694 * XXX using CurrentMemoryContext here means
1695 * intra-vacuum memory leak for functional indexes.
1696 * Should fix someday.
1698 * XXX This code fails to handle partial indexes!
1699 * Probably should change it to use ExecOpenIndices.
1701 for (i = 0; i < nindices; i++)
1703 FormIndexDatum(indexInfo[i],
1706 CurrentMemoryContext,
1709 iresult = index_insert(Irel[i],
1719 } /* walk along page */
1721 if (offnum < maxoff && keep_tuples > 0)
1725 for (off = OffsetNumberNext(offnum);
1727 off = OffsetNumberNext(off))
1729 itemid = PageGetItemId(page, off);
1730 if (!ItemIdIsUsed(itemid))
1732 tuple.t_datamcxt = NULL;
1733 tuple.t_data = (HeapTupleHeader) PageGetItem(page, itemid);
1734 if (tuple.t_data->t_infomask & HEAP_XMIN_COMMITTED)
1736 if ((TransactionId) tuple.t_data->t_cmin != myXID)
1737 elog(ERROR, "Invalid XID in t_cmin (4)");
1738 if (tuple.t_data->t_infomask & HEAP_MOVED_IN)
1739 elog(ERROR, "HEAP_MOVED_IN was not expected (2)");
1740 if (tuple.t_data->t_infomask & HEAP_MOVED_OFF)
1742 /* some chains was moved while */
1743 if (chain_tuple_moved)
1744 { /* cleaning this page */
1745 Assert(vacpage->offsets_free > 0);
1746 for (i = 0; i < vacpage->offsets_free; i++)
1748 if (vacpage->offsets[i] == off)
1751 if (i >= vacpage->offsets_free) /* not found */
1753 vacpage->offsets[vacpage->offsets_free++] = off;
1754 Assert(keep_tuples > 0);
1760 vacpage->offsets[vacpage->offsets_free++] = off;
1761 Assert(keep_tuples > 0);
1768 if (vacpage->offsets_free > 0) /* some tuples were moved */
1770 if (chain_tuple_moved) /* else - they are ordered */
1772 qsort((char *) (vacpage->offsets), vacpage->offsets_free,
1773 sizeof(OffsetNumber), vac_cmp_offno);
1775 reap_page(&Nvacpagelist, vacpage);
1783 if (offnum <= maxoff)
1784 break; /* some item(s) left */
1786 } /* walk along relation */
1788 blkno++; /* new number of blocks */
1790 if (cur_buffer != InvalidBuffer)
1792 Assert(num_moved > 0);
1793 WriteBuffer(cur_buffer);
1799 * We have to commit our tuple movings before we truncate the
1800 * relation. Ideally we should do Commit/StartTransactionCommand
1801 * here, relying on the session-level table lock to protect our
1802 * exclusive access to the relation. However, that would require
1803 * a lot of extra code to close and re-open the relation, indices,
1804 * etc. For now, a quick hack: record status of current transaction
1805 * as committed, and continue.
1807 RecordTransactionCommit();
1811 * Clean uncleaned reaped pages from vacuum_pages list list and set
1812 * xmin committed for inserted tuples
1815 for (i = 0, curpage = vacuum_pages->pagedesc; i < vacuumed_pages; i++, curpage++)
1817 Assert((*curpage)->blkno < (BlockNumber) blkno);
1818 buf = ReadBuffer(onerel, (*curpage)->blkno);
1819 LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
1820 page = BufferGetPage(buf);
1821 if ((*curpage)->offsets_used == 0) /* this page was not used */
1823 if (!PageIsEmpty(page))
1824 vacuum_page(onerel, buf, *curpage);
1827 /* this page was used */
1830 max_offset = PageGetMaxOffsetNumber(page);
1831 for (newoff = FirstOffsetNumber;
1832 newoff <= max_offset;
1833 newoff = OffsetNumberNext(newoff))
1835 itemid = PageGetItemId(page, newoff);
1836 if (!ItemIdIsUsed(itemid))
1838 tuple.t_datamcxt = NULL;
1839 tuple.t_data = (HeapTupleHeader) PageGetItem(page, itemid);
1840 if (!(tuple.t_data->t_infomask & HEAP_XMIN_COMMITTED))
1842 if ((TransactionId) tuple.t_data->t_cmin != myXID)
1843 elog(ERROR, "Invalid XID in t_cmin (2)");
1844 if (tuple.t_data->t_infomask & HEAP_MOVED_IN)
1846 tuple.t_data->t_infomask |= HEAP_XMIN_COMMITTED;
1849 else if (tuple.t_data->t_infomask & HEAP_MOVED_OFF)
1850 tuple.t_data->t_infomask |= HEAP_XMIN_INVALID;
1852 elog(ERROR, "HEAP_MOVED_OFF/HEAP_MOVED_IN was expected");
1855 Assert((*curpage)->offsets_used == num_tuples);
1856 checked_moved += num_tuples;
1858 LockBuffer(buf, BUFFER_LOCK_UNLOCK);
1861 Assert(num_moved == checked_moved);
1863 elog(MESSAGE_LEVEL, "Rel %s: Pages: %u --> %u; Tuple(s) moved: %u. %s",
1864 RelationGetRelationName(onerel),
1865 nblocks, blkno, num_moved,
1869 * Reflect the motion of system tuples to catalog cache here.
1871 CommandCounterIncrement();
1873 if (Nvacpagelist.num_pages > 0)
1875 /* vacuum indices again if needed */
1876 if (Irel != (Relation *) NULL)
1882 /* re-sort Nvacpagelist.pagedesc */
1883 for (vpleft = Nvacpagelist.pagedesc,
1884 vpright = Nvacpagelist.pagedesc + Nvacpagelist.num_pages - 1;
1885 vpleft < vpright; vpleft++, vpright--)
1891 Assert(keep_tuples >= 0);
1892 for (i = 0; i < nindices; i++)
1893 vacuum_index(&Nvacpagelist, Irel[i],
1894 vacrelstats->num_tuples, keep_tuples);
1897 /* clean moved tuples from last page in Nvacpagelist list */
1898 if (vacpage->blkno == (BlockNumber) (blkno - 1) &&
1899 vacpage->offsets_free > 0)
1902 OffsetNumber *unused = (OffsetNumber*)unbuf;
1905 buf = ReadBuffer(onerel, vacpage->blkno);
1906 LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
1907 START_CRIT_SECTION();
1908 page = BufferGetPage(buf);
1910 for (offnum = FirstOffsetNumber;
1912 offnum = OffsetNumberNext(offnum))
1914 itemid = PageGetItemId(page, offnum);
1915 if (!ItemIdIsUsed(itemid))
1917 tuple.t_datamcxt = NULL;
1918 tuple.t_data = (HeapTupleHeader) PageGetItem(page, itemid);
1920 if (!(tuple.t_data->t_infomask & HEAP_XMIN_COMMITTED))
1922 if ((TransactionId) tuple.t_data->t_cmin != myXID)
1923 elog(ERROR, "Invalid XID in t_cmin (3)");
1924 if (tuple.t_data->t_infomask & HEAP_MOVED_OFF)
1926 itemid->lp_flags &= ~LP_USED;
1930 elog(ERROR, "HEAP_MOVED_OFF was expected (2)");
1934 Assert(vacpage->offsets_free == num_tuples);
1935 uncnt = PageRepairFragmentation(page, unused);
1938 recptr = log_heap_clean(onerel, buf, (char*)unused,
1939 (char*)(&(unused[uncnt])) - (char*)unused);
1940 PageSetLSN(page, recptr);
1941 PageSetSUI(page, ThisStartUpID);
1944 LockBuffer(buf, BUFFER_LOCK_UNLOCK);
1948 /* now - free new list of reaped pages */
1949 curpage = Nvacpagelist.pagedesc;
1950 for (i = 0; i < Nvacpagelist.num_pages; i++, curpage++)
1952 pfree(Nvacpagelist.pagedesc);
1956 * Flush dirty pages out to disk. We do this unconditionally, even if
1957 * we don't need to truncate, because we want to ensure that all tuples
1958 * have correct on-row commit status on disk (see bufmgr.c's comments
1959 * for FlushRelationBuffers()).
1961 i = FlushRelationBuffers(onerel, blkno);
1963 elog(ERROR, "VACUUM (repair_frag): FlushRelationBuffers returned %d",
1966 /* truncate relation, if needed */
1967 if (blkno < nblocks)
1969 blkno = smgrtruncate(DEFAULT_SMGR, onerel, blkno);
1971 vacrelstats->num_pages = blkno; /* set new number of blocks */
1974 if (Irel != (Relation *) NULL) /* pfree index' allocations */
1976 close_indices(nindices, Irel);
1981 if (vacrelstats->vtlinks != NULL)
1982 pfree(vacrelstats->vtlinks);
1986 * vacuum_heap() -- free dead tuples
1988 * This routine marks dead tuples as unused and truncates relation
1989 * if there are "empty" end-blocks.
1992 vacuum_heap(VRelStats *vacrelstats, Relation onerel, VacPageList vacuum_pages)
1999 nblocks = vacuum_pages->num_pages;
2000 nblocks -= vacuum_pages->empty_end_pages; /* nothing to do with
2003 for (i = 0, vacpage = vacuum_pages->pagedesc; i < nblocks; i++, vacpage++)
2005 if ((*vacpage)->offsets_free > 0)
2007 buf = ReadBuffer(onerel, (*vacpage)->blkno);
2008 LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
2009 vacuum_page(onerel, buf, *vacpage);
2010 LockBuffer(buf, BUFFER_LOCK_UNLOCK);
2016 * Flush dirty pages out to disk. We do this unconditionally, even if
2017 * we don't need to truncate, because we want to ensure that all tuples
2018 * have correct on-row commit status on disk (see bufmgr.c's comments
2019 * for FlushRelationBuffers()).
2021 Assert(vacrelstats->num_pages >= vacuum_pages->empty_end_pages);
2022 nblocks = vacrelstats->num_pages - vacuum_pages->empty_end_pages;
2024 i = FlushRelationBuffers(onerel, nblocks);
2026 elog(ERROR, "VACUUM (vacuum_heap): FlushRelationBuffers returned %d",
2029 /* truncate relation if there are some empty end-pages */
2030 if (vacuum_pages->empty_end_pages > 0)
2032 elog(MESSAGE_LEVEL, "Rel %s: Pages: %u --> %u.",
2033 RelationGetRelationName(onerel),
2034 vacrelstats->num_pages, nblocks);
2035 nblocks = smgrtruncate(DEFAULT_SMGR, onerel, nblocks);
2036 Assert(nblocks >= 0);
2037 vacrelstats->num_pages = nblocks; /* set new number of blocks */
2042 * vacuum_page() -- free dead tuples on a page
2043 * and repair its fragmentation.
2046 vacuum_page(Relation onerel, Buffer buffer, VacPage vacpage)
2049 OffsetNumber *unused = (OffsetNumber*)unbuf;
2051 Page page = BufferGetPage(buffer);
2055 /* There shouldn't be any tuples moved onto the page yet! */
2056 Assert(vacpage->offsets_used == 0);
2058 START_CRIT_SECTION();
2059 for (i = 0; i < vacpage->offsets_free; i++)
2061 itemid = &(((PageHeader) page)->pd_linp[vacpage->offsets[i] - 1]);
2062 itemid->lp_flags &= ~LP_USED;
2064 uncnt = PageRepairFragmentation(page, unused);
2067 recptr = log_heap_clean(onerel, buffer, (char*)unused,
2068 (char*)(&(unused[uncnt])) - (char*)unused);
2069 PageSetLSN(page, recptr);
2070 PageSetSUI(page, ThisStartUpID);
2077 * _scan_index() -- scan one index relation to update statistic.
2081 scan_index(Relation indrel, int num_tuples)
2083 RetrieveIndexResult res;
2084 IndexScanDesc iscan;
2089 getrusage(RUSAGE_SELF, &ru0);
2091 /* walk through the entire index */
2092 iscan = index_beginscan(indrel, false, 0, (ScanKey) NULL);
2095 while ((res = index_getnext(iscan, ForwardScanDirection))
2096 != (RetrieveIndexResult) NULL)
2102 index_endscan(iscan);
2104 /* now update statistics in pg_class */
2105 nipages = RelationGetNumberOfBlocks(indrel);
2106 update_relstats(RelationGetRelid(indrel), nipages, nitups, false, NULL);
2108 elog(MESSAGE_LEVEL, "Index %s: Pages %u; Tuples %u. %s",
2109 RelationGetRelationName(indrel), nipages, nitups,
2112 if (nitups != num_tuples)
2113 elog(NOTICE, "Index %s: NUMBER OF INDEX' TUPLES (%u) IS NOT THE SAME AS HEAP' (%u).\
2114 \n\tRecreate the index.",
2115 RelationGetRelationName(indrel), nitups, num_tuples);
2120 * vacuum_index() -- vacuum one index relation.
2122 * Vpl is the VacPageList of the heap we're currently vacuuming.
2123 * It's locked. Indrel is an index relation on the vacuumed heap.
2124 * We don't set locks on the index relation here, since the indexed
2125 * access methods support locking at different granularities.
2126 * We let them handle it.
2128 * Finally, we arrange to update the index relation's statistics in
2132 vacuum_index(VacPageList vacpagelist, Relation indrel, int num_tuples, int keep_tuples)
2134 RetrieveIndexResult res;
2135 IndexScanDesc iscan;
2136 ItemPointer heapptr;
2138 int num_index_tuples;
2143 getrusage(RUSAGE_SELF, &ru0);
2145 /* walk through the entire index */
2146 iscan = index_beginscan(indrel, false, 0, (ScanKey) NULL);
2148 num_index_tuples = 0;
2150 while ((res = index_getnext(iscan, ForwardScanDirection))
2151 != (RetrieveIndexResult) NULL)
2153 heapptr = &res->heap_iptr;
2155 if ((vp = tid_reaped(heapptr, vacpagelist)) != (VacPage) NULL)
2158 elog(DEBUG, "<%x,%x> -> <%x,%x>",
2159 ItemPointerGetBlockNumber(&(res->index_iptr)),
2160 ItemPointerGetOffsetNumber(&(res->index_iptr)),
2161 ItemPointerGetBlockNumber(&(res->heap_iptr)),
2162 ItemPointerGetOffsetNumber(&(res->heap_iptr)));
2164 if (vp->offsets_free == 0)
2166 elog(NOTICE, "Index %s: pointer to EmptyPage (blk %u off %u) - fixing",
2167 RelationGetRelationName(indrel),
2168 vp->blkno, ItemPointerGetOffsetNumber(heapptr));
2171 index_delete(indrel, &res->index_iptr);
2179 index_endscan(iscan);
2181 /* now update statistics in pg_class */
2182 num_pages = RelationGetNumberOfBlocks(indrel);
2183 update_relstats(RelationGetRelid(indrel), num_pages, num_index_tuples, false, NULL);
2185 elog(MESSAGE_LEVEL, "Index %s: Pages %u; Tuples %u: Deleted %u. %s",
2186 RelationGetRelationName(indrel), num_pages,
2187 num_index_tuples - keep_tuples, tups_vacuumed,
2190 if (num_index_tuples != num_tuples + keep_tuples)
2191 elog(NOTICE, "Index %s: NUMBER OF INDEX' TUPLES (%u) IS NOT THE SAME AS HEAP' (%u).\
2192 \n\tRecreate the index.",
2193 RelationGetRelationName(indrel), num_index_tuples, num_tuples);
2198 * tid_reaped() -- is a particular tid reaped?
2200 * vacpagelist->VacPage_array is sorted in right order.
2203 tid_reaped(ItemPointer itemptr, VacPageList vacpagelist)
2205 OffsetNumber ioffno;
2209 VacPageData vacpage;
2211 vacpage.blkno = ItemPointerGetBlockNumber(itemptr);
2212 ioffno = ItemPointerGetOffsetNumber(itemptr);
2215 vpp = (VacPage *) vac_find_eq((void *) (vacpagelist->pagedesc),
2216 vacpagelist->num_pages, sizeof(VacPage), (void *) &vp,
2219 if (vpp == (VacPage *) NULL)
2220 return (VacPage) NULL;
2223 /* ok - we are on true page */
2225 if (vp->offsets_free == 0)
2226 { /* this is EmptyPage !!! */
2230 voff = (OffsetNumber *) vac_find_eq((void *) (vp->offsets),
2231 vp->offsets_free, sizeof(OffsetNumber), (void *) &ioffno,
2234 if (voff == (OffsetNumber *) NULL)
2235 return (VacPage) NULL;
2242 * update_relstats() -- update statistics for one relation
2244 * Update the whole-relation statistics that are kept in its pg_class
2245 * row. There are additional stats that will be updated if we are
2246 * doing VACUUM ANALYZE, but we always update these stats.
2248 * This routine works for both index and heap relation entries in
2249 * pg_class. We violate no-overwrite semantics here by storing new
2250 * values for the statistics columns directly into the pg_class
2251 * tuple that's already on the page. The reason for this is that if
2252 * we updated these tuples in the usual way, vacuuming pg_class itself
2253 * wouldn't work very well --- by the time we got done with a vacuum
2254 * cycle, most of the tuples in pg_class would've been obsoleted.
2255 * Updating pg_class's own statistics would be especially tricky.
2256 * Of course, this only works for fixed-size never-null columns, but
2260 update_relstats(Oid relid, int num_pages, int num_tuples, bool hasindex,
2261 VRelStats *vacrelstats)
2266 Form_pg_class pgcform;
2270 * update number of tuples and number of pages in pg_class
2272 rd = heap_openr(RelationRelationName, RowExclusiveLock);
2274 ctup = SearchSysCache(RELOID,
2275 ObjectIdGetDatum(relid),
2277 if (!HeapTupleIsValid(ctup))
2278 elog(ERROR, "pg_class entry for relid %u vanished during vacuuming",
2281 /* get the buffer cache tuple */
2282 rtup.t_self = ctup->t_self;
2283 ReleaseSysCache(ctup);
2284 heap_fetch(rd, SnapshotNow, &rtup, &buffer);
2286 /* overwrite the existing statistics in the tuple */
2287 pgcform = (Form_pg_class) GETSTRUCT(&rtup);
2288 pgcform->reltuples = num_tuples;
2289 pgcform->relpages = num_pages;
2290 pgcform->relhasindex = hasindex;
2292 /* invalidate the tuple in the cache and write the buffer */
2293 RelationInvalidateHeapTuple(rd, &rtup);
2294 WriteBuffer(buffer);
2296 heap_close(rd, RowExclusiveLock);
2300 * reap_page() -- save a page on the array of reaped pages.
2302 * As a side effect of the way that the vacuuming loop for a given
2303 * relation works, higher pages come after lower pages in the array
2304 * (and highest tid on a page is last).
2307 reap_page(VacPageList vacpagelist, VacPage vacpage)
2311 /* allocate a VacPageData entry */
2312 newvacpage = (VacPage) palloc(sizeof(VacPageData) + vacpage->offsets_free * sizeof(OffsetNumber));
2315 if (vacpage->offsets_free > 0)
2316 memmove(newvacpage->offsets, vacpage->offsets, vacpage->offsets_free * sizeof(OffsetNumber));
2317 newvacpage->blkno = vacpage->blkno;
2318 newvacpage->free = vacpage->free;
2319 newvacpage->offsets_used = vacpage->offsets_used;
2320 newvacpage->offsets_free = vacpage->offsets_free;
2322 /* insert this page into vacpagelist list */
2323 vpage_insert(vacpagelist, newvacpage);
2328 vpage_insert(VacPageList vacpagelist, VacPage vpnew)
2330 #define PG_NPAGEDESC 1024
2332 /* allocate a VacPage entry if needed */
2333 if (vacpagelist->num_pages == 0)
2335 vacpagelist->pagedesc = (VacPage *) palloc(PG_NPAGEDESC * sizeof(VacPage));
2336 vacpagelist->num_allocated_pages = PG_NPAGEDESC;
2338 else if (vacpagelist->num_pages >= vacpagelist->num_allocated_pages)
2340 vacpagelist->num_allocated_pages *= 2;
2341 vacpagelist->pagedesc = (VacPage *) repalloc(vacpagelist->pagedesc, vacpagelist->num_allocated_pages * sizeof(VacPage));
2343 vacpagelist->pagedesc[vacpagelist->num_pages] = vpnew;
2344 (vacpagelist->num_pages)++;
2349 vac_find_eq(void *bot, int nelem, int size, void *elm,
2350 int (*compar) (const void *, const void *))
2353 int last = nelem - 1;
2354 int celm = nelem / 2;
2358 last_move = first_move = true;
2361 if (first_move == true)
2363 res = compar(bot, elm);
2370 if (last_move == true)
2372 res = compar(elm, (void *) ((char *) bot + last * size));
2376 return (void *) ((char *) bot + last * size);
2379 res = compar(elm, (void *) ((char *) bot + celm * size));
2381 return (void *) ((char *) bot + celm * size);
2395 last = last - celm - 1;
2396 bot = (void *) ((char *) bot + (celm + 1) * size);
2397 celm = (last + 1) / 2;
2404 vac_cmp_blk(const void *left, const void *right)
2409 lblk = (*((VacPage *) left))->blkno;
2410 rblk = (*((VacPage *) right))->blkno;
2421 vac_cmp_offno(const void *left, const void *right)
2424 if (*(OffsetNumber *) left < *(OffsetNumber *) right)
2426 if (*(OffsetNumber *) left == *(OffsetNumber *) right)
2433 vac_cmp_vtlinks(const void *left, const void *right)
2436 if (((VTupleLink) left)->new_tid.ip_blkid.bi_hi <
2437 ((VTupleLink) right)->new_tid.ip_blkid.bi_hi)
2439 if (((VTupleLink) left)->new_tid.ip_blkid.bi_hi >
2440 ((VTupleLink) right)->new_tid.ip_blkid.bi_hi)
2442 /* bi_hi-es are equal */
2443 if (((VTupleLink) left)->new_tid.ip_blkid.bi_lo <
2444 ((VTupleLink) right)->new_tid.ip_blkid.bi_lo)
2446 if (((VTupleLink) left)->new_tid.ip_blkid.bi_lo >
2447 ((VTupleLink) right)->new_tid.ip_blkid.bi_lo)
2449 /* bi_lo-es are equal */
2450 if (((VTupleLink) left)->new_tid.ip_posid <
2451 ((VTupleLink) right)->new_tid.ip_posid)
2453 if (((VTupleLink) left)->new_tid.ip_posid >
2454 ((VTupleLink) right)->new_tid.ip_posid)
2462 get_indices(Relation relation, int *nindices, Relation **Irel)
2468 indexoidlist = RelationGetIndexList(relation);
2470 *nindices = length(indexoidlist);
2473 *Irel = (Relation *) palloc(*nindices * sizeof(Relation));
2478 foreach(indexoidscan, indexoidlist)
2480 Oid indexoid = lfirsti(indexoidscan);
2482 (*Irel)[i] = index_open(indexoid);
2486 freeList(indexoidlist);
2491 close_indices(int nindices, Relation *Irel)
2494 if (Irel == (Relation *) NULL)
2498 index_close(Irel[nindices]);
2505 * Obtain IndexInfo data for each index on the rel
2508 get_index_desc(Relation onerel, int nindices, Relation *Irel)
2510 IndexInfo **indexInfo;
2512 HeapTuple cachetuple;
2514 indexInfo = (IndexInfo **) palloc(nindices * sizeof(IndexInfo *));
2516 for (i = 0; i < nindices; i++)
2518 cachetuple = SearchSysCache(INDEXRELID,
2519 ObjectIdGetDatum(RelationGetRelid(Irel[i])),
2521 if (!HeapTupleIsValid(cachetuple))
2522 elog(ERROR, "get_index_desc: index %u not found",
2523 RelationGetRelid(Irel[i]));
2524 indexInfo[i] = BuildIndexInfo(cachetuple);
2525 ReleaseSysCache(cachetuple);
2533 enough_space(VacPage vacpage, Size len)
2536 len = MAXALIGN(len);
2538 if (len > vacpage->free)
2541 if (vacpage->offsets_used < vacpage->offsets_free) /* there are free
2543 return true; /* and len <= free_space */
2545 /* ok. noff_usd >= noff_free and so we'll have to allocate new itemid */
2546 if (len + MAXALIGN(sizeof(ItemIdData)) <= vacpage->free)
2555 * Compute elapsed time since ru0 usage snapshot, and format into
2556 * a displayable string. Result is in a static string, which is
2557 * tacky, but no one ever claimed that the Postgres backend is
2561 show_rusage(struct rusage * ru0)
2563 static char result[64];
2566 getrusage(RUSAGE_SELF, &ru1);
2568 if (ru1.ru_stime.tv_usec < ru0->ru_stime.tv_usec)
2570 ru1.ru_stime.tv_sec--;
2571 ru1.ru_stime.tv_usec += 1000000;
2573 if (ru1.ru_utime.tv_usec < ru0->ru_utime.tv_usec)
2575 ru1.ru_utime.tv_sec--;
2576 ru1.ru_utime.tv_usec += 1000000;
2579 snprintf(result, sizeof(result),
2580 "CPU %d.%02ds/%d.%02du sec.",
2581 (int) (ru1.ru_stime.tv_sec - ru0->ru_stime.tv_sec),
2582 (int) (ru1.ru_stime.tv_usec - ru0->ru_stime.tv_usec) / 10000,
2583 (int) (ru1.ru_utime.tv_sec - ru0->ru_utime.tv_sec),
2584 (int) (ru1.ru_utime.tv_usec - ru0->ru_utime.tv_usec) / 10000);