@@ -168,6 +168,75 @@ GetVisibilityMapPins(Relation relation, Buffer buffer1, Buffer buffer2,
168168 }
169169}
170170
171+ /*
172+ * Extend a relation by multiple blocks to avoid future contention on the
173+ * relation extension lock. Our goal is to pre-extend the relation by an
174+ * amount which ramps up as the degree of contention ramps up, but limiting
175+ * the result to some sane overall value.
176+ */
177+ static void
178+ RelationAddExtraBlocks (Relation relation , BulkInsertState bistate )
179+ {
180+ Page page ;
181+ BlockNumber blockNum = InvalidBlockNumber ,
182+ firstBlock = InvalidBlockNumber ;
183+ int extraBlocks = 0 ;
184+ int lockWaiters = 0 ;
185+ Size freespace = 0 ;
186+ Buffer buffer ;
187+
188+ /* Use the length of the lock wait queue to judge how much to extend. */
189+ lockWaiters = RelationExtensionLockWaiterCount (relation );
190+ if (lockWaiters <= 0 )
191+ return ;
192+
193+ /*
194+ * It might seem like multiplying the number of lock waiters by as much
195+ * as 20 is too aggressive, but benchmarking revealed that smaller numbers
196+ * were insufficient. 512 is just an arbitrary cap to prevent pathological
197+ * results.
198+ */
199+ extraBlocks = Min (512 , lockWaiters * 20 );
200+
201+ while (extraBlocks -- >= 0 )
202+ {
203+ /* Ouch - an unnecessary lseek() each time through the loop! */
204+ buffer = ReadBufferBI (relation , P_NEW , bistate );
205+
206+ /* Extend by one page. */
207+ LockBuffer (buffer , BUFFER_LOCK_EXCLUSIVE );
208+ page = BufferGetPage (buffer );
209+ PageInit (page , BufferGetPageSize (buffer ), 0 );
210+ MarkBufferDirty (buffer );
211+ blockNum = BufferGetBlockNumber (buffer );
212+ freespace = PageGetHeapFreeSpace (page );
213+ UnlockReleaseBuffer (buffer );
214+
215+ /* Remember first block number thus added. */
216+ if (firstBlock == InvalidBlockNumber )
217+ firstBlock = blockNum ;
218+
219+ /*
220+ * Immediately update the bottom level of the FSM. This has a good
221+ * chance of making this page visible to other concurrently inserting
222+ * backends, and we want that to happen without delay.
223+ */
224+ RecordPageWithFreeSpace (relation , blockNum , freespace );
225+ }
226+
227+ /*
228+ * Updating the upper levels of the free space map is too expensive
229+ * to do for every block, but it's worth doing once at the end to make
230+ * sure that subsequent insertion activity sees all of those nifty free
231+ * pages we just inserted.
232+ *
233+ * Note that we're using the freespace value that was reported for the
234+ * last block we added as if it were the freespace value for every block
235+ * we added. That's actually true, because they're all equally empty.
236+ */
237+ UpdateFreeSpaceMap (relation , firstBlock , blockNum , freespace );
238+ }
239+
171240/*
172241 * RelationGetBufferForTuple
173242 *
@@ -233,8 +302,8 @@ RelationGetBufferForTuple(Relation relation, Size len,
233302 bool use_fsm = !(options & HEAP_INSERT_SKIP_FSM );
234303 Buffer buffer = InvalidBuffer ;
235304 Page page ;
236- Size pageFreeSpace ,
237- saveFreeSpace ;
305+ Size pageFreeSpace = 0 ,
306+ saveFreeSpace = 0 ;
238307 BlockNumber targetBlock ,
239308 otherBlock ;
240309 bool needLock ;
@@ -308,6 +377,7 @@ RelationGetBufferForTuple(Relation relation, Size len,
308377 }
309378 }
310379
380+ loop :
311381 while (targetBlock != InvalidBlockNumber )
312382 {
313383 /*
@@ -440,10 +510,46 @@ RelationGetBufferForTuple(Relation relation, Size len,
440510 */
441511 needLock = !RELATION_IS_LOCAL (relation );
442512
513+ /*
514+ * If we need the lock but are not able to acquire it immediately, we'll
515+ * consider extending the relation by multiple blocks at a time to manage
516+ * contention on the relation extension lock. However, this only makes
517+ * sense if we're using the FSM; otherwise, there's no point.
518+ */
443519 if (needLock )
444- LockRelationForExtension (relation , ExclusiveLock );
520+ {
521+ if (!use_fsm )
522+ LockRelationForExtension (relation , ExclusiveLock );
523+ else if (!ConditionalLockRelationForExtension (relation , ExclusiveLock ))
524+ {
525+ /* Couldn't get the lock immediately; wait for it. */
526+ LockRelationForExtension (relation , ExclusiveLock );
527+
528+ /*
529+ * Check if some other backend has extended a block for us while
530+ * we were waiting on the lock.
531+ */
532+ targetBlock = GetPageWithFreeSpace (relation , len + saveFreeSpace );
533+
534+ /*
535+ * If some other waiter has already extended the relation, we
536+ * don't need to do so; just use the existing freespace.
537+ */
538+ if (targetBlock != InvalidBlockNumber )
539+ {
540+ UnlockRelationForExtension (relation , ExclusiveLock );
541+ goto loop ;
542+ }
543+
544+ /* Time to bulk-extend. */
545+ RelationAddExtraBlocks (relation , bistate );
546+ }
547+ }
445548
446549 /*
550+ * In addition to whatever extension we performed above, we always add
551+ * at least one block to satisfy our own request.
552+ *
447553 * XXX This does an lseek - rather expensive - but at the moment it is the
448554 * only way to accurately determine how many blocks are in a relation. Is
449555 * it worth keeping an accurate file length in shared memory someplace,
0 commit comments