Skip to content

Commit c98763b

Browse files
committed
Avoid spurious waits in concurrent indexing
In the various waiting phases of CREATE INDEX CONCURRENTLY (CIC) and REINDEX CONCURRENTLY (RC), we wait for other processes to release their snapshots; this is necessary in general for correctness. However, processes doing CIC in other tables cannot possibly affect CIC or RC done in "this" table, so we don't need to wait for those. This commit adds a flag in MyProc->statusFlags to indicate that the current process is doing CIC, so that other processes doing CIC or RC can ignore it when waiting. Note that this logic is only valid if the index does not access other tables. For simplicity we avoid setting the flag if the index has a column that's an expression, or has a WHERE predicate. (It is possible to have expressional or partial indexes that do not access other tables, but figuring that out would require more work.) This flag can potentially also be used by processes doing REINDEX CONCURRENTLY to be skipped; and by VACUUM to ignore processes in CIC or RC for the purposes of computing an Xmin. That's left for future commits. Author: Álvaro Herrera <alvherre@alvh.no-ip.org> Author: Dimitry Dolgov <9erthalion6@gmail.com> Reviewed-by: Michael Paquier <michael@paquier.xyz> Discussion: https://postgr.es/m/20200810233815.GA18970@alvherre.pgsql
1 parent 314fb9b commit c98763b

File tree

2 files changed

+64
-4
lines changed

2 files changed

+64
-4
lines changed

src/backend/commands/indexcmds.c

+60-3
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ static void ReindexPartitions(Oid relid, int options, bool isTopLevel);
9393
static void ReindexMultipleInternal(List *relids, int options);
9494
static bool ReindexRelationConcurrently(Oid relationOid, int options);
9595
static void update_relispartition(Oid relationId, bool newval);
96+
static inline void set_indexsafe_procflags(void);
9697

9798
/*
9899
* callback argument type for RangeVarCallbackForReindexIndex()
@@ -384,7 +385,10 @@ CompareOpclassOptions(Datum *opts1, Datum *opts2, int natts)
384385
* lazy VACUUMs, because they won't be fazed by missing index entries
385386
* either. (Manual ANALYZEs, however, can't be excluded because they
386387
* might be within transactions that are going to do arbitrary operations
387-
* later.)
388+
* later.) Processes running CREATE INDEX CONCURRENTLY
389+
* on indexes that are neither expressional nor partial are also safe to
390+
* ignore, since we know that those processes won't examine any data
391+
* outside the table they're indexing.
388392
*
389393
* Also, GetCurrentVirtualXIDs never reports our own vxid, so we need not
390394
* check for that.
@@ -405,7 +409,8 @@ WaitForOlderSnapshots(TransactionId limitXmin, bool progress)
405409
VirtualTransactionId *old_snapshots;
406410

407411
old_snapshots = GetCurrentVirtualXIDs(limitXmin, true, false,
408-
PROC_IS_AUTOVACUUM | PROC_IN_VACUUM,
412+
PROC_IS_AUTOVACUUM | PROC_IN_VACUUM
413+
| PROC_IN_SAFE_IC,
409414
&n_old_snapshots);
410415
if (progress)
411416
pgstat_progress_update_param(PROGRESS_WAITFOR_TOTAL, n_old_snapshots);
@@ -425,7 +430,8 @@ WaitForOlderSnapshots(TransactionId limitXmin, bool progress)
425430

426431
newer_snapshots = GetCurrentVirtualXIDs(limitXmin,
427432
true, false,
428-
PROC_IS_AUTOVACUUM | PROC_IN_VACUUM,
433+
PROC_IS_AUTOVACUUM | PROC_IN_VACUUM
434+
| PROC_IN_SAFE_IC,
429435
&n_newer_snapshots);
430436
for (j = i; j < n_old_snapshots; j++)
431437
{
@@ -518,6 +524,7 @@ DefineIndex(Oid relationId,
518524
bool amcanorder;
519525
amoptions_function amoptions;
520526
bool partitioned;
527+
bool safe_index;
521528
Datum reloptions;
522529
int16 *coloptions;
523530
IndexInfo *indexInfo;
@@ -1044,6 +1051,10 @@ DefineIndex(Oid relationId,
10441051
}
10451052
}
10461053

1054+
/* Is index safe for others to ignore? See set_indexsafe_procflags() */
1055+
safe_index = indexInfo->ii_Expressions == NIL &&
1056+
indexInfo->ii_Predicate == NIL;
1057+
10471058
/*
10481059
* Report index creation if appropriate (delay this till after most of the
10491060
* error checks)
@@ -1430,6 +1441,10 @@ DefineIndex(Oid relationId,
14301441
CommitTransactionCommand();
14311442
StartTransactionCommand();
14321443

1444+
/* Tell concurrent index builds to ignore us, if index qualifies */
1445+
if (safe_index)
1446+
set_indexsafe_procflags();
1447+
14331448
/*
14341449
* The index is now visible, so we can report the OID.
14351450
*/
@@ -1489,6 +1504,10 @@ DefineIndex(Oid relationId,
14891504
CommitTransactionCommand();
14901505
StartTransactionCommand();
14911506

1507+
/* Tell concurrent index builds to ignore us, if index qualifies */
1508+
if (safe_index)
1509+
set_indexsafe_procflags();
1510+
14921511
/*
14931512
* Phase 3 of concurrent index build
14941513
*
@@ -1545,6 +1564,10 @@ DefineIndex(Oid relationId,
15451564
CommitTransactionCommand();
15461565
StartTransactionCommand();
15471566

1567+
/* Tell concurrent index builds to ignore us, if index qualifies */
1568+
if (safe_index)
1569+
set_indexsafe_procflags();
1570+
15481571
/* We should now definitely not be advertising any xmin. */
15491572
Assert(MyProc->xmin == InvalidTransactionId);
15501573

@@ -3896,3 +3919,37 @@ update_relispartition(Oid relationId, bool newval)
38963919
heap_freetuple(tup);
38973920
table_close(classRel, RowExclusiveLock);
38983921
}
3922+
3923+
/*
3924+
* Set the PROC_IN_SAFE_IC flag in MyProc->statusFlags.
3925+
*
3926+
* When doing concurrent index builds, we can set this flag
3927+
* to tell other processes concurrently running CREATE
3928+
* INDEX CONCURRENTLY or REINDEX CONCURRENTLY to ignore us when
3929+
* doing their waits for concurrent snapshots. On one hand it
3930+
* avoids pointlessly waiting for a process that's not interesting
3931+
* anyway; but more importantly it avoids deadlocks in some cases.
3932+
*
3933+
* This can be done safely only for indexes that don't execute any
3934+
* expressions that could access other tables, so index must not be
3935+
* expressional nor partial. Caller is responsible for only calling
3936+
* this routine when that assumption holds true.
3937+
*
3938+
* (The flag is reset automatically at transaction end, so it must be
3939+
* set for each transaction.)
3940+
*/
3941+
static inline void
3942+
set_indexsafe_procflags(void)
3943+
{
3944+
/*
3945+
* This should only be called before installing xid or xmin in MyProc;
3946+
* otherwise, concurrent processes could see an Xmin that moves backwards.
3947+
*/
3948+
Assert(MyProc->xid == InvalidTransactionId &&
3949+
MyProc->xmin == InvalidTransactionId);
3950+
3951+
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
3952+
MyProc->statusFlags |= PROC_IN_SAFE_IC;
3953+
ProcGlobal->statusFlags[MyProc->pgxactoff] = MyProc->statusFlags;
3954+
LWLockRelease(ProcArrayLock);
3955+
}

src/include/storage/proc.h

+4-1
Original file line numberDiff line numberDiff line change
@@ -53,13 +53,16 @@ struct XidCache
5353
*/
5454
#define PROC_IS_AUTOVACUUM 0x01 /* is it an autovac worker? */
5555
#define PROC_IN_VACUUM 0x02 /* currently running lazy vacuum */
56+
#define PROC_IN_SAFE_IC 0x04 /* currently running CREATE INDEX
57+
* CONCURRENTLY on non-expressional,
58+
* non-partial index */
5659
#define PROC_VACUUM_FOR_WRAPAROUND 0x08 /* set by autovac only */
5760
#define PROC_IN_LOGICAL_DECODING 0x10 /* currently doing logical
5861
* decoding outside xact */
5962

6063
/* flags reset at EOXact */
6164
#define PROC_VACUUM_STATE_MASK \
62-
(PROC_IN_VACUUM | PROC_VACUUM_FOR_WRAPAROUND)
65+
(PROC_IN_VACUUM | PROC_IN_SAFE_IC | PROC_VACUUM_FOR_WRAPAROUND)
6366

6467
/*
6568
* We allow a small number of "weak" relation locks (AccessShareLock,

0 commit comments

Comments
 (0)