Re: autovac issue with large number of tables - Mailing list pgsql-hackers

From Fujii Masao
Subject Re: autovac issue with large number of tables
Date
Msg-id 4a466c0f-b5f0-a702-86e7-fceb07a3d07a@oss.nttdata.com
Whole thread Raw
In response to Re: autovac issue with large number of tables  (Masahiko Sawada <sawada.mshk@gmail.com>)
Responses Re: autovac issue with large number of tables
List pgsql-hackers

On 2020/11/30 10:43, Masahiko Sawada wrote:
> On Sun, Nov 29, 2020 at 10:34 PM Kasahara Tatsuhito
> <kasahara.tatsuhito@gmail.com> wrote:
>>
>> Hi, Thanks for you comments.
>>
>> On Fri, Nov 27, 2020 at 9:51 PM Fujii Masao <masao.fujii@oss.nttdata.com> wrote:
>>>
>>>
>>>
>>> On 2020/11/27 18:38, Kasahara Tatsuhito wrote:
>>>> Hi,
>>>>
>>>> On Fri, Nov 27, 2020 at 1:43 AM Fujii Masao <masao.fujii@oss.nttdata.com> wrote:
>>>>>
>>>>>
>>>>>
>>>>> On 2020/11/26 10:41, Kasahara Tatsuhito wrote:
>>>>>> On Wed, Nov 25, 2020 at 8:46 PM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
>>>>>>>
>>>>>>> On Wed, Nov 25, 2020 at 4:18 PM Kasahara Tatsuhito
>>>>>>> <kasahara.tatsuhito@gmail.com> wrote:
>>>>>>>>
>>>>>>>> Hi,
>>>>>>>>
>>>>>>>> On Wed, Nov 25, 2020 at 2:17 PM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
>>>>>>>>>
>>>>>>>>> On Fri, Sep 4, 2020 at 7:50 PM Kasahara Tatsuhito
>>>>>>>>> <kasahara.tatsuhito@gmail.com> wrote:
>>>>>>>>>>
>>>>>>>>>> Hi,
>>>>>>>>>>
>>>>>>>>>> On Wed, Sep 2, 2020 at 2:10 AM Kasahara Tatsuhito
>>>>>>>>>> <kasahara.tatsuhito@gmail.com> wrote:
>>>>>>>>>>>> I wonder if we could have table_recheck_autovac do two probes of the stats
>>>>>>>>>>>> data.  First probe the existing stats data, and if it shows the table to
>>>>>>>>>>>> be already vacuumed, return immediately.  If not, *then* force a stats
>>>>>>>>>>>> re-read, and check a second time.
>>>>>>>>>>> Does the above mean that the second and subsequent table_recheck_autovac()
>>>>>>>>>>> will be improved to first check using the previous refreshed statistics?
>>>>>>>>>>> I think that certainly works.
>>>>>>>>>>>
>>>>>>>>>>> If that's correct, I'll try to create a patch for the PoC
>>>>>>>>>>
>>>>>>>>>> I still don't know how to reproduce Jim's troubles, but I was able to reproduce
>>>>>>>>>> what was probably a very similar problem.
>>>>>>>>>>
>>>>>>>>>> This problem seems to be more likely to occur in cases where you have
>>>>>>>>>> a large number of tables,
>>>>>>>>>> i.e., a large amount of stats, and many small tables need VACUUM at
>>>>>>>>>> the same time.
>>>>>>>>>>
>>>>>>>>>> So I followed Tom's advice and created a patch for the PoC.
>>>>>>>>>> This patch will enable a flag in the table_recheck_autovac function to use
>>>>>>>>>> the existing stats next time if VACUUM (or ANALYZE) has already been done
>>>>>>>>>> by another worker on the check after the stats have been updated.
>>>>>>>>>> If the tables continue to require VACUUM after the refresh, then a refresh
>>>>>>>>>> will be required instead of using the existing statistics.
>>>>>>>>>>
>>>>>>>>>> I did simple test with HEAD and HEAD + this PoC patch.
>>>>>>>>>> The tests were conducted in two cases.
>>>>>>>>>> (I changed few configurations. see attached scripts)
>>>>>>>>>>
>>>>>>>>>> 1. Normal VACUUM case
>>>>>>>>>>      - SET autovacuum = off
>>>>>>>>>>      - CREATE tables with 100 rows
>>>>>>>>>>      - DELETE 90 rows for each tables
>>>>>>>>>>      - SET autovacuum = on and restart PostgreSQL
>>>>>>>>>>      - Measure the time it takes for all tables to be VACUUMed
>>>>>>>>>>
>>>>>>>>>> 2. Anti wrap round VACUUM case
>>>>>>>>>>      - CREATE brank tables
>>>>>>>>>>      - SELECT all of these tables (for generate stats)
>>>>>>>>>>      - SET autovacuum_freeze_max_age to low values and restart PostgreSQL
>>>>>>>>>>      - Consumes a lot of XIDs by using txid_curent()
>>>>>>>>>>      - Measure the time it takes for all tables to be VACUUMed
>>>>>>>>>>
>>>>>>>>>> For each test case, the following results were obtained by changing
>>>>>>>>>> autovacuum_max_workers parameters to 1, 2, 3(def) 5 and 10.
>>>>>>>>>> Also changing num of tables to 1000, 5000, 10000 and 20000.
>>>>>>>>>>
>>>>>>>>>> Due to the poor VM environment (2 VCPU/4 GB), the results are a little unstable,
>>>>>>>>>> but I think it's enough to ask for a trend.
>>>>>>>>>>
>>>>>>>>>> ===========================================================================
>>>>>>>>>> [1.Normal VACUUM case]
>>>>>>>>>>     tables:1000
>>>>>>>>>>      autovacuum_max_workers 1:   (HEAD) 20 sec VS (with patch)  20 sec
>>>>>>>>>>      autovacuum_max_workers 2:   (HEAD) 18 sec VS (with patch)  16 sec
>>>>>>>>>>      autovacuum_max_workers 3:   (HEAD) 18 sec VS (with patch)  16 sec
>>>>>>>>>>      autovacuum_max_workers 5:   (HEAD) 19 sec VS (with patch)  17 sec
>>>>>>>>>>      autovacuum_max_workers 10:  (HEAD) 19 sec VS (with patch)  17 sec
>>>>>>>>>>
>>>>>>>>>>     tables:5000
>>>>>>>>>>      autovacuum_max_workers 1:   (HEAD) 77 sec VS (with patch)  78 sec
>>>>>>>>>>      autovacuum_max_workers 2:   (HEAD) 61 sec VS (with patch)  43 sec
>>>>>>>>>>      autovacuum_max_workers 3:   (HEAD) 38 sec VS (with patch)  38 sec
>>>>>>>>>>      autovacuum_max_workers 5:   (HEAD) 45 sec VS (with patch)  37 sec
>>>>>>>>>>      autovacuum_max_workers 10:  (HEAD) 43 sec VS (with patch)  35 sec
>>>>>>>>>>
>>>>>>>>>>     tables:10000
>>>>>>>>>>      autovacuum_max_workers 1:   (HEAD) 152 sec VS (with patch)  153 sec
>>>>>>>>>>      autovacuum_max_workers 2:   (HEAD) 119 sec VS (with patch)   98 sec
>>>>>>>>>>      autovacuum_max_workers 3:   (HEAD)  87 sec VS (with patch)   78 sec
>>>>>>>>>>      autovacuum_max_workers 5:   (HEAD) 100 sec VS (with patch)   66 sec
>>>>>>>>>>      autovacuum_max_workers 10:  (HEAD)  97 sec VS (with patch)   56 sec
>>>>>>>>>>
>>>>>>>>>>     tables:20000
>>>>>>>>>>      autovacuum_max_workers 1:   (HEAD) 338 sec VS (with patch)  339 sec
>>>>>>>>>>      autovacuum_max_workers 2:   (HEAD) 231 sec VS (with patch)  229 sec
>>>>>>>>>>      autovacuum_max_workers 3:   (HEAD) 220 sec VS (with patch)  191 sec
>>>>>>>>>>      autovacuum_max_workers 5:   (HEAD) 234 sec VS (with patch)  147 sec
>>>>>>>>>>      autovacuum_max_workers 10:  (HEAD) 320 sec VS (with patch)  113 sec
>>>>>>>>>>
>>>>>>>>>> [2.Anti wrap round VACUUM case]
>>>>>>>>>>     tables:1000
>>>>>>>>>>      autovacuum_max_workers 1:   (HEAD) 19 sec VS (with patch) 18 sec
>>>>>>>>>>      autovacuum_max_workers 2:   (HEAD) 14 sec VS (with patch) 15 sec
>>>>>>>>>>      autovacuum_max_workers 3:   (HEAD) 14 sec VS (with patch) 14 sec
>>>>>>>>>>      autovacuum_max_workers 5:   (HEAD) 14 sec VS (with patch) 16 sec
>>>>>>>>>>      autovacuum_max_workers 10:  (HEAD) 16 sec VS (with patch) 14 sec
>>>>>>>>>>
>>>>>>>>>>     tables:5000
>>>>>>>>>>      autovacuum_max_workers 1:   (HEAD) 69 sec VS (with patch) 69 sec
>>>>>>>>>>      autovacuum_max_workers 2:   (HEAD) 66 sec VS (with patch) 47 sec
>>>>>>>>>>      autovacuum_max_workers 3:   (HEAD) 59 sec VS (with patch) 37 sec
>>>>>>>>>>      autovacuum_max_workers 5:   (HEAD) 39 sec VS (with patch) 28 sec
>>>>>>>>>>      autovacuum_max_workers 10:  (HEAD) 39 sec VS (with patch) 29 sec
>>>>>>>>>>
>>>>>>>>>>     tables:10000
>>>>>>>>>>      autovacuum_max_workers 1:   (HEAD) 139 sec VS (with patch) 138 sec
>>>>>>>>>>      autovacuum_max_workers 2:   (HEAD) 130 sec VS (with patch)  86 sec
>>>>>>>>>>      autovacuum_max_workers 3:   (HEAD) 120 sec VS (with patch)  68 sec
>>>>>>>>>>      autovacuum_max_workers 5:   (HEAD)  96 sec VS (with patch)  41 sec
>>>>>>>>>>      autovacuum_max_workers 10:  (HEAD)  90 sec VS (with patch)  39 sec
>>>>>>>>>>
>>>>>>>>>>     tables:20000
>>>>>>>>>>      autovacuum_max_workers 1:   (HEAD) 313 sec VS (with patch) 331 sec
>>>>>>>>>>      autovacuum_max_workers 2:   (HEAD) 209 sec VS (with patch) 201 sec
>>>>>>>>>>      autovacuum_max_workers 3:   (HEAD) 227 sec VS (with patch) 141 sec
>>>>>>>>>>      autovacuum_max_workers 5:   (HEAD) 236 sec VS (with patch)  88 sec
>>>>>>>>>>      autovacuum_max_workers 10:  (HEAD) 309 sec VS (with patch)  74 sec
>>>>>>>>>> ===========================================================================
>>>>>>>>>>
>>>>>>>>>> The cases without patch, the scalability of the worker has decreased
>>>>>>>>>> as the number of tables has increased.
>>>>>>>>>> In fact, the more workers there are, the longer it takes to complete
>>>>>>>>>> VACUUM to all tables.
>>>>>>>>>> The cases with patch, it shows good scalability with respect to the
>>>>>>>>>> number of workers.
>>>>>>>>>
>>>>>>>>> It seems a good performance improvement even without the patch of
>>>>>>>>> shared memory based stats collector.
>>>>>
>>>>> Sounds great!
>>>>>
>>>>>
>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>> Note that perf top results showed that hash_search_with_hash_value,
>>>>>>>>>> hash_seq_search and
>>>>>>>>>> pgstat_read_statsfiles are dominant during VACUUM in all patterns,
>>>>>>>>>> with or without the patch.
>>>>>>>>>>
>>>>>>>>>> Therefore, there is still a need to find ways to optimize the reading
>>>>>>>>>> of large amounts of stats.
>>>>>>>>>> However, this patch is effective in its own right, and since there are
>>>>>>>>>> only a few parts to modify,
>>>>>>>>>> I think it should be able to be applied to current (preferably
>>>>>>>>>> pre-v13) PostgreSQL.
>>>>>>>>>
>>>>>>>>> +1
>>>>>>>>>
>>>>>>>>> +
>>>>>>>>> +       /* We might be better to refresh stats */
>>>>>>>>> +       use_existing_stats = false;
>>>>>>>>>        }
>>>>>>>>> +   else
>>>>>>>>> +   {
>>>>>>>>>
>>>>>>>>> -   heap_freetuple(classTup);
>>>>>>>>> +       heap_freetuple(classTup);
>>>>>>>>> +       /* The relid has already vacuumed, so we might be better to
>>>>>>>>> use exiting stats */
>>>>>>>>> +       use_existing_stats = true;
>>>>>>>>> +   }
>>>>>>>>>
>>>>>>>>> With that patch, the autovacuum process refreshes the stats in the
>>>>>>>>> next check if it finds out that the table still needs to be vacuumed.
>>>>>>>>> But I guess it's not necessarily true because the next table might be
>>>>>>>>> vacuumed already. So I think we might want to always use the existing
>>>>>>>>> for the first check. What do you think?
>>>>>>>> Thanks for your comment.
>>>>>>>>
>>>>>>>> If we assume the case where some workers vacuum on large tables
>>>>>>>> and a single worker vacuum on small tables, the processing
>>>>>>>> performance of the single worker will be slightly lower if the
>>>>>>>> existing statistics are checked every time.
>>>>>>>>
>>>>>>>> In fact, at first I tried to check the existing stats every time,
>>>>>>>> but the performance was slightly worse in cases with a small number of workers.
>>>>>
>>>>> Do you have this benchmark result?
>>>>>
>>>>>
>>>>>>>> (Checking the existing stats is lightweight , but at high frequency,
>>>>>>>>     it affects processing performance.)
>>>>>>>> Therefore, at after refresh statistics, determine whether autovac
>>>>>>>> should use the existing statistics.
>>>>>>>
>>>>>>> Yeah, since the test you used uses a lot of small tables, if there are
>>>>>>> a few workers, checking the existing stats is unlikely to return true
>>>>>>> (no need to vacuum). So the cost of existing stats check ends up being
>>>>>>> overhead. Not sure how slow always checking the existing stats was,
>>>>>>> but given that the shared memory based stats collector patch could
>>>>>>> improve the performance of refreshing stats, it might be better not to
>>>>>>> check the existing stats frequently like the patch does. Anyway, I
>>>>>>> think it’s better to evaluate the performance improvement with other
>>>>>>> cases too.
>>>>>> Yeah, I would like to see how much the performance changes in other cases.
>>>>>> In addition, if the shared-based-stats patch is applied, we won't need to reload
>>>>>> a huge stats file, so we will just have to check the stats on
>>>>>> shared-mem every time.
>>>>>> Perhaps the logic of table_recheck_autovac could be simpler.
>>>>>>
>>>>>>>> BTW, I found some typos in comments, so attache a  fixed version.
>>>>>
>>>>> The patch adds some duplicated codes into table_recheck_autovac().
>>>>> It's better to make the common function performing them and make
>>>>> table_recheck_autovac() call that common function, to simplify the code.
>>>> Thanks for your comment.
>>>> Hmm.. I've cut out the duplicate part.
>>>> Attach the patch.
>>>> Could you confirm that it fits your expecting?
>>>
>>> Yes, thanks for updataing the patch! Here are another review comments.
>>>
>>> +       shared = pgstat_fetch_stat_dbentry(InvalidOid);
>>> +       dbentry = pgstat_fetch_stat_dbentry(MyDatabaseId);
>>>
>>> When using the existing stats, ISTM that these are not necessary and
>>> we can reuse "shared" and "dbentry" obtained before. Right?
>> Yeah, but unless autovac_refresh_stats() is called, these functions
>> read the information from the
>> local hash table without re-read the stats file, so the process is very light.
>> Therefore, I think, it is better to keep the current logic to keep the
>> code simple.
>>
>>>
>>> +               /* We might be better to refresh stats */
>>> +               use_existing_stats = false;
>>>
>>> I think that we should add more comments about why it's better to
>>> refresh the stats in this case.
>>>
>>> +               /* The relid has already vacuumed, so we might be better to use existing stats */
>>> +               use_existing_stats = true;
>>>
>>> I think that we should add more comments about why it's better to
>>> reuse the stats in this case.
>> I added  comments.
>>
>> Attache the patch.
>>
> 
> Thank you for updating the patch. Here are some small comments on the
> latest (v4) patch.
> 
> +    * So if the last time we checked a table that was already vacuumed after
> +    * refres stats, check the current statistics before refreshing it.
> +    */
> 
> s/refres/refresh/
> 
> -----
> +/* Counter to determine if statistics should be refreshed */
> +static bool use_existing_stats = false;
> +
> 
> I think 'use_existing_stats' can be declared within table_recheck_autovac().
> 
> -----
> While testing the performance, I realized that the statistics are
> reset every time vacuumed one table, leading to re-reading the stats
> file even if 'use_existing_stats' is true. Please refer that vacuum()
> eventually calls AtEOXact_PgStat() which calls to
> pgstat_clear_snapshot().

Good catch!


> I believe that's why the performance of the
> method of always checking the existing stats wasn’t good as expected.
> So if we save the statistics somewhere and use it for rechecking, the
> results of the performance benchmark will differ between these two
> methods.

Or it's simpler to make autovacuum worker skip calling
pgstat_clear_snapshot() in AtEOXact_PgStat()?

Regards,

-- 
Fujii Masao
Advanced Computing Technology Center
Research and Development Headquarters
NTT DATA CORPORATION



pgsql-hackers by date:

Previous
From: Fabien COELHO
Date:
Subject: Re: pgbench - test whether a variable exists
Next
From: Michael Paquier
Date:
Subject: Re: pgbench - test whether a variable exists