Skip to content

Commit 7e3da1c

Browse files
committed
Mitigate "snapshot too old" performance regression on NUMA
Limit maintenance of time to xid mapping to once per minute. At least in the tested case this brings performance within 5% of when the feature is off, compared to several times slower without this patch. While there, fix comments and whitespace. Ants Aasma, with cosmetic adjustments suggested by Andres Freund Reviewed by Kevin Grittner and Andres Freund
1 parent eb7de00 commit 7e3da1c

File tree

1 file changed

+62
-23
lines changed

1 file changed

+62
-23
lines changed

src/backend/utils/time/snapmgr.c

Lines changed: 62 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -78,10 +78,13 @@ typedef struct OldSnapshotControlData
7878
* Variables for old snapshot handling are shared among processes and are
7979
* only allowed to move forward.
8080
*/
81-
slock_t mutex_current; /* protect current timestamp */
81+
slock_t mutex_current; /* protect current_timestamp */
8282
int64 current_timestamp; /* latest snapshot timestamp */
83-
slock_t mutex_latest_xmin; /* protect latest snapshot xmin */
83+
slock_t mutex_latest_xmin; /* protect latest_xmin
84+
* and next_map_update
85+
*/
8486
TransactionId latest_xmin; /* latest snapshot xmin */
87+
int64 next_map_update; /* latest snapshot valid up to */
8588
slock_t mutex_threshold; /* protect threshold fields */
8689
int64 threshold_timestamp; /* earlier snapshot is old */
8790
TransactionId threshold_xid; /* earlier xid may be gone */
@@ -95,7 +98,10 @@ typedef struct OldSnapshotControlData
9598
* count_used value of OLD_SNAPSHOT_TIME_MAP_ENTRIES means that the buffer
9699
* is full and the head must be advanced to add new entries. Use
97100
* timestamps aligned to minute boundaries, since that seems less
98-
* surprising than aligning based on the first usage timestamp.
101+
* surprising than aligning based on the first usage timestamp. The
102+
* latest bucket is effectively stored within latest_xmin. The circular
103+
* buffer is updated when we get a new xmin value that doesn't fall into
104+
* the same interval.
99105
*
100106
* It is OK if the xid for a given time slot is from earlier than
101107
* calculated by adding the number of minutes corresponding to the
@@ -269,6 +275,7 @@ SnapMgrInit(void)
269275
oldSnapshotControl->current_timestamp = 0;
270276
SpinLockInit(&oldSnapshotControl->mutex_latest_xmin);
271277
oldSnapshotControl->latest_xmin = InvalidTransactionId;
278+
oldSnapshotControl->next_map_update = 0;
272279
SpinLockInit(&oldSnapshotControl->mutex_threshold);
273280
oldSnapshotControl->threshold_timestamp = 0;
274281
oldSnapshotControl->threshold_xid = InvalidTransactionId;
@@ -1595,9 +1602,15 @@ TransactionIdLimitedForOldSnapshots(TransactionId recentXmin,
15951602
{
15961603
int64 ts = GetSnapshotCurrentTimestamp();
15971604
TransactionId xlimit = recentXmin;
1598-
TransactionId latest_xmin = oldSnapshotControl->latest_xmin;
1605+
TransactionId latest_xmin;
1606+
int64 update_ts;
15991607
bool same_ts_as_threshold = false;
16001608

1609+
SpinLockAcquire(&oldSnapshotControl->mutex_latest_xmin);
1610+
latest_xmin = oldSnapshotControl->latest_xmin;
1611+
update_ts = oldSnapshotControl->next_map_update;
1612+
SpinLockRelease(&oldSnapshotControl->mutex_latest_xmin);
1613+
16011614
/*
16021615
* Zero threshold always overrides to latest xmin, if valid. Without
16031616
* some heuristic it will find its own snapshot too old on, for
@@ -1632,26 +1645,35 @@ TransactionIdLimitedForOldSnapshots(TransactionId recentXmin,
16321645

16331646
if (!same_ts_as_threshold)
16341647
{
1635-
LWLockAcquire(OldSnapshotTimeMapLock, LW_SHARED);
1636-
1637-
if (oldSnapshotControl->count_used > 0
1638-
&& ts >= oldSnapshotControl->head_timestamp)
1648+
if (ts == update_ts)
16391649
{
1640-
int offset;
1641-
1642-
offset = ((ts - oldSnapshotControl->head_timestamp)
1643-
/ USECS_PER_MINUTE);
1644-
if (offset > oldSnapshotControl->count_used - 1)
1645-
offset = oldSnapshotControl->count_used - 1;
1646-
offset = (oldSnapshotControl->head_offset + offset)
1647-
% OLD_SNAPSHOT_TIME_MAP_ENTRIES;
1648-
xlimit = oldSnapshotControl->xid_by_minute[offset];
1649-
1650+
xlimit = latest_xmin;
16501651
if (NormalTransactionIdFollows(xlimit, recentXmin))
16511652
SetOldSnapshotThresholdTimestamp(ts, xlimit);
16521653
}
1654+
else
1655+
{
1656+
LWLockAcquire(OldSnapshotTimeMapLock, LW_SHARED);
16531657

1654-
LWLockRelease(OldSnapshotTimeMapLock);
1658+
if (oldSnapshotControl->count_used > 0
1659+
&& ts >= oldSnapshotControl->head_timestamp)
1660+
{
1661+
int offset;
1662+
1663+
offset = ((ts - oldSnapshotControl->head_timestamp)
1664+
/ USECS_PER_MINUTE);
1665+
if (offset > oldSnapshotControl->count_used - 1)
1666+
offset = oldSnapshotControl->count_used - 1;
1667+
offset = (oldSnapshotControl->head_offset + offset)
1668+
% OLD_SNAPSHOT_TIME_MAP_ENTRIES;
1669+
xlimit = oldSnapshotControl->xid_by_minute[offset];
1670+
1671+
if (NormalTransactionIdFollows(xlimit, recentXmin))
1672+
SetOldSnapshotThresholdTimestamp(ts, xlimit);
1673+
}
1674+
1675+
LWLockRelease(OldSnapshotTimeMapLock);
1676+
}
16551677
}
16561678

16571679
/*
@@ -1681,16 +1703,35 @@ void
16811703
MaintainOldSnapshotTimeMapping(int64 whenTaken, TransactionId xmin)
16821704
{
16831705
int64 ts;
1706+
TransactionId latest_xmin;
1707+
int64 update_ts;
1708+
bool map_update_required = false;
16841709

16851710
/* Never call this function when old snapshot checking is disabled. */
16861711
Assert(old_snapshot_threshold >= 0);
16871712

1688-
/* Keep track of the latest xmin seen by any process. */
1713+
ts = AlignTimestampToMinuteBoundary(whenTaken);
1714+
1715+
/*
1716+
* Keep track of the latest xmin seen by any process. Update mapping
1717+
* with a new value when we have crossed a bucket boundary.
1718+
*/
16891719
SpinLockAcquire(&oldSnapshotControl->mutex_latest_xmin);
1690-
if (TransactionIdFollows(xmin, oldSnapshotControl->latest_xmin))
1720+
latest_xmin = oldSnapshotControl->latest_xmin;
1721+
update_ts = oldSnapshotControl->next_map_update;
1722+
if (ts > update_ts)
1723+
{
1724+
oldSnapshotControl->next_map_update = ts;
1725+
map_update_required = true;
1726+
}
1727+
if (TransactionIdFollows(xmin, latest_xmin))
16911728
oldSnapshotControl->latest_xmin = xmin;
16921729
SpinLockRelease(&oldSnapshotControl->mutex_latest_xmin);
16931730

1731+
/* We only needed to update the most recent xmin value. */
1732+
if (!map_update_required)
1733+
return;
1734+
16941735
/* No further tracking needed for 0 (used for testing). */
16951736
if (old_snapshot_threshold == 0)
16961737
return;
@@ -1716,8 +1757,6 @@ MaintainOldSnapshotTimeMapping(int64 whenTaken, TransactionId xmin)
17161757
return;
17171758
}
17181759

1719-
ts = AlignTimestampToMinuteBoundary(whenTaken);
1720-
17211760
LWLockAcquire(OldSnapshotTimeMapLock, LW_EXCLUSIVE);
17221761

17231762
Assert(oldSnapshotControl->head_offset >= 0);

0 commit comments

Comments
 (0)