Simplify autovacuum work-item implementation

The initial implementation of autovacuum work-items used a dynamic
shared memory area (DSA).  However, it's argued that dynamic shared
memory is not portable enough, so we cannot rely on it being supported
everywhere; at the same time, autovacuum work-items are now a critical
part of the server, so it's not acceptable that they don't work in the
cases where dynamic shared memory is disabled.  Therefore, let's fall
back to a simpler implementation of work-items that just uses
autovacuum's main shared memory segment for storage.

Discussion: https://postgr.es/m/CA+TgmobQVbz4K_+RSmiM9HeRKpy3vS5xnbkL95gSEnWijzprKQ@mail.gmail.com
This commit is contained in:
Alvaro Herrera 2017-08-15 18:14:07 -03:00
parent b73f1b5c29
commit 31ae1638ce

View File

@ -245,6 +245,24 @@ typedef enum
AutoVacNumSignals /* must be last */ AutoVacNumSignals /* must be last */
} AutoVacuumSignal; } AutoVacuumSignal;
/*
* Autovacuum workitem array, stored in AutoVacuumShmem->av_workItems. This
* list is mostly protected by AutovacuumLock, except that if an item is
* marked 'active' other processes must not modify the work-identifying
* members.
*/
typedef struct AutoVacuumWorkItem
{
AutoVacuumWorkItemType avw_type;
bool avw_used; /* below data is valid */
bool avw_active; /* being processed */
Oid avw_database;
Oid avw_relation;
BlockNumber avw_blockNumber;
} AutoVacuumWorkItem;
#define NUM_WORKITEMS 256
/*------------- /*-------------
* The main autovacuum shmem struct. On shared memory we store this main * The main autovacuum shmem struct. On shared memory we store this main
* struct and the array of WorkerInfo structs. This struct keeps: * struct and the array of WorkerInfo structs. This struct keeps:
@ -255,10 +273,10 @@ typedef enum
* av_runningWorkers the WorkerInfo non-free queue * av_runningWorkers the WorkerInfo non-free queue
* av_startingWorker pointer to WorkerInfo currently being started (cleared by * av_startingWorker pointer to WorkerInfo currently being started (cleared by
* the worker itself as soon as it's up and running) * the worker itself as soon as it's up and running)
* av_dsa_handle handle for allocatable shared memory * av_workItems work item array
* *
* This struct is protected by AutovacuumLock, except for av_signal and parts * This struct is protected by AutovacuumLock, except for av_signal and parts
* of the worker list (see above). av_dsa_handle is readable unlocked. * of the worker list (see above).
*------------- *-------------
*/ */
typedef struct typedef struct
@ -268,8 +286,7 @@ typedef struct
dlist_head av_freeWorkers; dlist_head av_freeWorkers;
dlist_head av_runningWorkers; dlist_head av_runningWorkers;
WorkerInfo av_startingWorker; WorkerInfo av_startingWorker;
dsa_handle av_dsa_handle; AutoVacuumWorkItem av_workItems[NUM_WORKITEMS];
dsa_pointer av_workitems;
} AutoVacuumShmemStruct; } AutoVacuumShmemStruct;
static AutoVacuumShmemStruct *AutoVacuumShmem; static AutoVacuumShmemStruct *AutoVacuumShmem;
@ -284,32 +301,6 @@ static MemoryContext DatabaseListCxt = NULL;
/* Pointer to my own WorkerInfo, valid on each worker */ /* Pointer to my own WorkerInfo, valid on each worker */
static WorkerInfo MyWorkerInfo = NULL; static WorkerInfo MyWorkerInfo = NULL;
/*
* Autovacuum workitem array, stored in AutoVacuumShmem->av_workitems. This
* list is mostly protected by AutovacuumLock, except that if it's marked
* 'active' other processes must not modify the work-identifying members,
* though changing the list pointers is okay.
*/
typedef struct AutoVacuumWorkItem
{
AutoVacuumWorkItemType avw_type;
Oid avw_database;
Oid avw_relation;
BlockNumber avw_blockNumber;
bool avw_active;
dsa_pointer avw_next; /* doubly linked list pointers */
dsa_pointer avw_prev;
} AutoVacuumWorkItem;
#define NUM_WORKITEMS 256
typedef struct
{
dsa_pointer avs_usedItems;
dsa_pointer avs_freeItems;
} AutovacWorkItems;
static dsa_area *AutoVacuumDSA = NULL;
/* PID of launcher, valid only in worker while shutting down */ /* PID of launcher, valid only in worker while shutting down */
int AutovacuumLauncherPid = 0; int AutovacuumLauncherPid = 0;
@ -356,8 +347,6 @@ static void av_sighup_handler(SIGNAL_ARGS);
static void avl_sigusr2_handler(SIGNAL_ARGS); static void avl_sigusr2_handler(SIGNAL_ARGS);
static void avl_sigterm_handler(SIGNAL_ARGS); static void avl_sigterm_handler(SIGNAL_ARGS);
static void autovac_refresh_stats(void); static void autovac_refresh_stats(void);
static void remove_wi_from_list(dsa_pointer *list, dsa_pointer wi_ptr);
static void add_wi_to_list(dsa_pointer *list, dsa_pointer wi_ptr);
@ -631,29 +620,6 @@ AutoVacLauncherMain(int argc, char *argv[])
*/ */
rebuild_database_list(InvalidOid); rebuild_database_list(InvalidOid);
/*
* Set up our DSA so that backends can install work-item requests. It may
* already exist as created by a previous launcher; and we may even be
* already attached to it, if we're here after longjmp'ing above.
*/
if (!AutoVacuumShmem->av_dsa_handle)
{
LWLockAcquire(AutovacuumLock, LW_EXCLUSIVE);
AutoVacuumDSA = dsa_create(AutovacuumLock->tranche);
/* make sure it doesn't go away even if we do */
dsa_pin(AutoVacuumDSA);
dsa_pin_mapping(AutoVacuumDSA);
AutoVacuumShmem->av_dsa_handle = dsa_get_handle(AutoVacuumDSA);
/* delay array allocation until first request */
AutoVacuumShmem->av_workitems = InvalidDsaPointer;
LWLockRelease(AutovacuumLock);
}
else if (AutoVacuumDSA == NULL)
{
AutoVacuumDSA = dsa_attach(AutoVacuumShmem->av_dsa_handle);
dsa_pin_mapping(AutoVacuumDSA);
}
/* loop until shutdown request */ /* loop until shutdown request */
while (!got_SIGTERM) while (!got_SIGTERM)
{ {
@ -1697,14 +1663,6 @@ AutoVacWorkerMain(int argc, char *argv[])
{ {
char dbname[NAMEDATALEN]; char dbname[NAMEDATALEN];
if (AutoVacuumShmem->av_dsa_handle)
{
/* First use of DSA in this worker, so attach to it */
Assert(!AutoVacuumDSA);
AutoVacuumDSA = dsa_attach(AutoVacuumShmem->av_dsa_handle);
dsa_pin_mapping(AutoVacuumDSA);
}
/* /*
* Report autovac startup to the stats collector. We deliberately do * Report autovac startup to the stats collector. We deliberately do
* this before InitPostgres, so that the last_autovac_time will get * this before InitPostgres, so that the last_autovac_time will get
@ -1987,6 +1945,7 @@ do_autovacuum(void)
int effective_multixact_freeze_max_age; int effective_multixact_freeze_max_age;
bool did_vacuum = false; bool did_vacuum = false;
bool found_concurrent_worker = false; bool found_concurrent_worker = false;
int i;
/* /*
* StartTransactionCommand and CommitTransactionCommand will automatically * StartTransactionCommand and CommitTransactionCommand will automatically
@ -2557,35 +2516,18 @@ deleted:
/* /*
* Perform additional work items, as requested by backends. * Perform additional work items, as requested by backends.
*/ */
if (AutoVacuumShmem->av_workitems)
{
dsa_pointer wi_ptr;
AutovacWorkItems *workitems;
LWLockAcquire(AutovacuumLock, LW_EXCLUSIVE); LWLockAcquire(AutovacuumLock, LW_EXCLUSIVE);
for (i = 0; i < NUM_WORKITEMS; i++)
/*
* Scan the list of pending items, and process the inactive ones in
* our database.
*/
workitems = (AutovacWorkItems *)
dsa_get_address(AutoVacuumDSA, AutoVacuumShmem->av_workitems);
wi_ptr = workitems->avs_usedItems;
while (wi_ptr != InvalidDsaPointer)
{ {
AutoVacuumWorkItem *workitem; AutoVacuumWorkItem *workitem = &AutoVacuumShmem->av_workItems[i];
workitem = (AutoVacuumWorkItem *) if (!workitem->avw_used)
dsa_get_address(AutoVacuumDSA, wi_ptr); continue;
if (workitem->avw_active)
continue;
if (workitem->avw_database == MyDatabaseId && !workitem->avw_active) /* claim this one, and release lock while performing it */
{
dsa_pointer next_ptr;
/* claim this one */
workitem->avw_active = true; workitem->avw_active = true;
LWLockRelease(AutovacuumLock); LWLockRelease(AutovacuumLock);
perform_work_item(workitem); perform_work_item(workitem);
@ -2603,19 +2545,11 @@ deleted:
LWLockAcquire(AutovacuumLock, LW_EXCLUSIVE); LWLockAcquire(AutovacuumLock, LW_EXCLUSIVE);
/* Put the array item back for the next user */ /* and mark it done */
next_ptr = workitem->avw_next; workitem->avw_active = false;
remove_wi_from_list(&workitems->avs_usedItems, wi_ptr); workitem->avw_used = false;
add_wi_to_list(&workitems->avs_freeItems, wi_ptr);
wi_ptr = next_ptr;
} }
else
wi_ptr = workitem->avw_next;
}
/* all done */
LWLockRelease(AutovacuumLock); LWLockRelease(AutovacuumLock);
}
/* /*
* We leak table_toast_map here (among other things), but since we're * We leak table_toast_map here (among other things), but since we're
@ -3252,104 +3186,32 @@ void
AutoVacuumRequestWork(AutoVacuumWorkItemType type, Oid relationId, AutoVacuumRequestWork(AutoVacuumWorkItemType type, Oid relationId,
BlockNumber blkno) BlockNumber blkno)
{ {
AutovacWorkItems *workitems; int i;
dsa_pointer wi_ptr;
AutoVacuumWorkItem *workitem;
LWLockAcquire(AutovacuumLock, LW_EXCLUSIVE); LWLockAcquire(AutovacuumLock, LW_EXCLUSIVE);
/* /*
* It may be useful to de-duplicate the list upon insertion. For the only * Locate an unused work item and fill it with the given data.
* currently existing caller, this is not necessary.
*/ */
/* First use in this process? Set up DSA */
if (!AutoVacuumDSA)
{
if (!AutoVacuumShmem->av_dsa_handle)
{
/* autovacuum launcher not started; nothing can be done */
LWLockRelease(AutovacuumLock);
return;
}
AutoVacuumDSA = dsa_attach(AutoVacuumShmem->av_dsa_handle);
dsa_pin_mapping(AutoVacuumDSA);
}
/* First use overall? Allocate work items array */
if (AutoVacuumShmem->av_workitems == InvalidDsaPointer)
{
int i;
AutovacWorkItems *workitems;
AutoVacuumShmem->av_workitems =
dsa_allocate_extended(AutoVacuumDSA,
sizeof(AutovacWorkItems) +
NUM_WORKITEMS * sizeof(AutoVacuumWorkItem),
DSA_ALLOC_NO_OOM);
/* if out of memory, silently disregard the request */
if (AutoVacuumShmem->av_workitems == InvalidDsaPointer)
{
LWLockRelease(AutovacuumLock);
dsa_detach(AutoVacuumDSA);
AutoVacuumDSA = NULL;
return;
}
/* Initialize each array entry as a member of the free list */
workitems = dsa_get_address(AutoVacuumDSA, AutoVacuumShmem->av_workitems);
workitems->avs_usedItems = InvalidDsaPointer;
workitems->avs_freeItems = InvalidDsaPointer;
for (i = 0; i < NUM_WORKITEMS; i++) for (i = 0; i < NUM_WORKITEMS; i++)
{ {
/* XXX surely there is a simpler way to do this */ AutoVacuumWorkItem *workitem = &AutoVacuumShmem->av_workItems[i];
wi_ptr = AutoVacuumShmem->av_workitems + sizeof(AutovacWorkItems) +
sizeof(AutoVacuumWorkItem) * i;
workitem = (AutoVacuumWorkItem *) dsa_get_address(AutoVacuumDSA, wi_ptr);
workitem->avw_type = 0; if (workitem->avw_used)
workitem->avw_database = InvalidOid; continue;
workitem->avw_relation = InvalidOid;
workitem->avw_used = true;
workitem->avw_active = false; workitem->avw_active = false;
/* put this item in the free list */
workitem->avw_next = workitems->avs_freeItems;
workitems->avs_freeItems = wi_ptr;
}
}
workitems = (AutovacWorkItems *)
dsa_get_address(AutoVacuumDSA, AutoVacuumShmem->av_workitems);
/* If array is full, disregard the request */
if (workitems->avs_freeItems == InvalidDsaPointer)
{
LWLockRelease(AutovacuumLock);
dsa_detach(AutoVacuumDSA);
AutoVacuumDSA = NULL;
return;
}
/* remove workitem struct from free list ... */
wi_ptr = workitems->avs_freeItems;
remove_wi_from_list(&workitems->avs_freeItems, wi_ptr);
/* ... initialize it ... */
workitem = dsa_get_address(AutoVacuumDSA, wi_ptr);
workitem->avw_type = type; workitem->avw_type = type;
workitem->avw_database = MyDatabaseId; workitem->avw_database = MyDatabaseId;
workitem->avw_relation = relationId; workitem->avw_relation = relationId;
workitem->avw_blockNumber = blkno; workitem->avw_blockNumber = blkno;
workitem->avw_active = false;
/* ... and put it on autovacuum's to-do list */ /* done */
add_wi_to_list(&workitems->avs_usedItems, wi_ptr); break;
}
LWLockRelease(AutovacuumLock); LWLockRelease(AutovacuumLock);
dsa_detach(AutoVacuumDSA);
AutoVacuumDSA = NULL;
} }
/* /*
@ -3429,6 +3291,8 @@ AutoVacuumShmemInit(void)
dlist_init(&AutoVacuumShmem->av_freeWorkers); dlist_init(&AutoVacuumShmem->av_freeWorkers);
dlist_init(&AutoVacuumShmem->av_runningWorkers); dlist_init(&AutoVacuumShmem->av_runningWorkers);
AutoVacuumShmem->av_startingWorker = NULL; AutoVacuumShmem->av_startingWorker = NULL;
memset(AutoVacuumShmem->av_workItems, 0,
sizeof(AutoVacuumWorkItem) * NUM_WORKITEMS);
worker = (WorkerInfo) ((char *) AutoVacuumShmem + worker = (WorkerInfo) ((char *) AutoVacuumShmem +
MAXALIGN(sizeof(AutoVacuumShmemStruct))); MAXALIGN(sizeof(AutoVacuumShmemStruct)));
@ -3473,59 +3337,3 @@ autovac_refresh_stats(void)
pgstat_clear_snapshot(); pgstat_clear_snapshot();
} }
/*
* Simplistic open-coded list implementation for objects stored in DSA.
* Each item is doubly linked, but we have no tail pointer, and the "prev"
* element of the first item is null, not the list.
*/
/*
* Remove a work item from the given list.
*/
static void
remove_wi_from_list(dsa_pointer *list, dsa_pointer wi_ptr)
{
AutoVacuumWorkItem *workitem = dsa_get_address(AutoVacuumDSA, wi_ptr);
dsa_pointer next = workitem->avw_next;
dsa_pointer prev = workitem->avw_prev;
workitem->avw_next = workitem->avw_prev = InvalidDsaPointer;
if (next != InvalidDsaPointer)
{
workitem = dsa_get_address(AutoVacuumDSA, next);
workitem->avw_prev = prev;
}
if (prev != InvalidDsaPointer)
{
workitem = dsa_get_address(AutoVacuumDSA, prev);
workitem->avw_next = next;
}
else
*list = next;
}
/*
* Add a workitem to the given list
*/
static void
add_wi_to_list(dsa_pointer *list, dsa_pointer wi_ptr)
{
if (*list == InvalidDsaPointer)
{
/* list is empty; item is now singleton */
*list = wi_ptr;
}
else
{
AutoVacuumWorkItem *workitem = dsa_get_address(AutoVacuumDSA, wi_ptr);
AutoVacuumWorkItem *old = dsa_get_address(AutoVacuumDSA, *list);
/* Put item at head of list */
workitem->avw_next = *list;
old->avw_prev = wi_ptr;
*list = wi_ptr;
}
}