Re: Protect syscache from bloating with negative cache entries

Поиск
Список
Период
Сортировка
От Kyotaro HORIGUCHI
Тема Re: Protect syscache from bloating with negative cache entries
Дата
Msg-id 20190301.173245.25820912.horiguchi.kyotaro@lab.ntt.co.jp
обсуждение исходный текст
Ответ на Re: Protect syscache from bloating with negative cache entries  (Robert Haas <robertmhaas@gmail.com>)
Ответы Re: Protect syscache from bloating with negative cache entries
Список pgsql-hackers
At Tue, 26 Feb 2019 10:55:18 -0500, Robert Haas <robertmhaas@gmail.com> wrote in
<CA+Tgmoa2b-LUF9h3wugD9ZA5MP0xyu2kJYHC9L6sdLywNSmhBQ@mail.gmail.com>
> On Mon, Feb 25, 2019 at 1:27 AM Kyotaro HORIGUCHI
> <horiguchi.kyotaro@lab.ntt.co.jp> wrote:
> > > I'd like to see some evidence that catalog_cache_memory_target has any
> > > value, vs. just always setting it to zero.
> >
> > It is artificial (or acutually wont't be repeatedly executed in a
> > session) but anyway what can get benefit from
> > catalog_cache_memory_target would be a kind of extreme.
> 
> I agree.  So then let's not have it.

Ah... Yeah! I see. Andres' concern was that crucial syscache
entries might be blown away during a long idle time. If that
happens, it's enough to just turn off in the almost all of such
cases.

We no longer need to count memory usage without the feature. That
sutff is moved to monitoring feature, which is out of the scope
of the current status of this patch.

> We shouldn't add more mechanism here than actually has value.  It
> seems pretty clear that keeping cache entries that go unused for long
> periods can't be that important; even if we need them again
> eventually, reloading them every 5 or 10 minutes can't hurt that much.
> On the other hand, I think it's also pretty clear that evicting cache
> entries that are being used frequently will have disastrous effects on
> performance; as I noted in the other email I just sent, consider the
> effects of CLOBBER_CACHE_ALWAYS.  No reasonable user is going to want
> to incur a massive slowdown to save a little bit of memory.
> 
> I see that *in theory* there is a value to
> catalog_cache_memory_target, because *maybe* there is a workload where
> tuning that GUC will lead to better performance at lower memory usage
> than any competing proposal.  But unless we can actually see an
> example of such a workload, which so far I don't, we're adding a knob
> that everybody has to think about how to tune when in fact we have no
> idea how to tune it or whether it even needs to be tuned.  That
> doesn't make sense.  We have to be able to document the parameters we
> have and explain to users how they should be used.  And as far as this
> parameter is concerned I think we are not at that point.

In the attached v18,
   catalog_cache_memory_target is removed,
   removed some leftover of removing the hard limit feature, 
   separated catcache clock update during a query into 0003.
   attached 0004 (monitor part) in order just to see how it is working.

v18-0001-Add-dlist_move_tail:
  Just adds dlist_move_tail

v18-0002-Remove-entries-that-haven-t-been-used-for-a-certain-:
  Revised pruning feature.

====
v18-0003-Asynchronous-update-of-catcache-clock:
  Separated catcache clock update feature.

v18-0004-Syscache-usage-tracking-feature:
  Usage tracking feature.

regards.

-- 
Kyotaro Horiguchi
NTT Open Source Software Center
From 54388a7452eda1faadaa108e1bc21d51844f9224 Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Thu, 7 Feb 2019 14:56:07 +0900
Subject: [PATCH 1/6] Add dlist_move_tail

We have dlist_push_head/tail and dlist_move_head but not
dlist_move_tail. Add it.
---
 src/include/lib/ilist.h | 19 +++++++++++++++++++
 1 file changed, 19 insertions(+)

diff --git a/src/include/lib/ilist.h b/src/include/lib/ilist.h
index b1a5974ee4..659ab1ac87 100644
--- a/src/include/lib/ilist.h
+++ b/src/include/lib/ilist.h
@@ -394,6 +394,25 @@ dlist_move_head(dlist_head *head, dlist_node *node)
     dlist_check(head);
 }
 
+/*
+ * Move element from its current position in the list to the tail position in
+ * the same list.
+ *
+ * Undefined behaviour if 'node' is not already part of the list.
+ */
+static inline void
+dlist_move_tail(dlist_head *head, dlist_node *node)
+{
+    /* fast path if it's already at the tail */
+    if (head->head.prev == node)
+        return;
+
+    dlist_delete(node);
+    dlist_push_tail(head, node);
+
+    dlist_check(head);
+}
+
 /*
  * Check whether 'node' has a following node.
  * Caution: unreliable if 'node' is not in the list.
-- 
2.16.3

From c79d5fc86f45e6545cbc257040e46125ffc5cb92 Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Fri, 1 Mar 2019 13:32:51 +0900
Subject: [PATCH 2/6] Remove entries that haven't been used for a certain time

Catcache entries happen to be left alone for several reasons. It is
not desirable that such useless entries eat up memory. Catcache
pruning feature removes entries that haven't been accessed for a
certain time before enlarging hash array.
---
 doc/src/sgml/config.sgml                      |  19 ++++
 src/backend/tcop/postgres.c                   |   2 +
 src/backend/utils/cache/catcache.c            | 122 +++++++++++++++++++++++++-
 src/backend/utils/misc/guc.c                  |  12 +++
 src/backend/utils/misc/postgresql.conf.sample |   1 +
 src/include/utils/catcache.h                  |  18 ++++
 6 files changed, 171 insertions(+), 3 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 6d42b7afe7..737a156bb4 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1661,6 +1661,25 @@ include_dir 'conf.d'
       </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-catalog-cache-prune-min-age" xreflabel="catalog_cache_prune_min_age">
+      <term><varname>catalog_cache_prune_min_age</varname> (<type>integer</type>)
+      <indexterm>
+       <primary><varname>catalog_cache_prune_min_age</varname> configuration
+       parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+         Specifies the minimum amount of unused time in seconds at which a
+         system catalog cache entry is removed. -1 indicates that this feature
+         is disabled at all. The value defaults to 300 seconds (<literal>5
+         minutes</literal>). The entries that are not used for the duration
+         can be removed to prevent catalog cache from bloating with useless
+         entries.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry id="guc-max-stack-depth" xreflabel="max_stack_depth">
       <term><varname>max_stack_depth</varname> (<type>integer</type>)
       <indexterm>
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 8b4d94c9a1..02b9ef98aa 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -71,6 +71,7 @@
 #include "tcop/pquery.h"
 #include "tcop/tcopprot.h"
 #include "tcop/utility.h"
+#include "utils/catcache.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
 #include "utils/ps_status.h"
@@ -2584,6 +2585,7 @@ start_xact_command(void)
      * not desired, the timeout has to be disabled explicitly.
      */
     enable_statement_timeout();
+    SetCatCacheClock(GetCurrentStatementStartTimestamp());
 }
 
 static void
diff --git a/src/backend/utils/cache/catcache.c b/src/backend/utils/cache/catcache.c
index 78dd5714fa..4386957497 100644
--- a/src/backend/utils/cache/catcache.c
+++ b/src/backend/utils/cache/catcache.c
@@ -39,6 +39,7 @@
 #include "utils/rel.h"
 #include "utils/resowner_private.h"
 #include "utils/syscache.h"
+#include "utils/timeout.h"
 
 
  /* #define CACHEDEBUG */    /* turns DEBUG elogs on */
@@ -61,9 +62,24 @@
 #define CACHE_elog(...)
 #endif
 
+/*
+ * GUC variable to define the minimum age of entries that will be considered
+ * to be evicted in seconds. -1 to disable the feature.
+ */
+int catalog_cache_prune_min_age = 300;
+
+/*
+ * Minimum interval between two successive moves of a cache entry in LRU list,
+ * in microseconds.
+ */
+#define MIN_LRU_UPDATE_INTERVAL 100000    /* 100ms */
+
 /* Cache management header --- pointer is NULL until created */
 static CatCacheHeader *CacheHdr = NULL;
 
+/* Clock for the last accessed time of a catcache entry. */
+TimestampTz    catcacheclock = 0;
+
 static inline HeapTuple SearchCatCacheInternal(CatCache *cache,
                        int nkeys,
                        Datum v1, Datum v2,
@@ -469,6 +485,7 @@ CatCacheRemoveCTup(CatCache *cache, CatCTup *ct)
 
     /* delink from linked list */
     dlist_delete(&ct->cache_elem);
+    dlist_delete(&ct->lru_node);
 
     /*
      * Free keys when we're dealing with a negative entry, normal entries just
@@ -829,6 +846,7 @@ InitCatCache(int id,
     cp->cc_nkeys = nkeys;
     for (i = 0; i < nkeys; ++i)
         cp->cc_keyno[i] = key[i];
+    dlist_init(&cp->cc_lru_list);
 
     /*
      * new cache is initialized as far as we can go for now. print some
@@ -846,9 +864,83 @@ InitCatCache(int id,
      */
     MemoryContextSwitchTo(oldcxt);
 
+    /* initialize catcache reference clock if haven't done yet */
+    if (catcacheclock == 0)
+        catcacheclock = GetCurrentTimestamp();
+
     return cp;
 }
 
+/*
+ * CatCacheCleanupOldEntries - Remove infrequently-used entries
+ *
+ * Catcache entries happen to be left unused for a long time for several
+ * reasons. Remove such entries to prevent catcache from bloating. It is based
+ * on the similar algorithm with buffer eviction. Entries that are accessed
+ * several times in a certain period live longer than those that have had less
+ * access in the same duration.
+ */
+static bool
+CatCacheCleanupOldEntries(CatCache *cp)
+{
+    int            nremoved = 0;
+    dlist_mutable_iter    iter;
+
+    /* Return immediately if disabled */
+    if (catalog_cache_prune_min_age == 0)
+        return false;
+
+    /* Scan over LRU to find entries to remove */
+    dlist_foreach_modify(iter, &cp->cc_lru_list)
+    {
+        CatCTup    *ct = dlist_container(CatCTup, lru_node, iter.cur);
+        long        entry_age;
+        int            us;
+
+        /* Don't remove referenced entries */
+        if (ct->refcount != 0 ||
+            (ct->c_list && ct->c_list->refcount != 0))
+            continue;
+
+        /*
+         * Calculate the duration from the time from the last access to
+         * the "current" time. catcacheclock is updated per-statement
+         * basis.
+         */
+        TimestampDifference(ct->lastaccess, catcacheclock, &entry_age, &us);
+
+        if (entry_age < catalog_cache_prune_min_age)
+        {
+            /*
+             * We don't have older entries, exit.  At least one removal
+             * prevents rehashing this time.
+             */
+            break;
+        }
+
+        /*
+         * Entries that are not accessed after the last pruning are removed in
+         * that seconds, and their lives are prolonged according to how many
+         * times they are accessed up to three times of the duration. We don't
+         * try shrink buckets since pruning effectively caps catcache
+         * expansion in the long term.
+         */
+        if (ct->naccess > 0)
+            ct->naccess--;
+        else
+        {
+            CatCacheRemoveCTup(cp, ct);
+            nremoved++;
+        }
+    }
+
+    if (nremoved > 0)
+        elog(DEBUG1, "pruning catalog cache id=%d for %s: removed %d / %d",
+             cp->id, cp->cc_relname, nremoved, cp->cc_ntup + nremoved);
+
+    return nremoved > 0;
+}
+
 /*
  * Enlarge a catcache, doubling the number of buckets.
  */
@@ -1260,6 +1352,20 @@ SearchCatCacheInternal(CatCache *cache,
          */
         dlist_move_head(bucket, &ct->cache_elem);
 
+        /* prolong life of this entry */
+        if (ct->naccess < 2)
+            ct->naccess++;
+
+        /*
+         * Don't update LRU too frequently. We need to maintain the LRU even
+         * if pruning is inactive since it can be turned on on-session.
+         */
+        if (catcacheclock - ct->lastaccess > MIN_LRU_UPDATE_INTERVAL)
+        {
+            ct->lastaccess = catcacheclock;
+            dlist_move_tail(&cache->cc_lru_list, &ct->lru_node);
+        }
+
         /*
          * If it's a positive entry, bump its refcount and return it. If it's
          * negative, we can report failure to the caller.
@@ -1884,19 +1990,29 @@ CatalogCacheCreateEntry(CatCache *cache, HeapTuple ntp, Datum *arguments,
     ct->dead = false;
     ct->negative = negative;
     ct->hash_value = hashValue;
+    ct->naccess = 0;
+    ct->lastaccess = catcacheclock;
+    dlist_push_tail(&cache->cc_lru_list, &ct->lru_node);
 
     dlist_push_head(&cache->cc_bucket[hashIndex], &ct->cache_elem);
 
     cache->cc_ntup++;
     CacheHdr->ch_ntup++;
 
+    /* increase refcount so that the new entry survives pruning */
+    ct->refcount++;
+
     /*
-     * If the hash table has become too full, enlarge the buckets array. Quite
-     * arbitrarily, we enlarge when fill factor > 2.
+     * If the hash table has become too full, try removing infrequently used
+     * entries to make a room for the new entry. If failed, enlarge the bucket
+     * array instead.  Quite arbitrarily, we try this when fill factor > 2.
      */
-    if (cache->cc_ntup > cache->cc_nbuckets * 2)
+    if (cache->cc_ntup > cache->cc_nbuckets * 2 &&
+        !CatCacheCleanupOldEntries(cache))
         RehashCatCache(cache);
 
+    ct->refcount--;
+
     return ct;
 }
 
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 156d147c85..3acc86cd07 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -81,6 +81,7 @@
 #include "tsearch/ts_cache.h"
 #include "utils/builtins.h"
 #include "utils/bytea.h"
+#include "utils/catcache.h"
 #include "utils/guc_tables.h"
 #include "utils/float.h"
 #include "utils/memutils.h"
@@ -2205,6 +2206,17 @@ static struct config_int ConfigureNamesInt[] =
         NULL, NULL, NULL
     },
 
+    {
+        {"catalog_cache_prune_min_age", PGC_USERSET, RESOURCES_MEM,
+            gettext_noop("System catalog cache entries that live unused for longer than this seconds are considered
forremoval."),
 
+            gettext_noop("The value of -1 turns off pruning."),
+            GUC_UNIT_S
+        },
+        &catalog_cache_prune_min_age,
+        300, -1, INT_MAX,
+        NULL, NULL, NULL
+    },
+
     /*
      * We use the hopefully-safely-small value of 100kB as the compiled-in
      * default for max_stack_depth.  InitializeGUCOptions will increase it if
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index bd6ea65d0c..e9e3acc903 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -128,6 +128,7 @@
 #work_mem = 4MB                # min 64kB
 #maintenance_work_mem = 64MB        # min 1MB
 #autovacuum_work_mem = -1        # min 1MB, or -1 to use maintenance_work_mem
+#catalog_cache_prune_min_age = 300s    # -1 disables pruning
 #max_stack_depth = 2MB            # min 100kB
 #shared_memory_type = mmap        # the default is the first option
                     # supported by the operating system:
diff --git a/src/include/utils/catcache.h b/src/include/utils/catcache.h
index 65d816a583..a21c53644a 100644
--- a/src/include/utils/catcache.h
+++ b/src/include/utils/catcache.h
@@ -22,6 +22,7 @@
 
 #include "access/htup.h"
 #include "access/skey.h"
+#include "datatype/timestamp.h"
 #include "lib/ilist.h"
 #include "utils/relcache.h"
 
@@ -61,6 +62,7 @@ typedef struct catcache
     slist_node    cc_next;        /* list link */
     ScanKeyData cc_skey[CATCACHE_MAXKEYS];    /* precomputed key info for heap
                                              * scans */
+    dlist_head    cc_lru_list;
 
     /*
      * Keep these at the end, so that compiling catcache.c with CATCACHE_STATS
@@ -119,6 +121,9 @@ typedef struct catctup
     bool        dead;            /* dead but not yet removed? */
     bool        negative;        /* negative cache entry? */
     HeapTupleData tuple;        /* tuple management header */
+    int            naccess;        /* # of access to this entry, up to 2  */
+    TimestampTz    lastaccess;        /* timestamp of the last usage */
+    dlist_node    lru_node;        /* LRU node */
 
     /*
      * The tuple may also be a member of at most one CatCList.  (If a single
@@ -189,6 +194,19 @@ typedef struct catcacheheader
 /* this extern duplicates utils/memutils.h... */
 extern PGDLLIMPORT MemoryContext CacheMemoryContext;
 
+/* for guc.c, not PGDLLPMPORT'ed */
+extern int catalog_cache_prune_min_age;
+
+/* source clock for access timestamp of catcache entries */
+extern TimestampTz catcacheclock;
+
+/* SetCatCacheClock - set catcache timestamp source clodk */
+static inline void
+SetCatCacheClock(TimestampTz ts)
+{
+    catcacheclock = ts;
+}
+
 extern void CreateCacheMemoryContext(void);
 
 extern CatCache *InitCatCache(int id, Oid reloid, Oid indexoid,
-- 
2.16.3

From 5c6357cc575bf0f1d03740c2f2e94d3d79a53f4e Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Fri, 1 Mar 2019 14:16:55 +0900
Subject: [PATCH 3/6] Asynchronous update of catcache clock

The catcache pruning feature fails to work while a long running query
executes many commands and fetches many syscache entries. This patch
asynchronously updates the catcache clock to make the pruning work
even in the case.
---
 src/backend/tcop/postgres.c        | 11 +++++++
 src/backend/utils/cache/catcache.c | 65 ++++++++++++++++++++++++++++++++++++--
 src/backend/utils/init/globals.c   |  1 +
 src/backend/utils/init/postinit.c  | 14 ++++++++
 src/backend/utils/misc/guc.c       |  2 +-
 src/include/miscadmin.h            |  1 +
 src/include/utils/catcache.h       | 23 +++++++++++++-
 src/include/utils/timeout.h        |  1 +
 8 files changed, 114 insertions(+), 4 deletions(-)

diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 02b9ef98aa..d9a54ed37f 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -3161,6 +3161,14 @@ ProcessInterrupts(void)
 
     if (ParallelMessagePending)
         HandleParallelMessages();
+
+    if (CatcacheClockTimeoutPending)
+    {
+        CatcacheClockTimeoutPending = false;
+
+        /* Update timestamp then set up the next timeout */
+        UpdateCatCacheClock();
+    }
 }
 
 
@@ -4023,6 +4031,9 @@ PostgresMain(int argc, char *argv[],
         QueryCancelPending = false; /* second to avoid race condition */
         stmt_timeout_active = false;
 
+        /* get sync with the timer state */
+        catcache_clock_timeout_active = false;
+
         /* Not reading from the client anymore. */
         DoingCommandRead = false;
 
diff --git a/src/backend/utils/cache/catcache.c b/src/backend/utils/cache/catcache.c
index 4386957497..e0ecfe09d4 100644
--- a/src/backend/utils/cache/catcache.c
+++ b/src/backend/utils/cache/catcache.c
@@ -69,7 +69,12 @@
 int catalog_cache_prune_min_age = 300;
 
 /*
- * Minimum interval between two successive moves of a cache entry in LRU list,
+ * Flag to keep track of whether catcache clock timer is active.
+ */
+bool catcache_clock_timeout_active = false;
+
+/*
+ * Minimum interval between two success move of a cache entry in LRU list,
  * in microseconds.
  */
 #define MIN_LRU_UPDATE_INTERVAL 100000    /* 100ms */
@@ -871,6 +876,61 @@ InitCatCache(int id,
     return cp;
 }
 
+/*
+ * Helper routine for SetCatCacheClock and UpdateCatCacheClockTimer.
+ *
+ * Maintains the catcache clock during a long query.
+ */
+void
+SetupCatCacheClockTimer(void)
+{
+    long delay;
+
+    /* stop timer if no longer needed */
+    if (catalog_cache_prune_min_age == 0)
+    {
+        catcache_clock_timeout_active = false;
+        return;
+    }
+
+    /* One 10th of the prune age, in milliseconds */
+    delay  = catalog_cache_prune_min_age * 1000/10;
+
+    /* We don't need to update the clock so frequently. */
+    if (delay < 1000)
+        delay = 1000;
+
+    enable_timeout_after(CATCACHE_CLOCK_TIMEOUT, delay);
+
+    catcache_clock_timeout_active = true;
+}
+
+/*
+ * Update catcacheclock:
+ *
+ * Intended to be called when CATCACHE_CLOCK_TIMEOUT fires. The interval is
+ * expected more than 1 second (see above), so GetCurrentTime() doesn't harm.
+ */
+void
+UpdateCatCacheClock(void)
+{
+    catcacheclock = GetCurrentTimestamp();
+    SetupCatCacheClockTimer();
+}
+
+/*
+ * Change of catalog_cache_prune_min_age requires rearming of the timer. Just
+ * disabling here causes later rearming as needed.
+ */
+void
+assign_catalog_cache_prune_min_age(int newval, void *extra)
+{
+    if (catcache_clock_timeout_active)
+        disable_timeout(CATCACHE_CLOCK_TIMEOUT, false);
+
+    catcache_clock_timeout_active = false;
+}
+
 /*
  * CatCacheCleanupOldEntries - Remove infrequently-used entries
  *
@@ -905,7 +965,8 @@ CatCacheCleanupOldEntries(CatCache *cp)
         /*
          * Calculate the duration from the time from the last access to
          * the "current" time. catcacheclock is updated per-statement
-         * basis.
+         * basis and additionaly udpated periodically during a long
+         * running query.
          */
         TimestampDifference(ct->lastaccess, catcacheclock, &entry_age, &us);
 
diff --git a/src/backend/utils/init/globals.c b/src/backend/utils/init/globals.c
index fd51934aaf..0e8b972a29 100644
--- a/src/backend/utils/init/globals.c
+++ b/src/backend/utils/init/globals.c
@@ -32,6 +32,7 @@ volatile sig_atomic_t QueryCancelPending = false;
 volatile sig_atomic_t ProcDiePending = false;
 volatile sig_atomic_t ClientConnectionLost = false;
 volatile sig_atomic_t IdleInTransactionSessionTimeoutPending = false;
+volatile sig_atomic_t CatcacheClockTimeoutPending = false;
 volatile sig_atomic_t ConfigReloadPending = false;
 volatile uint32 InterruptHoldoffCount = 0;
 volatile uint32 QueryCancelHoldoffCount = 0;
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index a5ee209f91..eb17103595 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -72,6 +72,7 @@ static void ShutdownPostgres(int code, Datum arg);
 static void StatementTimeoutHandler(void);
 static void LockTimeoutHandler(void);
 static void IdleInTransactionSessionTimeoutHandler(void);
+static void CatcacheClockTimeoutHandler(void);
 static bool ThereIsAtLeastOneRole(void);
 static void process_startup_options(Port *port, bool am_superuser);
 static void process_settings(Oid databaseid, Oid roleid);
@@ -628,6 +629,8 @@ InitPostgres(const char *in_dbname, Oid dboid, const char *username,
         RegisterTimeout(LOCK_TIMEOUT, LockTimeoutHandler);
         RegisterTimeout(IDLE_IN_TRANSACTION_SESSION_TIMEOUT,
                         IdleInTransactionSessionTimeoutHandler);
+        RegisterTimeout(CATCACHE_CLOCK_TIMEOUT,
+                        CatcacheClockTimeoutHandler);
     }
 
     /*
@@ -1238,6 +1241,17 @@ IdleInTransactionSessionTimeoutHandler(void)
     SetLatch(MyLatch);
 }
 
+/*
+ * CATCACHE_CLOCK_TIMEOUT handler: trigger a catcache source clock update
+ */
+static void
+CatcacheClockTimeoutHandler(void)
+{
+    CatcacheClockTimeoutPending = true;
+    InterruptPending = true;
+    SetLatch(MyLatch);
+}
+
 /*
  * Returns true if at least one role is defined in this database cluster.
  */
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 3acc86cd07..0bdea0c383 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -2214,7 +2214,7 @@ static struct config_int ConfigureNamesInt[] =
         },
         &catalog_cache_prune_min_age,
         300, -1, INT_MAX,
-        NULL, NULL, NULL
+        NULL, assign_catalog_cache_prune_min_age, NULL
     },
 
     /*
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index c9e35003a5..33b800e80f 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -82,6 +82,7 @@ extern PGDLLIMPORT volatile sig_atomic_t InterruptPending;
 extern PGDLLIMPORT volatile sig_atomic_t QueryCancelPending;
 extern PGDLLIMPORT volatile sig_atomic_t ProcDiePending;
 extern PGDLLIMPORT volatile sig_atomic_t IdleInTransactionSessionTimeoutPending;
+extern PGDLLIMPORT volatile sig_atomic_t CatcacheClockTimeoutPending;
 extern PGDLLIMPORT volatile sig_atomic_t ConfigReloadPending;
 
 extern PGDLLIMPORT volatile sig_atomic_t ClientConnectionLost;
diff --git a/src/include/utils/catcache.h b/src/include/utils/catcache.h
index a21c53644a..5141f57bac 100644
--- a/src/include/utils/catcache.h
+++ b/src/include/utils/catcache.h
@@ -200,13 +200,34 @@ extern int catalog_cache_prune_min_age;
 /* source clock for access timestamp of catcache entries */
 extern TimestampTz catcacheclock;
 
-/* SetCatCacheClock - set catcache timestamp source clodk */
+/*
+ * Flag to keep track of whether catcache timestamp timer is active.
+ */
+extern bool catcache_clock_timeout_active;
+
+/* catcache prune time helper functions  */
+extern void SetupCatCacheClockTimer(void);
+extern void UpdateCatCacheClock(void);
+
+/*
+ * SetCatCacheClock - set catcache timestamp source clock
+ *
+ * The clock is passively updated per-query basis. We need to update it
+ * asynchronously in the case where a long running query executes many
+ * commands. Setup the timeout to do that. Setting a timout is so complex that
+ * we don't want do that for every query start so it runs until
+ * catalog_cache_prune_min_age is changed. See UpdateCatCacheClock().
+ */
 static inline void
 SetCatCacheClock(TimestampTz ts)
 {
     catcacheclock = ts;
+
+    if (!catcache_clock_timeout_active && catalog_cache_prune_min_age > 0)
+        SetupCatCacheClockTimer();
 }
 
+extern void assign_catalog_cache_prune_min_age(int newval, void *extra);
 extern void CreateCacheMemoryContext(void);
 
 extern CatCache *InitCatCache(int id, Oid reloid, Oid indexoid,
diff --git a/src/include/utils/timeout.h b/src/include/utils/timeout.h
index 9244a2a7b7..b2d97b4f7b 100644
--- a/src/include/utils/timeout.h
+++ b/src/include/utils/timeout.h
@@ -31,6 +31,7 @@ typedef enum TimeoutId
     STANDBY_TIMEOUT,
     STANDBY_LOCK_TIMEOUT,
     IDLE_IN_TRANSACTION_SESSION_TIMEOUT,
+    CATCACHE_CLOCK_TIMEOUT,
     /* First user-definable timeout reason */
     USER_TIMEOUT,
     /* Maximum number of timeout reasons */
-- 
2.16.3

From 89f64ee52ea4656b8397524d511abbdf793521b9 Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyotaro@lab.ntt.co.jp>
Date: Fri, 1 Mar 2019 12:00:26 +0900
Subject: [PATCH 4/6] Syscache usage tracking feature

Collects syscache usage statictics and show it using the view
pg_stat_syscache. The feature is controlled by the GUC variable
track_syscache_usage_interval.
---
 doc/src/sgml/config.sgml                      |  16 ++
 src/backend/catalog/system_views.sql          |  17 +++
 src/backend/postmaster/pgstat.c               | 201 ++++++++++++++++++++++++--
 src/backend/tcop/postgres.c                   |  23 +++
 src/backend/utils/adt/pgstatfuncs.c           | 133 +++++++++++++++++
 src/backend/utils/cache/catcache.c            | 145 +++++++++++++++----
 src/backend/utils/cache/syscache.c            |  24 +++
 src/backend/utils/init/globals.c              |   1 +
 src/backend/utils/init/postinit.c             |  11 ++
 src/backend/utils/misc/guc.c                  |  10 ++
 src/backend/utils/misc/postgresql.conf.sample |   1 +
 src/include/catalog/pg_proc.dat               |   9 ++
 src/include/miscadmin.h                       |   1 +
 src/include/pgstat.h                          |   4 +
 src/include/utils/catcache.h                  |  13 +-
 src/include/utils/syscache.h                  |  19 +++
 src/include/utils/timeout.h                   |   1 +
 src/test/regress/expected/rules.out           |  24 ++-
 18 files changed, 612 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 737a156bb4..850fe4ea90 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -6689,6 +6689,22 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv;
       </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-track-catalog-cache-usage-interval" xreflabel="track_catalog_cache_usage_interval">
+      <term><varname>track_catalog_cache_usage_interval</varname> (<type>integer</type>)
+      <indexterm>
+       <primary><varname>track_catlog_cache_usage_interval</varname>
+       configuration parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        Specifies the interval to collect catalog cache usage statistics on
+        the session in milliseconds. This parameter is 0 by default, which
+        means disabled.  Only superusers can change this setting.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry id="guc-track-io-timing" xreflabel="track_io_timing">
       <term><varname>track_io_timing</varname> (<type>boolean</type>)
       <indexterm>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 3e229c693c..f5d1aaf96f 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -906,6 +906,22 @@ CREATE VIEW pg_stat_progress_vacuum AS
     FROM pg_stat_get_progress_info('VACUUM') AS S
         LEFT JOIN pg_database D ON S.datid = D.oid;
 
+CREATE VIEW pg_stat_syscache AS
+    SELECT
+        S.pid                AS pid,
+        S.relid::regclass    AS relname,
+        S.indid::regclass    AS cache_name,
+        S.size                AS size,
+        S.ntup                AS ntuples,
+        S.searches            AS searches,
+        S.hits                AS hits,
+        S.neg_hits            AS neg_hits,
+        S.ageclass            AS ageclass,
+        S.last_update        AS last_update
+    FROM pg_stat_activity A
+    JOIN LATERAL (SELECT A.pid, * FROM pg_get_syscache_stats(A.pid)) S
+        ON (A.pid = S.pid);
+
 CREATE VIEW pg_user_mappings AS
     SELECT
         U.oid       AS umid,
@@ -1185,6 +1201,7 @@ GRANT EXECUTE ON FUNCTION pg_ls_waldir() TO pg_monitor;
 GRANT EXECUTE ON FUNCTION pg_ls_archive_statusdir() TO pg_monitor;
 GRANT EXECUTE ON FUNCTION pg_ls_tmpdir() TO pg_monitor;
 GRANT EXECUTE ON FUNCTION pg_ls_tmpdir(oid) TO pg_monitor;
+GRANT EXECUTE ON FUNCTION pg_get_syscache_stats(int) TO pg_monitor;
 
 GRANT pg_read_all_settings TO pg_monitor;
 GRANT pg_read_all_stats TO pg_monitor;
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index 81c6499251..b15a3273ca 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -66,6 +66,7 @@
 #include "utils/ps_status.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
+#include "utils/syscache.h"
 #include "utils/timestamp.h"
 
 
@@ -124,6 +125,7 @@
 bool        pgstat_track_activities = false;
 bool        pgstat_track_counts = false;
 int            pgstat_track_functions = TRACK_FUNC_OFF;
+int            pgstat_track_syscache_usage_interval = 0;
 int            pgstat_track_activity_query_size = 1024;
 
 /* ----------
@@ -236,6 +238,11 @@ typedef struct TwoPhasePgStatRecord
     bool        t_truncated;    /* was the relation truncated? */
 } TwoPhasePgStatRecord;
 
+/* bitmap symbols to specify target file types remove */
+#define PGSTAT_REMFILE_DBSTAT    1        /* remove only database stats files */
+#define PGSTAT_REMFILE_SYSCACHE    2        /* remove only syscache stats files */
+#define PGSTAT_REMFILE_ALL        3        /* remove both type of files */
+
 /*
  * Info about current "snapshot" of stats file
  */
@@ -335,6 +342,7 @@ static void pgstat_recv_funcpurge(PgStat_MsgFuncpurge *msg, int len);
 static void pgstat_recv_recoveryconflict(PgStat_MsgRecoveryConflict *msg, int len);
 static void pgstat_recv_deadlock(PgStat_MsgDeadlock *msg, int len);
 static void pgstat_recv_tempfile(PgStat_MsgTempFile *msg, int len);
+static void pgstat_remove_syscache_statsfile(void);
 
 /* ------------------------------------------------------------
  * Public functions called from postmaster follow
@@ -630,10 +638,13 @@ startup_failed:
 }
 
 /*
- * subroutine for pgstat_reset_all
+ * remove stats files
+ *
+ * clean up stats files in specified directory. target is one of
+ * PGSTAT_REFILE_DBSTAT/SYSCACHE/ALL and restricts files to remove.
  */
 static void
-pgstat_reset_remove_files(const char *directory)
+pgstat_reset_remove_files(const char *directory, int target)
 {
     DIR           *dir;
     struct dirent *entry;
@@ -644,25 +655,39 @@ pgstat_reset_remove_files(const char *directory)
     {
         int            nchars;
         Oid            tmp_oid;
+        int            filetype = 0;
 
         /*
          * Skip directory entries that don't match the file names we write.
          * See get_dbstat_filename for the database-specific pattern.
          */
         if (strncmp(entry->d_name, "global.", 7) == 0)
+        {
+            filetype = PGSTAT_REMFILE_DBSTAT;
             nchars = 7;
+        }
         else
         {
+            char head[2];
+
             nchars = 0;
-            (void) sscanf(entry->d_name, "db_%u.%n",
-                          &tmp_oid, &nchars);
-            if (nchars <= 0)
-                continue;
+            (void) sscanf(entry->d_name, "%c%c_%u.%n",
+                          head, head + 1, &tmp_oid, &nchars);
+
             /* %u allows leading whitespace, so reject that */
-            if (strchr("0123456789", entry->d_name[3]) == NULL)
+            if (nchars < 3 || !isdigit(entry->d_name[3]))
                 continue;
+
+            if  (strncmp(head, "db", 2) == 0)
+                filetype = PGSTAT_REMFILE_DBSTAT;
+            else if (strncmp(head, "cc", 2) == 0)
+                filetype = PGSTAT_REMFILE_SYSCACHE;
         }
 
+        /* skip if this is not a target */
+        if ((filetype & target) == 0)
+            continue;
+
         if (strcmp(entry->d_name + nchars, "tmp") != 0 &&
             strcmp(entry->d_name + nchars, "stat") != 0)
             continue;
@@ -683,8 +708,9 @@ pgstat_reset_remove_files(const char *directory)
 void
 pgstat_reset_all(void)
 {
-    pgstat_reset_remove_files(pgstat_stat_directory);
-    pgstat_reset_remove_files(PGSTAT_STAT_PERMANENT_DIRECTORY);
+    pgstat_reset_remove_files(pgstat_stat_directory, PGSTAT_REMFILE_ALL);
+    pgstat_reset_remove_files(PGSTAT_STAT_PERMANENT_DIRECTORY,
+                              PGSTAT_REMFILE_ALL);
 }
 
 #ifdef EXEC_BACKEND
@@ -2963,6 +2989,10 @@ pgstat_beshutdown_hook(int code, Datum arg)
     if (OidIsValid(MyDatabaseId))
         pgstat_report_stat(true);
 
+    /* clear syscache statistics files and temporary settings */
+    if (MyBackendId != InvalidBackendId)
+        pgstat_remove_syscache_statsfile();
+
     /*
      * Clear my status entry, following the protocol of bumping st_changecount
      * before and after.  We use a volatile pointer here to ensure the
@@ -4287,6 +4317,9 @@ PgstatCollectorMain(int argc, char *argv[])
     pgStatRunningInCollector = true;
     pgStatDBHash = pgstat_read_statsfiles(InvalidOid, true, true);
 
+    /* Remove left-over syscache stats files */
+    pgstat_reset_remove_files(pgstat_stat_directory, PGSTAT_REMFILE_SYSCACHE);
+
     /*
      * Loop to process messages until we get SIGQUIT or detect ungraceful
      * death of our parent postmaster.
@@ -6377,3 +6410,153 @@ pgstat_clip_activity(const char *raw_activity)
 
     return activity;
 }
+
+/*
+ * return the filename for a syscache stat file; filename is the output
+ * buffer, of length len.
+ */
+void
+pgstat_get_syscachestat_filename(bool permanent, bool tempname, int backendid,
+                                 char *filename, int len)
+{
+    int            printed;
+
+    /* NB -- pgstat_reset_remove_files knows about the pattern this uses */
+    printed = snprintf(filename, len, "%s/cc_%u.%s",
+                       permanent ? PGSTAT_STAT_PERMANENT_DIRECTORY :
+                       pgstat_stat_directory,
+                       backendid,
+                       tempname ? "tmp" : "stat");
+    if (printed >= len)
+        elog(ERROR, "overlength pgstat path");
+}
+
+/* removes syscache stats files of this backend */
+static void
+pgstat_remove_syscache_statsfile(void)
+{
+    char    fname[MAXPGPATH];
+
+    pgstat_get_syscachestat_filename(false, false, MyBackendId,
+                                     fname, MAXPGPATH);
+    unlink(fname);        /* don't care of the result */
+}
+
+/*
+ * pgstat_write_syscache_stats() -
+ *        Write the syscache statistics files.
+ *
+ * If 'force' is false, this function skips writing a file and returns the
+ * time remaining in the current interval in milliseconds. If 'force' is true,
+ * writes a file regardless of the remaining time and reset the interval.
+ */
+long
+pgstat_write_syscache_stats(bool force)
+{
+    static TimestampTz last_report = 0;
+    TimestampTz now;
+    long elapsed;
+    long secs;
+    int     usecs;
+    int    cacheId;
+    FILE    *fpout;
+    char    statfile[MAXPGPATH];
+    char    tmpfile[MAXPGPATH];
+
+    /* Return if we don't want it */
+    if (!force && pgstat_track_syscache_usage_interval <= 0)
+    {
+        /* disabled. remove the statistics file if any */
+        if (last_report > 0)
+        {
+            last_report = 0;
+            pgstat_remove_syscache_statsfile();
+        }
+        return 0;
+    }
+
+    /* Check against the interval */
+    now = GetCurrentTransactionStopTimestamp();
+    TimestampDifference(last_report, now, &secs, &usecs);
+    elapsed = secs * 1000 + usecs / 1000;
+
+    if (!force && elapsed < pgstat_track_syscache_usage_interval)
+    {
+        /* not yet the time, inform the remaining time to the caller */
+        return pgstat_track_syscache_usage_interval - elapsed;
+    }
+
+    /* now update the stats */
+    last_report = now;
+
+    pgstat_get_syscachestat_filename(false, true,
+                                     MyBackendId, tmpfile, MAXPGPATH);
+    pgstat_get_syscachestat_filename(false, false,
+                                     MyBackendId, statfile, MAXPGPATH);
+
+    /*
+     * This function can be called from ProcessInterrupts(). Inhibit recursive
+     * interrupts to avoid recursive entry.
+     */
+    HOLD_INTERRUPTS();
+
+    fpout = AllocateFile(tmpfile, PG_BINARY_W);
+    if (fpout == NULL)
+    {
+        ereport(LOG,
+                (errcode_for_file_access(),
+                 errmsg("could not open temporary statistics file \"%s\": %m",
+                        tmpfile)));
+        /*
+         * Failure writing this file is not critical. Just skip this time and
+         * tell caller to wait for the next interval.
+         */
+        RESUME_INTERRUPTS();
+        return pgstat_track_syscache_usage_interval;
+    }
+
+    /* write out every catcache stats */
+    for (cacheId = 0 ; cacheId < SysCacheSize ; cacheId++)
+    {
+        SysCacheStats *stats;
+
+        stats = SysCacheGetStats(cacheId);
+        Assert (stats);
+
+        /* write error is checked later using ferror() */
+        fputc('T', fpout);
+        (void)fwrite(&cacheId, sizeof(int), 1, fpout);
+        (void)fwrite(&last_report, sizeof(TimestampTz), 1, fpout);
+        (void)fwrite(stats, sizeof(*stats), 1, fpout);
+    }
+    fputc('E', fpout);
+
+    if (ferror(fpout))
+    {
+        ereport(LOG,
+                (errcode_for_file_access(),
+                 errmsg("could not write syscache statistics file \"%s\": %m",
+                        tmpfile)));
+        FreeFile(fpout);
+        unlink(tmpfile);
+    }
+    else if (FreeFile(fpout) < 0)
+    {
+        ereport(LOG,
+                (errcode_for_file_access(),
+                 errmsg("could not close syscache statistics file \"%s\": %m",
+                        tmpfile)));
+        unlink(tmpfile);
+    }
+    else if (rename(tmpfile, statfile) < 0)
+    {
+        ereport(LOG,
+                (errcode_for_file_access(),
+                 errmsg("could not rename syscache statistics file \"%s\" to \"%s\": %m",
+                        tmpfile, statfile)));
+        unlink(tmpfile);
+    }
+
+    RESUME_INTERRUPTS();
+    return 0;
+}
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index d9a54ed37f..39abb9fbab 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -3159,6 +3159,12 @@ ProcessInterrupts(void)
 
     }
 
+    if (IdleSyscacheStatsUpdateTimeoutPending)
+    {
+        IdleSyscacheStatsUpdateTimeoutPending = false;
+        pgstat_write_syscache_stats(true);
+    }
+
     if (ParallelMessagePending)
         HandleParallelMessages();
 
@@ -3743,6 +3749,7 @@ PostgresMain(int argc, char *argv[],
     sigjmp_buf    local_sigjmp_buf;
     volatile bool send_ready_for_query = true;
     bool        disable_idle_in_transaction_timeout = false;
+    bool        disable_idle_syscache_update_timeout = false;
 
     /* Initialize startup process environment if necessary. */
     if (!IsUnderPostmaster)
@@ -4186,9 +4193,19 @@ PostgresMain(int argc, char *argv[],
             }
             else
             {
+                long timeout;
+
                 ProcessCompletedNotifies();
                 pgstat_report_stat(false);
 
+                timeout = pgstat_write_syscache_stats(false);
+
+                if (timeout > 0)
+                {
+                    disable_idle_syscache_update_timeout = true;
+                    enable_timeout_after(IDLE_SYSCACHE_STATS_UPDATE_TIMEOUT,
+                                         timeout);
+                }
                 set_ps_display("idle", false);
                 pgstat_report_activity(STATE_IDLE, NULL);
             }
@@ -4231,6 +4248,12 @@ PostgresMain(int argc, char *argv[],
             disable_idle_in_transaction_timeout = false;
         }
 
+        if (disable_idle_syscache_update_timeout)
+        {
+            disable_timeout(IDLE_SYSCACHE_STATS_UPDATE_TIMEOUT, false);
+            disable_idle_syscache_update_timeout = false;
+        }
+
         /*
          * (6) check for any other interesting events that happened while we
          * slept.
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 69f7265779..26f923a66b 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -14,6 +14,8 @@
  */
 #include "postgres.h"
 
+#include <sys/stat.h>
+
 #include "access/htup_details.h"
 #include "catalog/pg_authid.h"
 #include "catalog/pg_type.h"
@@ -28,6 +30,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/inet.h"
+#include "utils/syscache.h"
 #include "utils/timestamp.h"
 
 #define UINT32_ACCESS_ONCE(var)         ((uint32)(*((volatile uint32 *)&(var))))
@@ -1908,3 +1911,133 @@ pg_stat_get_archiver(PG_FUNCTION_ARGS)
     PG_RETURN_DATUM(HeapTupleGetDatum(
                                       heap_form_tuple(tupdesc, values, nulls)));
 }
+
+Datum
+pgstat_get_syscache_stats(PG_FUNCTION_ARGS)
+{
+#define PG_GET_SYSCACHE_SIZE 9
+    int                    pid     = PG_GETARG_INT32(0);
+    ReturnSetInfo       *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+    TupleDesc            tupdesc;
+    Tuplestorestate    *tupstore;
+    MemoryContext        per_query_ctx;
+    MemoryContext        oldcontext;
+    PgBackendStatus       *beentry;
+    int                    beid;
+    char                fname[MAXPGPATH];
+    FILE                  *fpin;
+    char c;
+
+    if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+        ereport(ERROR,
+                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                 errmsg("set-valued function called in context that cannot accept a set")));
+    if (!(rsinfo->allowedModes & SFRM_Materialize))
+        ereport(ERROR,
+                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                 errmsg("materialize mode required, but it is not " \
+                        "allowed in this context")));
+
+    /* Build a tuple descriptor for our result type */
+    if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+        elog(ERROR, "return type must be a row type");
+
+    per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
+
+    oldcontext = MemoryContextSwitchTo(per_query_ctx);
+    tupstore = tuplestore_begin_heap(true, false, work_mem);
+    rsinfo->returnMode = SFRM_Materialize;
+    rsinfo->setResult = tupstore;
+    rsinfo->setDesc = tupdesc;
+
+    MemoryContextSwitchTo(oldcontext);
+
+    /* find beentry for given pid*/
+    beentry = NULL;
+    for (beid = 1;
+         (beentry = pgstat_fetch_stat_beentry(beid)) &&
+             beentry->st_procpid != pid ;
+         beid++);
+
+    /*
+     * we silently return empty result on failure or insufficient privileges
+     */
+    if (!beentry ||
+        (!has_privs_of_role(GetUserId(), beentry->st_userid) &&
+         !is_member_of_role(GetUserId(), DEFAULT_ROLE_READ_ALL_STATS)))
+        goto no_data;
+
+    pgstat_get_syscachestat_filename(false, false, beid, fname, MAXPGPATH);
+
+    if ((fpin = AllocateFile(fname, PG_BINARY_R)) == NULL)
+    {
+        if (errno != ENOENT)
+            ereport(WARNING,
+                    (errcode_for_file_access(),
+                     errmsg("could not open statistics file \"%s\": %m",
+                            fname)));
+        /* also return empty on no statistics file */
+        goto no_data;
+    }
+
+    /* read the statistics file into tuplestore */
+    while ((c = fgetc(fpin)) == 'T')
+    {
+        TimestampTz last_update;
+        SysCacheStats stats;
+        int cacheid;
+        Datum values[PG_GET_SYSCACHE_SIZE];
+        bool nulls[PG_GET_SYSCACHE_SIZE] = {0};
+        Datum datums[SYSCACHE_STATS_NAGECLASSES * 2];
+        bool arrnulls[SYSCACHE_STATS_NAGECLASSES * 2] = {0};
+        int    dims[] = {SYSCACHE_STATS_NAGECLASSES, 2};
+        int lbs[] = {1, 1};
+        ArrayType *arr;
+        int i, j;
+
+        if (fread(&cacheid, sizeof(int), 1, fpin) != 1 ||
+            fread(&last_update, sizeof(TimestampTz), 1, fpin) != 1 ||
+            fread(&stats, 1, sizeof(stats), fpin) != sizeof(stats))
+        {
+            ereport(WARNING,
+                    (errmsg("corrupted syscache statistics file \"%s\"",
+                            fname)));
+            goto no_data;
+        }
+
+        i = 0;
+        values[i++] = ObjectIdGetDatum(stats.reloid);
+        values[i++] = ObjectIdGetDatum(stats.indoid);
+        values[i++] = Int64GetDatum(stats.size);
+        values[i++] = Int64GetDatum(stats.ntuples);
+        values[i++] = Int64GetDatum(stats.nsearches);
+        values[i++] = Int64GetDatum(stats.nhits);
+        values[i++] = Int64GetDatum(stats.nneg_hits);
+
+        for (j = 0 ; j < SYSCACHE_STATS_NAGECLASSES ; j++)
+        {
+            datums[j * 2] = Int32GetDatum((int32) stats.ageclasses[j]);
+            datums[j * 2 + 1] = Int32GetDatum((int32) stats.nclass_entries[j]);
+        }
+
+        arr = construct_md_array(datums, arrnulls, 2, dims, lbs,
+                              INT4OID, sizeof(int32), true, 'i');
+        values[i++] = PointerGetDatum(arr);
+
+        values[i++] = TimestampTzGetDatum(last_update);
+
+        Assert (i == PG_GET_SYSCACHE_SIZE);
+
+        tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+    }
+
+    /* check for the end of file. abandon the result if file is broken */
+    if (c != 'E' || fgetc(fpin) != EOF)
+        tuplestore_clear(tupstore);
+
+    FreeFile(fpin);
+
+no_data:
+    tuplestore_donestoring(tupstore);
+    return (Datum) 0;
+}
diff --git a/src/backend/utils/cache/catcache.c b/src/backend/utils/cache/catcache.c
index e0ecfe09d4..63c0ea3b17 100644
--- a/src/backend/utils/cache/catcache.c
+++ b/src/backend/utils/cache/catcache.c
@@ -85,6 +85,10 @@ static CatCacheHeader *CacheHdr = NULL;
 /* Clock for the last accessed time of a catcache entry. */
 TimestampTz    catcacheclock = 0;
 
+/* age classes for pruning */
+static double ageclass[SYSCACHE_STATS_NAGECLASSES]
+    = {0.05, 0.1, 1.0, 2.0, 3.0, 0.0};
+
 static inline HeapTuple SearchCatCacheInternal(CatCache *cache,
                        int nkeys,
                        Datum v1, Datum v2,
@@ -118,7 +122,7 @@ static CatCTup *CatalogCacheCreateEntry(CatCache *cache, HeapTuple ntp,
 
 static void CatCacheFreeKeys(TupleDesc tupdesc, int nkeys, int *attnos,
                  Datum *keys);
-static void CatCacheCopyKeys(TupleDesc tupdesc, int nkeys, int *attnos,
+static int  CatCacheCopyKeys(TupleDesc tupdesc, int nkeys, int *attnos,
                  Datum *srckeys, Datum *dstkeys);
 
 
@@ -500,6 +504,7 @@ CatCacheRemoveCTup(CatCache *cache, CatCTup *ct)
         CatCacheFreeKeys(cache->cc_tupdesc, cache->cc_nkeys,
                          cache->cc_keyno, ct->keys);
 
+    cache->cc_memusage -= ct->size;
     pfree(ct);
 
     --cache->cc_ntup;
@@ -613,9 +618,7 @@ CatCacheInvalidate(CatCache *cache, uint32 hashValue)
             else
                 CatCacheRemoveCTup(cache, ct);
             CACHE_elog(DEBUG2, "CatCacheInvalidate: invalidated");
-#ifdef CATCACHE_STATS
             cache->cc_invals++;
-#endif
             /* could be multiple matches, so keep looking! */
         }
     }
@@ -691,9 +694,7 @@ ResetCatalogCache(CatCache *cache)
             }
             else
                 CatCacheRemoveCTup(cache, ct);
-#ifdef CATCACHE_STATS
             cache->cc_invals++;
-#endif
         }
     }
 }
@@ -833,7 +834,12 @@ InitCatCache(int id,
      */
     sz = sizeof(CatCache) + PG_CACHE_LINE_SIZE;
     cp = (CatCache *) CACHELINEALIGN(palloc0(sz));
-    cp->cc_bucket = palloc0(nbuckets * sizeof(dlist_head));
+    cp->cc_head_alloc_size = sz;
+    sz = nbuckets * sizeof(dlist_head);
+    cp->cc_bucket = palloc0(sz);
+
+    /* cc_head_alloc_size + consumed size for cc_bucket */
+    cp->cc_memusage = cp->cc_head_alloc_size + sz;
 
     /*
      * initialize the cache's relation information for the relation
@@ -1011,13 +1017,17 @@ RehashCatCache(CatCache *cp)
     dlist_head *newbucket;
     int            newnbuckets;
     int            i;
+    size_t        sz;
 
     elog(DEBUG1, "rehashing catalog cache id %d for %s; %d tups, %d buckets",
          cp->id, cp->cc_relname, cp->cc_ntup, cp->cc_nbuckets);
 
     /* Allocate a new, larger, hash table. */
     newnbuckets = cp->cc_nbuckets * 2;
-    newbucket = (dlist_head *) MemoryContextAllocZero(CacheMemoryContext, newnbuckets * sizeof(dlist_head));
+    sz = newnbuckets * sizeof(dlist_head);
+    newbucket = (dlist_head *) MemoryContextAllocZero(CacheMemoryContext, sz);
+
+    cp->cc_memusage = cp->cc_head_alloc_size + sz;
 
     /* Move all entries from old hash table to new. */
     for (i = 0; i < cp->cc_nbuckets; i++)
@@ -1031,6 +1041,7 @@ RehashCatCache(CatCache *cp)
 
             dlist_delete(iter.cur);
             dlist_push_head(&newbucket[hashIndex], &ct->cache_elem);
+            cp->cc_memusage += ct->size;
         }
     }
 
@@ -1369,9 +1380,7 @@ SearchCatCacheInternal(CatCache *cache,
     if (unlikely(cache->cc_tupdesc == NULL))
         CatalogCacheInitializeCache(cache);
 
-#ifdef CATCACHE_STATS
     cache->cc_searches++;
-#endif
 
     /* Initialize local parameter array */
     arguments[0] = v1;
@@ -1440,9 +1449,7 @@ SearchCatCacheInternal(CatCache *cache,
             CACHE_elog(DEBUG2, "SearchCatCache(%s): found in bucket %d",
                        cache->cc_relname, hashIndex);
 
-#ifdef CATCACHE_STATS
             cache->cc_hits++;
-#endif
 
             return &ct->tuple;
         }
@@ -1451,9 +1458,7 @@ SearchCatCacheInternal(CatCache *cache,
             CACHE_elog(DEBUG2, "SearchCatCache(%s): found neg entry in bucket %d",
                        cache->cc_relname, hashIndex);
 
-#ifdef CATCACHE_STATS
             cache->cc_neg_hits++;
-#endif
 
             return NULL;
         }
@@ -1581,9 +1586,7 @@ SearchCatCacheMiss(CatCache *cache,
     CACHE_elog(DEBUG2, "SearchCatCache(%s): put in bucket %d",
                cache->cc_relname, hashIndex);
 
-#ifdef CATCACHE_STATS
     cache->cc_newloads++;
-#endif
 
     return &ct->tuple;
 }
@@ -1694,9 +1697,7 @@ SearchCatCacheList(CatCache *cache,
 
     Assert(nkeys > 0 && nkeys < cache->cc_nkeys);
 
-#ifdef CATCACHE_STATS
     cache->cc_lsearches++;
-#endif
 
     /* Initialize local parameter array */
     arguments[0] = v1;
@@ -1753,9 +1754,7 @@ SearchCatCacheList(CatCache *cache,
         CACHE_elog(DEBUG2, "SearchCatCacheList(%s): found list",
                    cache->cc_relname);
 
-#ifdef CATCACHE_STATS
         cache->cc_lhits++;
-#endif
 
         return cl;
     }
@@ -1862,6 +1861,11 @@ SearchCatCacheList(CatCache *cache,
         /* Now we can build the CatCList entry. */
         oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
         nmembers = list_length(ctlist);
+
+        /*
+         * Don't waste a time by counting the list's memory usage, since it
+         * doesn't live a long life.
+         */
         cl = (CatCList *)
             palloc(offsetof(CatCList, members) + nmembers * sizeof(CatCTup *));
 
@@ -1972,6 +1976,7 @@ CatalogCacheCreateEntry(CatCache *cache, HeapTuple ntp, Datum *arguments,
     CatCTup    *ct;
     HeapTuple    dtp;
     MemoryContext oldcxt;
+    int            tupsize;
 
     /* negative entries have no tuple associated */
     if (ntp)
@@ -1995,8 +2000,8 @@ CatalogCacheCreateEntry(CatCache *cache, HeapTuple ntp, Datum *arguments,
         /* Allocate memory for CatCTup and the cached tuple in one go */
         oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
 
-        ct = (CatCTup *) palloc(sizeof(CatCTup) +
-                                MAXIMUM_ALIGNOF + dtp->t_len);
+        tupsize = sizeof(CatCTup) +    MAXIMUM_ALIGNOF + dtp->t_len;
+        ct = (CatCTup *) palloc(tupsize);
         ct->tuple.t_len = dtp->t_len;
         ct->tuple.t_self = dtp->t_self;
         ct->tuple.t_tableOid = dtp->t_tableOid;
@@ -2029,14 +2034,16 @@ CatalogCacheCreateEntry(CatCache *cache, HeapTuple ntp, Datum *arguments,
     {
         Assert(negative);
         oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
-        ct = (CatCTup *) palloc(sizeof(CatCTup));
+        tupsize = sizeof(CatCTup);
+        ct = (CatCTup *) palloc(tupsize);
 
         /*
          * Store keys - they'll point into separately allocated memory if not
          * by-value.
          */
-        CatCacheCopyKeys(cache->cc_tupdesc, cache->cc_nkeys, cache->cc_keyno,
-                         arguments, ct->keys);
+        tupsize +=
+            CatCacheCopyKeys(cache->cc_tupdesc, cache->cc_nkeys,
+                             cache->cc_keyno, arguments, ct->keys);
         MemoryContextSwitchTo(oldcxt);
     }
 
@@ -2060,7 +2067,10 @@ CatalogCacheCreateEntry(CatCache *cache, HeapTuple ntp, Datum *arguments,
     cache->cc_ntup++;
     CacheHdr->ch_ntup++;
 
-    /* increase refcount so that the new entry survives pruning */
+    ct->size = tupsize;
+    cache->cc_memusage += ct->size;
+
+    /* increase refcount so that this survives pruning */
     ct->refcount++;
 
     /*
@@ -2103,13 +2113,14 @@ CatCacheFreeKeys(TupleDesc tupdesc, int nkeys, int *attnos, Datum *keys)
 /*
  * Helper routine that copies the keys in the srckeys array into the dstkeys
  * one, guaranteeing that the datums are fully allocated in the current memory
- * context.
+ * context. Returns allocated memory size.
  */
-static void
+static int
 CatCacheCopyKeys(TupleDesc tupdesc, int nkeys, int *attnos,
                  Datum *srckeys, Datum *dstkeys)
 {
     int            i;
+    int            size = 0;
 
     /*
      * XXX: memory and lookup performance could possibly be improved by
@@ -2138,8 +2149,25 @@ CatCacheCopyKeys(TupleDesc tupdesc, int nkeys, int *attnos,
         dstkeys[i] = datumCopy(src,
                                att->attbyval,
                                att->attlen);
+
+        /* calculate rough estimate memory usage by datumCopy */
+        if (!att->attbyval)
+        {
+            if (att->attlen == -1)
+            {
+                struct varlena *vl = (struct varlena *) DatumGetPointer(src);
+                
+                if (VARATT_IS_EXTERNAL_EXPANDED(vl))
+                    size += EOH_get_flat_size(DatumGetEOHP(src));
+                else
+                    size += VARSIZE_ANY(vl);
+            }
+            else
+                size += datumGetSize(src, att->attbyval, att->attlen);
+        }
     }
 
+    return size;
 }
 
 /*
@@ -2263,3 +2291,66 @@ PrintCatCacheListLeakWarning(CatCList *list)
          list->my_cache->cc_relname, list->my_cache->id,
          list, list->refcount);
 }
+
+/*
+ * CatCacheGetStats - fill in SysCacheStats struct.
+ *
+ * This is a support routine for SysCacheGetStats, substantially fills in the
+ * result. The classification here is based on the same criteria to
+ * CatCacheCleanupOldEntries().
+ */
+void
+CatCacheGetStats(CatCache *cache, SysCacheStats *stats)
+{
+    int    i, j;
+
+    Assert(ageclass[SYSCACHE_STATS_NAGECLASSES - 1] == 0.0);
+
+    /* fill in the stats struct */
+    stats->size = cache->cc_memusage;
+    stats->ntuples = cache->cc_ntup;
+    stats->nsearches = cache->cc_searches;
+    stats->nhits = cache->cc_hits;
+    stats->nneg_hits = cache->cc_neg_hits;
+
+    /*
+     * catalog_cache_prune_min_age can be changed on-session, fill it every
+     * time
+     */
+    for (i = 0 ; i < SYSCACHE_STATS_NAGECLASSES ; i++)
+        stats->ageclasses[i] =
+            (int) (catalog_cache_prune_min_age * ageclass[i]);
+
+    /*
+     * nth element in nclass_entries stores the number of cache entries that
+     * have lived unaccessed for corresponding multiple in ageclass of
+     * catalog_cache_prune_min_age.
+     */
+    memset(stats->nclass_entries, 0, sizeof(int) * SYSCACHE_STATS_NAGECLASSES);
+
+    /* Scan the whole hash */
+    for (i = 0; i < cache->cc_nbuckets; i++)
+    {
+        dlist_mutable_iter iter;
+
+        dlist_foreach_modify(iter, &cache->cc_bucket[i])
+        {
+            CatCTup    *ct = dlist_container(CatCTup, cache_elem, iter.cur);
+            long entry_age;
+            int us;
+
+            /*
+             * Calculate the duration from the time from the last access to
+             * the "current" time. See CatCacheCleanupOldEntries for details.
+             */
+            TimestampDifference(ct->lastaccess, catcacheclock, &entry_age, &us);
+
+            j = 0;
+            while (j < SYSCACHE_STATS_NAGECLASSES - 1 &&
+                   entry_age > stats->ageclasses[j])
+                j++;
+
+            stats->nclass_entries[j]++;
+        }
+    }
+}
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index ac98c19155..7b38a06708 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -20,6 +20,9 @@
  */
 #include "postgres.h"
 
+#include <sys/stat.h>
+#include <unistd.h>
+
 #include "access/htup_details.h"
 #include "access/sysattr.h"
 #include "catalog/indexing.h"
@@ -1534,6 +1537,27 @@ RelationSupportsSysCache(Oid relid)
     return false;
 }
 
+/*
+ * SysCacheGetStats - returns stats of specified syscache
+ *
+ * This routine returns the address of its local static memory.
+ */
+SysCacheStats *
+SysCacheGetStats(int cacheId)
+{
+    static SysCacheStats stats;
+
+    Assert(cacheId >=0 && cacheId < SysCacheSize);
+
+    memset(&stats, 0, sizeof(stats));
+
+    stats.reloid = cacheinfo[cacheId].reloid;
+    stats.indoid = cacheinfo[cacheId].indoid;
+
+    CatCacheGetStats(SysCache[cacheId], &stats);
+
+    return &stats;
+}
 
 /*
  * OID comparator for pg_qsort
diff --git a/src/backend/utils/init/globals.c b/src/backend/utils/init/globals.c
index 0e8b972a29..b7c647b5e0 100644
--- a/src/backend/utils/init/globals.c
+++ b/src/backend/utils/init/globals.c
@@ -33,6 +33,7 @@ volatile sig_atomic_t ProcDiePending = false;
 volatile sig_atomic_t ClientConnectionLost = false;
 volatile sig_atomic_t IdleInTransactionSessionTimeoutPending = false;
 volatile sig_atomic_t CatcacheClockTimeoutPending = false;
+volatile sig_atomic_t IdleSyscacheStatsUpdateTimeoutPending = false;
 volatile sig_atomic_t ConfigReloadPending = false;
 volatile uint32 InterruptHoldoffCount = 0;
 volatile uint32 QueryCancelHoldoffCount = 0;
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index eb17103595..f2f879b6d8 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -73,6 +73,7 @@ static void StatementTimeoutHandler(void);
 static void LockTimeoutHandler(void);
 static void IdleInTransactionSessionTimeoutHandler(void);
 static void CatcacheClockTimeoutHandler(void);
+static void IdleSyscacheStatsUpdateTimeoutHandler(void);
 static bool ThereIsAtLeastOneRole(void);
 static void process_startup_options(Port *port, bool am_superuser);
 static void process_settings(Oid databaseid, Oid roleid);
@@ -631,6 +632,8 @@ InitPostgres(const char *in_dbname, Oid dboid, const char *username,
                         IdleInTransactionSessionTimeoutHandler);
         RegisterTimeout(CATCACHE_CLOCK_TIMEOUT,
                         CatcacheClockTimeoutHandler);
+        RegisterTimeout(IDLE_SYSCACHE_STATS_UPDATE_TIMEOUT,
+                        IdleSyscacheStatsUpdateTimeoutHandler);
     }
 
     /*
@@ -1252,6 +1255,14 @@ CatcacheClockTimeoutHandler(void)
     SetLatch(MyLatch);
 }
 
+static void
+IdleSyscacheStatsUpdateTimeoutHandler(void)
+{
+    IdleSyscacheStatsUpdateTimeoutPending = true;
+    InterruptPending = true;
+    SetLatch(MyLatch);
+}
+
 /*
  * Returns true if at least one role is defined in this database cluster.
  */
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 0bdea0c383..5c8c9146d1 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -3158,6 +3158,16 @@ static struct config_int ConfigureNamesInt[] =
         NULL, NULL, NULL
     },
 
+    {
+        {"track_catalog_cache_usage_interval", PGC_SUSET, STATS_COLLECTOR,
+            gettext_noop("Sets the interval between syscache usage collection, in milliseconds. Zero disables syscache
usagetracking."), 
+            NULL
+        },
+        &pgstat_track_syscache_usage_interval,
+        0, 0, INT_MAX / 2,
+        NULL, NULL, NULL
+    },
+
     {
         {"gin_pending_list_limit", PGC_USERSET, CLIENT_CONN_STATEMENT,
             gettext_noop("Sets the maximum size of the pending list for GIN index."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index e9e3acc903..4d39daced6 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -555,6 +555,7 @@
 #track_io_timing = off
 #track_functions = none            # none, pl, all
 #track_activity_query_size = 1024    # (change requires restart)
+#track_catlog_cache_usage_interval = 0    # zero disables tracking
 #stats_temp_directory = 'pg_stat_tmp'
 
 
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index a4e173b484..1a67c4219f 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -9689,6 +9689,15 @@
   proargmodes => '{o,o,o,o,o,o,o,o,o,o,o}',
   proargnames =>
'{slot_name,plugin,slot_type,datoid,temporary,active,active_pid,xmin,catalog_xmin,restart_lsn,confirmed_flush_lsn}',
   prosrc => 'pg_get_replication_slots' },
+{ oid => '3425',
+  descr => 'syscache statistics',
+  proname => 'pg_get_syscache_stats', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 'v', prorettype => 'record',
+  proargtypes => 'int4',
+  proallargtypes => '{int4,oid,oid,int8,int8,int8,int8,int8,_int4,timestamptz}',
+  proargmodes => '{i,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{pid,relid,indid,size,ntup,searches,hits,neg_hits,ageclass,last_update}',
+  prosrc => 'pgstat_get_syscache_stats' },
 { oid => '3786', descr => 'set up a logical replication slot',
   proname => 'pg_create_logical_replication_slot', provolatile => 'v',
   proparallel => 'u', prorettype => 'record', proargtypes => 'name name bool',
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 33b800e80f..767c94a63c 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -83,6 +83,7 @@ extern PGDLLIMPORT volatile sig_atomic_t QueryCancelPending;
 extern PGDLLIMPORT volatile sig_atomic_t ProcDiePending;
 extern PGDLLIMPORT volatile sig_atomic_t IdleInTransactionSessionTimeoutPending;
 extern PGDLLIMPORT volatile sig_atomic_t CatcacheClockTimeoutPending;
+extern PGDLLIMPORT volatile sig_atomic_t IdleSyscacheStatsUpdateTimeoutPending;
 extern PGDLLIMPORT volatile sig_atomic_t ConfigReloadPending;
 
 extern PGDLLIMPORT volatile sig_atomic_t ClientConnectionLost;
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 88a75fb798..c90ee1a064 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -1144,6 +1144,7 @@ extern bool pgstat_track_activities;
 extern bool pgstat_track_counts;
 extern int    pgstat_track_functions;
 extern PGDLLIMPORT int pgstat_track_activity_query_size;
+extern int    pgstat_track_syscache_usage_interval;
 extern char *pgstat_stat_directory;
 extern char *pgstat_stat_tmpname;
 extern char *pgstat_stat_filename;
@@ -1228,6 +1229,8 @@ extern PgStat_BackendFunctionEntry *find_funcstat_entry(Oid func_id);
 extern void pgstat_initstats(Relation rel);
 
 extern char *pgstat_clip_activity(const char *raw_activity);
+extern void pgstat_get_syscachestat_filename(bool permanent,
+                    bool tempname, int backendid, char *filename, int len);
 
 /* ----------
  * pgstat_report_wait_start() -
@@ -1363,5 +1366,6 @@ extern PgStat_StatFuncEntry *pgstat_fetch_stat_funcentry(Oid funcid);
 extern int    pgstat_fetch_stat_numbackends(void);
 extern PgStat_ArchiverStats *pgstat_fetch_stat_archiver(void);
 extern PgStat_GlobalStats *pgstat_fetch_global(void);
+extern long pgstat_write_syscache_stats(bool force);
 
 #endif                            /* PGSTAT_H */
diff --git a/src/include/utils/catcache.h b/src/include/utils/catcache.h
index 5141f57bac..310aeaeab5 100644
--- a/src/include/utils/catcache.h
+++ b/src/include/utils/catcache.h
@@ -63,12 +63,13 @@ typedef struct catcache
     ScanKeyData cc_skey[CATCACHE_MAXKEYS];    /* precomputed key info for heap
                                              * scans */
     dlist_head    cc_lru_list;
+    int            cc_head_alloc_size;/* consumed memory to allocate this struct */
+    int            cc_memusage;    /* memory usage of this catcache (excluding
+                                 * header part) */
 
     /*
-     * Keep these at the end, so that compiling catcache.c with CATCACHE_STATS
-     * doesn't break ABI for other modules
+     * Statistics entries
      */
-#ifdef CATCACHE_STATS
     long        cc_searches;    /* total # searches against this cache */
     long        cc_hits;        /* # of matches against existing entry */
     long        cc_neg_hits;    /* # of matches against negative entry */
@@ -81,7 +82,6 @@ typedef struct catcache
     long        cc_invals;        /* # of entries invalidated from cache */
     long        cc_lsearches;    /* total # list-searches */
     long        cc_lhits;        /* # of matches against existing lists */
-#endif
 } CatCache;
 
 
@@ -124,6 +124,7 @@ typedef struct catctup
     int            naccess;        /* # of access to this entry, up to 2  */
     TimestampTz    lastaccess;        /* timestamp of the last usage */
     dlist_node    lru_node;        /* LRU node */
+    int            size;            /* palloc'ed size off this tuple */
 
     /*
      * The tuple may also be a member of at most one CatCList.  (If a single
@@ -267,4 +268,8 @@ extern void PrepareToInvalidateCacheTuple(Relation relation,
 extern void PrintCatCacheLeakWarning(HeapTuple tuple);
 extern void PrintCatCacheListLeakWarning(CatCList *list);
 
+/* defined in syscache.h */
+typedef struct syscachestats SysCacheStats;
+extern void CatCacheGetStats(CatCache *cache, SysCacheStats *syscachestats);
+
 #endif                            /* CATCACHE_H */
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index 95ee48954e..71b399c902 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -112,6 +112,24 @@ enum SysCacheIdentifier
 #define SysCacheSize (USERMAPPINGUSERSERVER + 1)
 };
 
+#define SYSCACHE_STATS_NAGECLASSES 6
+/* Struct for catcache tracking information */
+typedef struct syscachestats
+{
+    Oid        reloid;            /* target relation */
+    Oid        indoid;            /* index */
+    size_t    size;            /* size of the catcache */
+    int        ntuples;        /* number of tuples resides in the catcache */
+    int        nsearches;        /* number of searches */
+    int        nhits;            /* number of cache hits */
+    int        nneg_hits;        /* number of negative cache hits */
+    /* age classes in seconds */
+    int        ageclasses[SYSCACHE_STATS_NAGECLASSES];
+    /* number of tuples fall into the corresponding age class */
+    int        nclass_entries[SYSCACHE_STATS_NAGECLASSES];
+} SysCacheStats;
+
+
 extern void InitCatalogCache(void);
 extern void InitCatalogCachePhase2(void);
 
@@ -164,6 +182,7 @@ extern void SysCacheInvalidate(int cacheId, uint32 hashValue);
 extern bool RelationInvalidatesSnapshotsOnly(Oid relid);
 extern bool RelationHasSysCache(Oid relid);
 extern bool RelationSupportsSysCache(Oid relid);
+extern SysCacheStats *SysCacheGetStats(int cacheId);
 
 /*
  * The use of the macros below rather than direct calls to the corresponding
diff --git a/src/include/utils/timeout.h b/src/include/utils/timeout.h
index b2d97b4f7b..0677978923 100644
--- a/src/include/utils/timeout.h
+++ b/src/include/utils/timeout.h
@@ -32,6 +32,7 @@ typedef enum TimeoutId
     STANDBY_LOCK_TIMEOUT,
     IDLE_IN_TRANSACTION_SESSION_TIMEOUT,
     CATCACHE_CLOCK_TIMEOUT,
+    IDLE_SYSCACHE_STATS_UPDATE_TIMEOUT,
     /* First user-definable timeout reason */
     USER_TIMEOUT,
     /* Maximum number of timeout reasons */
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 98f417cb57..cf404a3930 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1929,6 +1929,28 @@ pg_stat_sys_tables| SELECT pg_stat_all_tables.relid,
     pg_stat_all_tables.autoanalyze_count
    FROM pg_stat_all_tables
   WHERE ((pg_stat_all_tables.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR
(pg_stat_all_tables.schemaname~ '^pg_toast'::text));
 
+pg_stat_syscache| SELECT s.pid,
+    (s.relid)::regclass AS relname,
+    (s.indid)::regclass AS cache_name,
+    s.size,
+    s.ntup AS ntuples,
+    s.searches,
+    s.hits,
+    s.neg_hits,
+    s.ageclass,
+    s.last_update
+   FROM (pg_stat_activity a
+     JOIN LATERAL ( SELECT a.pid,
+            pg_get_syscache_stats.relid,
+            pg_get_syscache_stats.indid,
+            pg_get_syscache_stats.size,
+            pg_get_syscache_stats.ntup,
+            pg_get_syscache_stats.searches,
+            pg_get_syscache_stats.hits,
+            pg_get_syscache_stats.neg_hits,
+            pg_get_syscache_stats.ageclass,
+            pg_get_syscache_stats.last_update
+           FROM pg_get_syscache_stats(a.pid) pg_get_syscache_stats(relid, indid, size, ntup, searches, hits, neg_hits,
ageclass,last_update)) s ON ((a.pid = s.pid)));
 
 pg_stat_user_functions| SELECT p.oid AS funcid,
     n.nspname AS schemaname,
     p.proname AS funcname,
@@ -2360,7 +2382,7 @@ pg_settings|pg_settings_n|CREATE RULE pg_settings_n AS
     ON UPDATE TO pg_catalog.pg_settings DO INSTEAD NOTHING;
 pg_settings|pg_settings_u|CREATE RULE pg_settings_u AS
     ON UPDATE TO pg_catalog.pg_settings
-   WHERE (new.name = old.name) DO  SELECT set_config(old.name, new.setting, false) AS set_config;
+   WHERE (new.name = old.name) DO  SELECT set_config(old.name, new.setting, false, false) AS set_config;
 rtest_emp|rtest_emp_del|CREATE RULE rtest_emp_del AS
     ON DELETE TO public.rtest_emp DO  INSERT INTO rtest_emplog (ename, who, action, newsal, oldsal)
   VALUES (old.ename, CURRENT_USER, 'fired'::bpchar, '$0.00'::money, old.salary);
-- 
2.16.3


В списке pgsql-hackers по дате отправления:

Предыдущее
От: Konstantin Knizhnik
Дата:
Сообщение: Re: Drop type "smgr"?
Следующее
От: "Matsumura, Ryo"
Дата:
Сообщение: RE: SQL statement PREPARE does not work in ECPG