Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * gistvacuum.c
4 : * vacuuming routines for the postgres GiST index access method.
5 : *
6 : *
7 : * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
8 : * Portions Copyright (c) 1994, Regents of the University of California
9 : *
10 : * IDENTIFICATION
11 : * src/backend/access/gist/gistvacuum.c
12 : *
13 : *-------------------------------------------------------------------------
14 : */
15 : #include "postgres.h"
16 :
17 : #include "access/genam.h"
18 : #include "access/gist_private.h"
19 : #include "commands/vacuum.h"
20 : #include "miscadmin.h"
21 : #include "storage/indexfsm.h"
22 : #include "storage/lmgr.h"
23 :
24 :
25 : /*
26 : * VACUUM cleanup: update FSM
27 : */
28 : IndexBulkDeleteResult *
29 10 : gistvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats)
30 : {
31 10 : Relation rel = info->index;
32 : BlockNumber npages,
33 : blkno;
34 : BlockNumber totFreePages;
35 : bool needLock;
36 :
37 : /* No-op in ANALYZE ONLY mode */
38 10 : if (info->analyze_only)
39 2 : return stats;
40 :
41 : /* Set up all-zero stats if gistbulkdelete wasn't called */
42 8 : if (stats == NULL)
43 : {
44 5 : stats = (IndexBulkDeleteResult *) palloc0(sizeof(IndexBulkDeleteResult));
45 : /* use heap's tuple count */
46 5 : stats->num_index_tuples = info->num_heap_tuples;
47 5 : stats->estimated_count = info->estimated_count;
48 :
49 : /*
50 : * XXX the above is wrong if index is partial. Would it be OK to just
51 : * return NULL, or is there work we must do below?
52 : */
53 : }
54 :
55 : /*
56 : * Need lock unless it's local to this backend.
57 : */
58 8 : needLock = !RELATION_IS_LOCAL(rel);
59 :
60 : /* try to find deleted pages */
61 8 : if (needLock)
62 8 : LockRelationForExtension(rel, ExclusiveLock);
63 8 : npages = RelationGetNumberOfBlocks(rel);
64 8 : if (needLock)
65 8 : UnlockRelationForExtension(rel, ExclusiveLock);
66 :
67 8 : totFreePages = 0;
68 287 : for (blkno = GIST_ROOT_BLKNO + 1; blkno < npages; blkno++)
69 : {
70 : Buffer buffer;
71 : Page page;
72 :
73 279 : vacuum_delay_point();
74 :
75 279 : buffer = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL,
76 : info->strategy);
77 279 : LockBuffer(buffer, GIST_SHARE);
78 279 : page = (Page) BufferGetPage(buffer);
79 :
80 279 : if (PageIsNew(page) || GistPageIsDeleted(page))
81 : {
82 0 : totFreePages++;
83 0 : RecordFreeIndexPage(rel, blkno);
84 : }
85 279 : UnlockReleaseBuffer(buffer);
86 : }
87 :
88 : /* Finally, vacuum the FSM */
89 8 : IndexFreeSpaceMapVacuum(info->index);
90 :
91 : /* return statistics */
92 8 : stats->pages_free = totFreePages;
93 8 : if (needLock)
94 8 : LockRelationForExtension(rel, ExclusiveLock);
95 8 : stats->num_pages = RelationGetNumberOfBlocks(rel);
96 8 : if (needLock)
97 8 : UnlockRelationForExtension(rel, ExclusiveLock);
98 :
99 8 : return stats;
100 : }
101 :
102 : typedef struct GistBDItem
103 : {
104 : GistNSN parentlsn;
105 : BlockNumber blkno;
106 : struct GistBDItem *next;
107 : } GistBDItem;
108 :
109 : static void
110 220 : pushStackIfSplited(Page page, GistBDItem *stack)
111 : {
112 220 : GISTPageOpaque opaque = GistPageGetOpaque(page);
113 :
114 437 : if (stack->blkno != GIST_ROOT_BLKNO && !XLogRecPtrIsInvalid(stack->parentlsn) &&
115 434 : (GistFollowRight(page) || stack->parentlsn < GistPageGetNSN(page)) &&
116 0 : opaque->rightlink != InvalidBlockNumber /* sanity check */ )
117 : {
118 : /* split page detected, install right link to the stack */
119 :
120 0 : GistBDItem *ptr = (GistBDItem *) palloc(sizeof(GistBDItem));
121 :
122 0 : ptr->blkno = opaque->rightlink;
123 0 : ptr->parentlsn = stack->parentlsn;
124 0 : ptr->next = stack->next;
125 0 : stack->next = ptr;
126 : }
127 220 : }
128 :
129 :
130 : /*
131 : * Bulk deletion of all index entries pointing to a set of heap tuples and
132 : * check invalid tuples left after upgrade.
133 : * The set of target tuples is specified via a callback routine that tells
134 : * whether any given heap tuple (identified by ItemPointer) is being deleted.
135 : *
136 : * Result: a palloc'd struct containing statistical info for VACUUM displays.
137 : */
138 : IndexBulkDeleteResult *
139 3 : gistbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
140 : IndexBulkDeleteCallback callback, void *callback_state)
141 : {
142 3 : Relation rel = info->index;
143 : GistBDItem *stack,
144 : *ptr;
145 :
146 : /* first time through? */
147 3 : if (stats == NULL)
148 3 : stats = (IndexBulkDeleteResult *) palloc0(sizeof(IndexBulkDeleteResult));
149 : /* we'll re-count the tuples each time */
150 3 : stats->estimated_count = false;
151 3 : stats->num_index_tuples = 0;
152 :
153 3 : stack = (GistBDItem *) palloc0(sizeof(GistBDItem));
154 3 : stack->blkno = GIST_ROOT_BLKNO;
155 :
156 226 : while (stack)
157 : {
158 : Buffer buffer;
159 : Page page;
160 : OffsetNumber i,
161 : maxoff;
162 : IndexTuple idxtuple;
163 : ItemId iid;
164 :
165 220 : buffer = ReadBufferExtended(rel, MAIN_FORKNUM, stack->blkno,
166 : RBM_NORMAL, info->strategy);
167 220 : LockBuffer(buffer, GIST_SHARE);
168 220 : gistcheckpage(rel, buffer);
169 220 : page = (Page) BufferGetPage(buffer);
170 :
171 220 : if (GistPageIsLeaf(page))
172 : {
173 : OffsetNumber todelete[MaxOffsetNumber];
174 217 : int ntodelete = 0;
175 :
176 217 : LockBuffer(buffer, GIST_UNLOCK);
177 217 : LockBuffer(buffer, GIST_EXCLUSIVE);
178 :
179 217 : page = (Page) BufferGetPage(buffer);
180 217 : if (stack->blkno == GIST_ROOT_BLKNO && !GistPageIsLeaf(page))
181 : {
182 : /* only the root can become non-leaf during relock */
183 0 : UnlockReleaseBuffer(buffer);
184 : /* one more check */
185 0 : continue;
186 : }
187 :
188 : /*
189 : * check for split proceeded after look at parent, we should check
190 : * it after relock
191 : */
192 217 : pushStackIfSplited(page, stack);
193 :
194 : /*
195 : * Remove deletable tuples from page
196 : */
197 :
198 217 : maxoff = PageGetMaxOffsetNumber(page);
199 :
200 20226 : for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i))
201 : {
202 20009 : iid = PageGetItemId(page, i);
203 20009 : idxtuple = (IndexTuple) PageGetItem(page, iid);
204 :
205 20009 : if (callback(&(idxtuple->t_tid), callback_state))
206 10003 : todelete[ntodelete++] = i;
207 : else
208 10006 : stats->num_index_tuples += 1;
209 : }
210 :
211 217 : stats->tuples_removed += ntodelete;
212 :
213 217 : if (ntodelete)
214 : {
215 217 : START_CRIT_SECTION();
216 :
217 217 : MarkBufferDirty(buffer);
218 :
219 217 : PageIndexMultiDelete(page, todelete, ntodelete);
220 217 : GistMarkTuplesDeleted(page);
221 :
222 217 : if (RelationNeedsWAL(rel))
223 : {
224 : XLogRecPtr recptr;
225 :
226 217 : recptr = gistXLogUpdate(buffer,
227 : todelete, ntodelete,
228 : NULL, 0, InvalidBuffer);
229 217 : PageSetLSN(page, recptr);
230 : }
231 : else
232 0 : PageSetLSN(page, gistGetFakeLSN(rel));
233 :
234 217 : END_CRIT_SECTION();
235 : }
236 :
237 : }
238 : else
239 : {
240 : /* check for split proceeded after look at parent */
241 3 : pushStackIfSplited(page, stack);
242 :
243 3 : maxoff = PageGetMaxOffsetNumber(page);
244 :
245 220 : for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i))
246 : {
247 217 : iid = PageGetItemId(page, i);
248 217 : idxtuple = (IndexTuple) PageGetItem(page, iid);
249 :
250 217 : ptr = (GistBDItem *) palloc(sizeof(GistBDItem));
251 217 : ptr->blkno = ItemPointerGetBlockNumber(&(idxtuple->t_tid));
252 217 : ptr->parentlsn = PageGetLSN(page);
253 217 : ptr->next = stack->next;
254 217 : stack->next = ptr;
255 :
256 217 : if (GistTupleIsInvalid(idxtuple))
257 0 : ereport(LOG,
258 : (errmsg("index \"%s\" contains an inner tuple marked as invalid",
259 : RelationGetRelationName(rel)),
260 : errdetail("This is caused by an incomplete page split at crash recovery before upgrading to PostgreSQL 9.1."),
261 : errhint("Please REINDEX it.")));
262 : }
263 : }
264 :
265 220 : UnlockReleaseBuffer(buffer);
266 :
267 220 : ptr = stack->next;
268 220 : pfree(stack);
269 220 : stack = ptr;
270 :
271 220 : vacuum_delay_point();
272 : }
273 :
274 3 : return stats;
275 : }
|