46
46
#include "commands/vacuum.h"
47
47
#include "miscadmin.h"
48
48
#include "pgstat.h"
49
+ #include "portability/instr_time.h"
49
50
#include "postmaster/autovacuum.h"
50
51
#include "storage/bufmgr.h"
51
52
#include "storage/freespace.h"
66
67
#define REL_TRUNCATE_MINIMUM 1000
67
68
#define REL_TRUNCATE_FRACTION 16
68
69
70
+ /*
71
+ * Timing parameters for truncate locking heuristics.
72
+ *
73
+ * These were not exposed as user tunable GUC values because it didn't seem
74
+ * that the potential for improvement was great enough to merit the cost of
75
+ * supporting them.
76
+ */
77
+ #define AUTOVACUUM_TRUNCATE_LOCK_CHECK_INTERVAL 20 /* ms */
78
+ #define AUTOVACUUM_TRUNCATE_LOCK_WAIT_INTERVAL 50 /* ms */
79
+ #define AUTOVACUUM_TRUNCATE_LOCK_TIMEOUT 5000 /* ms */
80
+
69
81
/*
70
82
* Guesstimation of number of dead tuples per page. This is used to
71
83
* provide an upper limit to memory allocated when vacuuming small
@@ -100,6 +112,7 @@ typedef struct LVRelStats
100
112
ItemPointer dead_tuples ; /* array of ItemPointerData */
101
113
int num_index_scans ;
102
114
TransactionId latestRemovedXid ;
115
+ bool lock_waiter_detected ;
103
116
} LVRelStats ;
104
117
105
118
@@ -183,6 +196,8 @@ lazy_vacuum_rel(Relation onerel, VacuumStmt *vacstmt,
183
196
vacrelstats -> old_rel_pages = onerel -> rd_rel -> relpages ;
184
197
vacrelstats -> old_rel_tuples = onerel -> rd_rel -> reltuples ;
185
198
vacrelstats -> num_index_scans = 0 ;
199
+ vacrelstats -> pages_removed = 0 ;
200
+ vacrelstats -> lock_waiter_detected = false;
186
201
187
202
/* Open all indexes of the relation */
188
203
vac_open_indexes (onerel , RowExclusiveLock , & nindexes , & Irel );
@@ -239,10 +254,17 @@ lazy_vacuum_rel(Relation onerel, VacuumStmt *vacstmt,
239
254
vacrelstats -> hasindex ,
240
255
new_frozen_xid );
241
256
242
- /* report results to the stats collector, too */
243
- pgstat_report_vacuum (RelationGetRelid (onerel ),
244
- onerel -> rd_rel -> relisshared ,
245
- new_rel_tuples );
257
+ /*
258
+ * Report results to the stats collector, too. An early terminated
259
+ * lazy_truncate_heap attempt suppresses the message and also cancels the
260
+ * execution of ANALYZE, if that was ordered.
261
+ */
262
+ if (!vacrelstats -> lock_waiter_detected )
263
+ pgstat_report_vacuum (RelationGetRelid (onerel ),
264
+ onerel -> rd_rel -> relisshared ,
265
+ new_rel_tuples );
266
+ else
267
+ vacstmt -> options &= ~VACOPT_ANALYZE ;
246
268
247
269
/* and log the action if appropriate */
248
270
if (IsAutoVacuumWorkerProcess () && Log_autovacuum_min_duration >= 0 )
@@ -1087,80 +1109,124 @@ lazy_truncate_heap(Relation onerel, LVRelStats *vacrelstats)
1087
1109
BlockNumber old_rel_pages = vacrelstats -> rel_pages ;
1088
1110
BlockNumber new_rel_pages ;
1089
1111
PGRUsage ru0 ;
1112
+ int lock_retry ;
1090
1113
1091
1114
pg_rusage_init (& ru0 );
1092
1115
1093
1116
/*
1094
- * We need full exclusive lock on the relation in order to do truncation.
1095
- * If we can't get it, give up rather than waiting --- we don't want to
1096
- * block other backends, and we don't want to deadlock (which is quite
1097
- * possible considering we already hold a lower-grade lock).
1098
- */
1099
- if (!ConditionalLockRelation (onerel , AccessExclusiveLock ))
1100
- return ;
1101
-
1102
- /*
1103
- * Now that we have exclusive lock, look to see if the rel has grown
1104
- * whilst we were vacuuming with non-exclusive lock. If so, give up; the
1105
- * newly added pages presumably contain non-deletable tuples.
1117
+ * Loop until no more truncating can be done.
1106
1118
*/
1107
- new_rel_pages = RelationGetNumberOfBlocks (onerel );
1108
- if (new_rel_pages != old_rel_pages )
1119
+ do
1109
1120
{
1110
1121
/*
1111
- * Note: we intentionally don't update vacrelstats->rel_pages with
1112
- * the new rel size here. If we did, it would amount to assuming that
1113
- * the new pages are empty, which is unlikely. Leaving the numbers
1114
- * alone amounts to assuming that the new pages have the same tuple
1115
- * density as existing ones, which is less unlikely .
1122
+ * We need full exclusive lock on the relation in order to do
1123
+ * truncation. If we can't get it, give up rather than waiting --- we
1124
+ * don't want to block other backends, and we don't want to deadlock
1125
+ * (which is quite possible considering we already hold a lower-grade
1126
+ * lock) .
1116
1127
*/
1117
- UnlockRelation (onerel , AccessExclusiveLock );
1118
- return ;
1119
- }
1128
+ vacrelstats -> lock_waiter_detected = false;
1129
+ lock_retry = 0 ;
1130
+ while (true)
1131
+ {
1132
+ if (ConditionalLockRelation (onerel , AccessExclusiveLock ))
1133
+ break ;
1120
1134
1121
- /*
1122
- * Scan backwards from the end to verify that the end pages actually
1123
- * contain no tuples. This is *necessary*, not optional, because other
1124
- * backends could have added tuples to these pages whilst we were
1125
- * vacuuming.
1126
- */
1127
- new_rel_pages = count_nondeletable_pages (onerel , vacrelstats );
1135
+ /*
1136
+ * Check for interrupts while trying to (re-)acquire the exclusive
1137
+ * lock.
1138
+ */
1139
+ CHECK_FOR_INTERRUPTS ();
1128
1140
1129
- if (new_rel_pages >= old_rel_pages )
1130
- {
1131
- /* can't do anything after all */
1132
- UnlockRelation (onerel , AccessExclusiveLock );
1133
- return ;
1134
- }
1141
+ if (++ lock_retry > (AUTOVACUUM_TRUNCATE_LOCK_TIMEOUT /
1142
+ AUTOVACUUM_TRUNCATE_LOCK_WAIT_INTERVAL ))
1143
+ {
1144
+ /*
1145
+ * We failed to establish the lock in the specified number of
1146
+ * retries. This means we give up truncating. Suppress the
1147
+ * ANALYZE step. Doing an ANALYZE at this point will reset the
1148
+ * dead_tuple_count in the stats collector, so we will not get
1149
+ * called by the autovacuum launcher again to do the truncate.
1150
+ */
1151
+ vacrelstats -> lock_waiter_detected = true;
1152
+ ereport (LOG ,
1153
+ (errmsg ("automatic vacuum of table \"%s.%s.%s\": "
1154
+ "cannot (re)acquire exclusive "
1155
+ "lock for truncate scan" ,
1156
+ get_database_name (MyDatabaseId ),
1157
+ get_namespace_name (RelationGetNamespace (onerel )),
1158
+ RelationGetRelationName (onerel ))));
1159
+ return ;
1160
+ }
1135
1161
1136
- /*
1137
- * Okay to truncate.
1138
- */
1139
- RelationTruncate (onerel , new_rel_pages );
1162
+ pg_usleep (AUTOVACUUM_TRUNCATE_LOCK_WAIT_INTERVAL );
1163
+ }
1140
1164
1141
- /*
1142
- * We can release the exclusive lock as soon as we have truncated. Other
1143
- * backends can't safely access the relation until they have processed the
1144
- * smgr invalidation that smgrtruncate sent out ... but that should happen
1145
- * as part of standard invalidation processing once they acquire lock on
1146
- * the relation.
1147
- */
1148
- UnlockRelation (onerel , AccessExclusiveLock );
1165
+ /*
1166
+ * Now that we have exclusive lock, look to see if the rel has grown
1167
+ * whilst we were vacuuming with non-exclusive lock. If so, give up;
1168
+ * the newly added pages presumably contain non-deletable tuples.
1169
+ */
1170
+ new_rel_pages = RelationGetNumberOfBlocks (onerel );
1171
+ if (new_rel_pages != old_rel_pages )
1172
+ {
1173
+ /*
1174
+ * Note: we intentionally don't update vacrelstats->rel_pages with
1175
+ * the new rel size here. If we did, it would amount to assuming
1176
+ * that the new pages are empty, which is unlikely. Leaving the
1177
+ * numbers alone amounts to assuming that the new pages have the
1178
+ * same tuple density as existing ones, which is less unlikely.
1179
+ */
1180
+ UnlockRelation (onerel , AccessExclusiveLock );
1181
+ return ;
1182
+ }
1149
1183
1150
- /*
1151
- * Update statistics. Here, it *is* correct to adjust rel_pages without
1152
- * also touching reltuples, since the tuple count wasn't changed by the
1153
- * truncation.
1154
- */
1155
- vacrelstats -> rel_pages = new_rel_pages ;
1156
- vacrelstats -> pages_removed = old_rel_pages - new_rel_pages ;
1184
+ /*
1185
+ * Scan backwards from the end to verify that the end pages actually
1186
+ * contain no tuples. This is *necessary*, not optional, because
1187
+ * other backends could have added tuples to these pages whilst we
1188
+ * were vacuuming.
1189
+ */
1190
+ new_rel_pages = count_nondeletable_pages ( onerel , vacrelstats ) ;
1157
1191
1158
- ereport (elevel ,
1159
- (errmsg ("\"%s\": truncated %u to %u pages" ,
1160
- RelationGetRelationName (onerel ),
1161
- old_rel_pages , new_rel_pages ),
1162
- errdetail ("%s." ,
1163
- pg_rusage_show (& ru0 ))));
1192
+ if (new_rel_pages >= old_rel_pages )
1193
+ {
1194
+ /* can't do anything after all */
1195
+ UnlockRelation (onerel , AccessExclusiveLock );
1196
+ return ;
1197
+ }
1198
+
1199
+ /*
1200
+ * Okay to truncate.
1201
+ */
1202
+ RelationTruncate (onerel , new_rel_pages );
1203
+
1204
+ /*
1205
+ * We can release the exclusive lock as soon as we have truncated.
1206
+ * Other backends can't safely access the relation until they have
1207
+ * processed the smgr invalidation that smgrtruncate sent out ... but
1208
+ * that should happen as part of standard invalidation processing once
1209
+ * they acquire lock on the relation.
1210
+ */
1211
+ UnlockRelation (onerel , AccessExclusiveLock );
1212
+
1213
+ /*
1214
+ * Update statistics. Here, it *is* correct to adjust rel_pages
1215
+ * without also touching reltuples, since the tuple count wasn't
1216
+ * changed by the truncation.
1217
+ */
1218
+ vacrelstats -> pages_removed += old_rel_pages - new_rel_pages ;
1219
+ vacrelstats -> rel_pages = new_rel_pages ;
1220
+
1221
+ ereport (elevel ,
1222
+ (errmsg ("\"%s\": truncated %u to %u pages" ,
1223
+ RelationGetRelationName (onerel ),
1224
+ old_rel_pages , new_rel_pages ),
1225
+ errdetail ("%s." ,
1226
+ pg_rusage_show (& ru0 ))));
1227
+ old_rel_pages = new_rel_pages ;
1228
+ } while (new_rel_pages > vacrelstats -> nonempty_pages &&
1229
+ vacrelstats -> lock_waiter_detected );
1164
1230
}
1165
1231
1166
1232
/*
@@ -1172,6 +1238,12 @@ static BlockNumber
1172
1238
count_nondeletable_pages (Relation onerel , LVRelStats * vacrelstats )
1173
1239
{
1174
1240
BlockNumber blkno ;
1241
+ instr_time starttime ;
1242
+ instr_time currenttime ;
1243
+ instr_time elapsed ;
1244
+
1245
+ /* Initialize the starttime if we check for conflicting lock requests */
1246
+ INSTR_TIME_SET_CURRENT (starttime );
1175
1247
1176
1248
/* Strange coding of loop control is needed because blkno is unsigned */
1177
1249
blkno = vacrelstats -> rel_pages ;
@@ -1183,6 +1255,36 @@ count_nondeletable_pages(Relation onerel, LVRelStats *vacrelstats)
1183
1255
maxoff ;
1184
1256
bool hastup ;
1185
1257
1258
+ /*
1259
+ * Check if another process requests a lock on our relation. We are
1260
+ * holding an AccessExclusiveLock here, so they will be waiting. We
1261
+ * only do this in autovacuum_truncate_lock_check millisecond
1262
+ * intervals, and we only check if that interval has elapsed once
1263
+ * every 32 blocks to keep the number of system calls and actual
1264
+ * shared lock table lookups to a minimum.
1265
+ */
1266
+ if ((blkno % 32 ) == 0 )
1267
+ {
1268
+ INSTR_TIME_SET_CURRENT (currenttime );
1269
+ elapsed = currenttime ;
1270
+ INSTR_TIME_SUBTRACT (elapsed , starttime );
1271
+ if ((INSTR_TIME_GET_MICROSEC (elapsed ) / 1000 )
1272
+ >= AUTOVACUUM_TRUNCATE_LOCK_CHECK_INTERVAL )
1273
+ {
1274
+ if (LockHasWaitersRelation (onerel , AccessExclusiveLock ))
1275
+ {
1276
+ ereport (elevel ,
1277
+ (errmsg ("\"%s\": suspending truncate "
1278
+ "due to conflicting lock request" ,
1279
+ RelationGetRelationName (onerel ))));
1280
+
1281
+ vacrelstats -> lock_waiter_detected = true;
1282
+ return blkno ;
1283
+ }
1284
+ starttime = currenttime ;
1285
+ }
1286
+ }
1287
+
1186
1288
/*
1187
1289
* We don't insert a vacuum delay point here, because we have an
1188
1290
* exclusive lock on the table which we want to hold for as short a
0 commit comments