Re: Some other CLOBBER_CACHE_ALWAYS culprits - Mailing list pgsql-hackers

From Andres Freund
Subject Re: Some other CLOBBER_CACHE_ALWAYS culprits
Date
Msg-id 20210512020200.dvjxqy6nwny3b7cr@alap3.anarazel.de
Whole thread Raw
In response to Re: Some other CLOBBER_CACHE_ALWAYS culprits  (Tom Lane <tgl@sss.pgh.pa.us>)
Responses Re: Some other CLOBBER_CACHE_ALWAYS culprits
Re: Some other CLOBBER_CACHE_ALWAYS culprits
List pgsql-hackers
Hi,

On 2021-05-11 19:30:48 -0400, Tom Lane wrote:
> > IMO the problem largely stems from eagerly rebuilding *all* relcache entries
> > during invalidation processing.
>
> Uh, we don't do that; only for relations that are pinned, which we
> know are being used.

Sorry, all surviving relcache entries - but that's typically quite a
few.


> I spent some time thinking along those lines too, but desisted after
> concluding that that would fundamentally break the point of CCA
> testing, namely to be sure we survive when a cache flush occurs at
> $any-random-point.

Why would rebuilding non-accessed relcache entries over and over help
with that? I am not proposing that we do not mark all cache entries are
invalid, or that we do not rebuild tables that aren't accessed.

During an extremely trivial query from a user defined table ('blarg'),
here's top 10 RelationBuildDesc() calls:
    344 rebuild pg_attrdef
    274 rebuild pg_opclass
    274 rebuild pg_amproc
    260 rebuild pg_index
    243 rebuild pg_am
    236 rebuild pg_attrdef_adrelid_adnum_index
    236 rebuild blarg
     74 rebuild pg_namespace
     52 rebuild pg_statistic
     37 rebuild pg_tablespace
134.420 ms

Here's the same when joining two tables:
   5828 rebuild pg_opclass
   2897 rebuild pg_amop
   2250 rebuild pg_cast
   2086 rebuild pg_amproc
   1465 rebuild pg_statistic
   1274 rebuild pg_index
    936 rebuild pg_attrdef
    646 rebuild pg_operator
    619 rebuild pg_am
    518 rebuild pg_tablespace
1414.886 ms

three tables:
  16614 rebuild pg_opclass
   7787 rebuild pg_amop
   6750 rebuild pg_cast
   5388 rebuild pg_amproc
   5141 rebuild pg_statistic
   3058 rebuild pg_index
   1824 rebuild pg_operator
   1374 rebuild pg_attrdef
   1233 rebuild pg_am
   1110 rebuild pg_tablespace
3971.506 ms

four:
  33328 rebuild pg_opclass
  16020 rebuild pg_amop
  14000 rebuild pg_statistic
  13500 rebuild pg_cast
  10876 rebuild pg_amproc
   5792 rebuild pg_index
   3950 rebuild pg_operator
   2035 rebuild pg_am
   1924 rebuild pg_tablespace
   1746 rebuild pg_attrdef
7927.172 ms

This omits all the work done as part of RelationReloadNailed(), but
shows the problem quite clearly, I think?

Basically, every additional accessed table in a transaction makes things
drastically slower.

In the four join case my four user defined tables were rebuilt a lot of
times:
    463 rebuild blarg
    440 rebuild blarg2
    293 rebuild blarg3
    233 rebuild blarg4
despite obviously not being relevant for the cache invalidation
processing itself.


The list of sytable scans in the four table case:
 380278 systable_beginscan: pg_class, using index: 1
 111539 systable_beginscan: pg_attribute, using index: 1
  73544 systable_beginscan: pg_class, using index: 0
   4134 systable_beginscan: pg_opclass, using index: 1
   4099 systable_beginscan: pg_amproc, using index: 1
   2791 systable_beginscan: pg_am, using index: 0
   2061 systable_beginscan: pg_index, using index: 1
   1429 systable_beginscan: pg_attrdef, using index: 1
    345 systable_beginscan: pg_type, using index: 1
    300 systable_beginscan: pg_cast, using index: 1
    195 systable_beginscan: pg_statistic, using index: 1
    191 systable_beginscan: pg_amop, using index: 1
    103 systable_beginscan: pg_operator, using index: 1
     52 systable_beginscan: pg_tablespace, using index: 1
     33 systable_beginscan: pg_proc, using index: 1
     27 systable_beginscan: pg_authid, using index: 1
     20 systable_beginscan: pg_namespace, using index: 1
      4 systable_beginscan: pg_statistic_ext, using index: 1

581145 in total.


> Sure, in practice it will not be the case that a flush occurs at EVERY
> random point.  But I think if you try to optimize away a rebuild at
> point B on the grounds that you just did one at point A, you will fail
> to cover the scenario where flush requests arrive at exactly points A
> and B.

I don't think we'd loose a lot of practical coverage if we avoided
rebuilding non-accessed relcache entries eagerly during cache
lookups. What coverage do we e.g. gain by having a single
SearchCatCacheMiss() triggering rebuilding the relcache of a user
defined table several times?

The InvalidateSystemCaches() marks all catcache entries as invalid. The
next catcache lookup will thus trigger a cache miss. That cache miss
will typically at least open the previously not locked relation + index
the cache is over. Each of those relation opens will fire off another
InvalidateSystemCaches(). Which will rebuild all the surviving relcache
entries at least twice - despite never being accessed in that path.


> What it looked like to me, in an admittedly cursory bit of perf
> testing, was that most of the cycles were going into fetching
> cache entries from catalogs over and over.  But it's hard to avoid
> that.

Sure - but that's only because we rebuild stuff over and over despite
not being accessed...

Greetings,

Andres Freund



pgsql-hackers by date:

Previous
From: Tom Lane
Date:
Subject: Re: Some other CLOBBER_CACHE_ALWAYS culprits
Next
From: Kyotaro Horiguchi
Date:
Subject: Re: compute_query_id and pg_stat_statements