]> granicus.if.org Git - postgresql/commitdiff
Simplify autovacuum work-item implementation
authorAlvaro Herrera <alvherre@alvh.no-ip.org>
Tue, 15 Aug 2017 21:14:07 +0000 (18:14 -0300)
committerAlvaro Herrera <alvherre@alvh.no-ip.org>
Tue, 15 Aug 2017 21:14:07 +0000 (18:14 -0300)
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

src/backend/postmaster/autovacuum.c

index e1019ce39570745fa76f0a53ab1fae14f7c81984..776b1c0a9d5cfae8cbfa3e0b3fe5e71cc415c40b 100644 (file)
@@ -245,6 +245,24 @@ typedef enum
        AutoVacNumSignals                       /* must be last */
 }                      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
  * struct and the array of WorkerInfo structs.  This struct keeps:
@@ -255,10 +273,10 @@ typedef enum
  * av_runningWorkers the WorkerInfo non-free queue
  * av_startingWorker pointer to WorkerInfo currently being started (cleared by
  *                                     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
- * of the worker list (see above).  av_dsa_handle is readable unlocked.
+ * of the worker list (see above).
  *-------------
  */
 typedef struct
@@ -268,8 +286,7 @@ typedef struct
        dlist_head      av_freeWorkers;
        dlist_head      av_runningWorkers;
        WorkerInfo      av_startingWorker;
-       dsa_handle      av_dsa_handle;
-       dsa_pointer av_workitems;
+       AutoVacuumWorkItem av_workItems[NUM_WORKITEMS];
 } AutoVacuumShmemStruct;
 
 static AutoVacuumShmemStruct *AutoVacuumShmem;
@@ -284,32 +301,6 @@ static MemoryContext DatabaseListCxt = NULL;
 /* Pointer to my own WorkerInfo, valid on each worker */
 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 */
 int                    AutovacuumLauncherPid = 0;
 
@@ -356,8 +347,6 @@ static void av_sighup_handler(SIGNAL_ARGS);
 static void avl_sigusr2_handler(SIGNAL_ARGS);
 static void avl_sigterm_handler(SIGNAL_ARGS);
 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);
 
-       /*
-        * 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 */
        while (!got_SIGTERM)
        {
@@ -1697,14 +1663,6 @@ AutoVacWorkerMain(int argc, char *argv[])
        {
                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
                 * this before InitPostgres, so that the last_autovac_time will get
@@ -1987,6 +1945,7 @@ do_autovacuum(void)
        int                     effective_multixact_freeze_max_age;
        bool            did_vacuum = false;
        bool            found_concurrent_worker = false;
+       int                     i;
 
        /*
         * StartTransactionCommand and CommitTransactionCommand will automatically
@@ -2557,65 +2516,40 @@ deleted:
        /*
         * Perform additional work items, as requested by backends.
         */
-       if (AutoVacuumShmem->av_workitems)
+       LWLockAcquire(AutovacuumLock, LW_EXCLUSIVE);
+       for (i = 0; i < NUM_WORKITEMS; i++)
        {
-               dsa_pointer wi_ptr;
-               AutovacWorkItems *workitems;
+               AutoVacuumWorkItem *workitem = &AutoVacuumShmem->av_workItems[i];
 
-               LWLockAcquire(AutovacuumLock, LW_EXCLUSIVE);
+               if (!workitem->avw_used)
+                       continue;
+               if (workitem->avw_active)
+                       continue;
+
+               /* claim this one, and release lock while performing it */
+               workitem->avw_active = true;
+               LWLockRelease(AutovacuumLock);
+
+               perform_work_item(workitem);
 
                /*
-                * Scan the list of pending items, and process the inactive ones in
-                * our database.
+                * Check for config changes before acquiring lock for further
+                * jobs.
                 */
-               workitems = (AutovacWorkItems *)
-                       dsa_get_address(AutoVacuumDSA, AutoVacuumShmem->av_workitems);
-               wi_ptr = workitems->avs_usedItems;
-
-               while (wi_ptr != InvalidDsaPointer)
+               CHECK_FOR_INTERRUPTS();
+               if (got_SIGHUP)
                {
-                       AutoVacuumWorkItem *workitem;
-
-                       workitem = (AutoVacuumWorkItem *)
-                               dsa_get_address(AutoVacuumDSA, wi_ptr);
-
-                       if (workitem->avw_database == MyDatabaseId && !workitem->avw_active)
-                       {
-                               dsa_pointer next_ptr;
-
-                               /* claim this one */
-                               workitem->avw_active = true;
-
-                               LWLockRelease(AutovacuumLock);
-
-                               perform_work_item(workitem);
-
-                               /*
-                                * Check for config changes before acquiring lock for further
-                                * jobs.
-                                */
-                               CHECK_FOR_INTERRUPTS();
-                               if (got_SIGHUP)
-                               {
-                                       got_SIGHUP = false;
-                                       ProcessConfigFile(PGC_SIGHUP);
-                               }
-
-                               LWLockAcquire(AutovacuumLock, LW_EXCLUSIVE);
-
-                               /* Put the array item back for the next user */
-                               next_ptr = workitem->avw_next;
-                               remove_wi_from_list(&workitems->avs_usedItems, wi_ptr);
-                               add_wi_to_list(&workitems->avs_freeItems, wi_ptr);
-                               wi_ptr = next_ptr;
-                       }
-                       else
-                               wi_ptr = workitem->avw_next;
+                       got_SIGHUP = false;
+                       ProcessConfigFile(PGC_SIGHUP);
                }
 
-               /* all done */
-               LWLockRelease(AutovacuumLock);
+               LWLockAcquire(AutovacuumLock, LW_EXCLUSIVE);
+
+               /* and mark it done */
+               workitem->avw_active = false;
+               workitem->avw_used = false;
        }
+       LWLockRelease(AutovacuumLock);
 
        /*
         * We leak table_toast_map here (among other things), but since we're
@@ -3252,104 +3186,32 @@ void
 AutoVacuumRequestWork(AutoVacuumWorkItemType type, Oid relationId,
                                          BlockNumber blkno)
 {
-       AutovacWorkItems *workitems;
-       dsa_pointer wi_ptr;
-       AutoVacuumWorkItem *workitem;
+       int                     i;
 
        LWLockAcquire(AutovacuumLock, LW_EXCLUSIVE);
 
        /*
-        * It may be useful to de-duplicate the list upon insertion.  For the only
-        * currently existing caller, this is not necessary.
+        * Locate an unused work item and fill it with the given data.
         */
-
-       /* 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)
+       for (i = 0; i < NUM_WORKITEMS; i++)
        {
-               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);
+               AutoVacuumWorkItem *workitem = &AutoVacuumShmem->av_workItems[i];
 
-               workitems->avs_usedItems = InvalidDsaPointer;
-               workitems->avs_freeItems = InvalidDsaPointer;
-               for (i = 0; i < NUM_WORKITEMS; i++)
-               {
-                       /* XXX surely there is a simpler way to do this */
-                       wi_ptr = AutoVacuumShmem->av_workitems + sizeof(AutovacWorkItems) +
-                               sizeof(AutoVacuumWorkItem) * i;
-                       workitem = (AutoVacuumWorkItem *) dsa_get_address(AutoVacuumDSA, wi_ptr);
-
-                       workitem->avw_type = 0;
-                       workitem->avw_database = InvalidOid;
-                       workitem->avw_relation = InvalidOid;
-                       workitem->avw_active = false;
-
-                       /* put this item in the free list */
-                       workitem->avw_next = workitems->avs_freeItems;
-                       workitems->avs_freeItems = wi_ptr;
-               }
-       }
+               if (workitem->avw_used)
+                       continue;
 
-       workitems = (AutovacWorkItems *)
-               dsa_get_address(AutoVacuumDSA, AutoVacuumShmem->av_workitems);
+               workitem->avw_used = true;
+               workitem->avw_active = false;
+               workitem->avw_type = type;
+               workitem->avw_database = MyDatabaseId;
+               workitem->avw_relation = relationId;
+               workitem->avw_blockNumber = blkno;
 
-       /* If array is full, disregard the request */
-       if (workitems->avs_freeItems == InvalidDsaPointer)
-       {
-               LWLockRelease(AutovacuumLock);
-               dsa_detach(AutoVacuumDSA);
-               AutoVacuumDSA = NULL;
-               return;
+               /* done */
+               break;
        }
 
-       /* 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_database = MyDatabaseId;
-       workitem->avw_relation = relationId;
-       workitem->avw_blockNumber = blkno;
-       workitem->avw_active = false;
-
-       /* ... and put it on autovacuum's to-do list */
-       add_wi_to_list(&workitems->avs_usedItems, wi_ptr);
-
        LWLockRelease(AutovacuumLock);
-
-       dsa_detach(AutoVacuumDSA);
-       AutoVacuumDSA = NULL;
 }
 
 /*
@@ -3429,6 +3291,8 @@ AutoVacuumShmemInit(void)
                dlist_init(&AutoVacuumShmem->av_freeWorkers);
                dlist_init(&AutoVacuumShmem->av_runningWorkers);
                AutoVacuumShmem->av_startingWorker = NULL;
+               memset(AutoVacuumShmem->av_workItems, 0,
+                          sizeof(AutoVacuumWorkItem) * NUM_WORKITEMS);
 
                worker = (WorkerInfo) ((char *) AutoVacuumShmem +
                                                           MAXALIGN(sizeof(AutoVacuumShmemStruct)));
@@ -3473,59 +3337,3 @@ autovac_refresh_stats(void)
 
        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;
-       }
-}