@@ -168,6 +168,75 @@ GetVisibilityMapPins(Relation relation, Buffer buffer1, Buffer buffer2,
168
168
}
169
169
}
170
170
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
+
171
240
/*
172
241
* RelationGetBufferForTuple
173
242
*
@@ -233,8 +302,8 @@ RelationGetBufferForTuple(Relation relation, Size len,
233
302
bool use_fsm = !(options & HEAP_INSERT_SKIP_FSM );
234
303
Buffer buffer = InvalidBuffer ;
235
304
Page page ;
236
- Size pageFreeSpace ,
237
- saveFreeSpace ;
305
+ Size pageFreeSpace = 0 ,
306
+ saveFreeSpace = 0 ;
238
307
BlockNumber targetBlock ,
239
308
otherBlock ;
240
309
bool needLock ;
@@ -308,6 +377,7 @@ RelationGetBufferForTuple(Relation relation, Size len,
308
377
}
309
378
}
310
379
380
+ loop :
311
381
while (targetBlock != InvalidBlockNumber )
312
382
{
313
383
/*
@@ -440,10 +510,46 @@ RelationGetBufferForTuple(Relation relation, Size len,
440
510
*/
441
511
needLock = !RELATION_IS_LOCAL (relation );
442
512
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
+ */
443
519
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
+ }
445
548
446
549
/*
550
+ * In addition to whatever extension we performed above, we always add
551
+ * at least one block to satisfy our own request.
552
+ *
447
553
* XXX This does an lseek - rather expensive - but at the moment it is the
448
554
* only way to accurately determine how many blocks are in a relation. Is
449
555
* it worth keeping an accurate file length in shared memory someplace,
0 commit comments