Protect syscache from bloating with negative cache entries

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
264 messages Options
1234 ... 14
Reply | Threaded
Open this post in threaded view
|

Protect syscache from bloating with negative cache entries

Kyotaro HORIGUCHI-2
Hello, recently one of my customer stumbled over an immoderate
catcache bloat.

This is a known issue living on the Todo page in the PostgreSQL
wiki.

https://wiki.postgresql.org/wiki/Todo#Cache_Usage
> Fix memory leak caused by negative catcache entries

https://www.postgresql.org/message-id/51C0A1FF.2050404@...

This patch addresses the two cases of syscache bloat by using
invalidation callback mechanism.


Overview of the patch

The bloat is caused by negative cache entries in catcaches. They
are crucial for performance but it is a problem that there's no
way to remove them. They last for the backends' lifetime.

The first patch provides a means to flush catcache negative
entries, then defines a relcache invalidation callback to flush
negative entries in syscaches for pg_statistic(STATRELATTINH) and
pg_attributes(ATTNAME, ATTNUM).  The second patch implements a
syscache invalidation callback so that deletion of a schema
causes a flush for pg_class (RELNAMENSP).

Both of the aboves are not hard-coded and defined in cacheinfo
using additional four members.


Remaining problems

Still, catcache can bloat by repeatedly accessing non-existent
table with unique names in a permanently-living schema but it
seems a bit too artificial (or malicious). Since such negative
entries don't have a trigger to remove, caps are needed to
prevent them from bloating syscaches, but the limits are hardly
seem reasonably determinable.


Defects or disadvantages

This patch scans over whole the target catcache to find negative
entries to remove and it might take a (comparably) long time on a
catcache with so many entries. By the second patch, unrelated
negative caches may be involved in flushing since they are keyd
by hashvalue, not by the exact key values.



The attached files are the following.

1. 0001-Cleanup-negative-cache-of-pg_statistic-when-dropping.patch
   Negative entry flushing by relcache invalidation using
   relcache invalidation callback.

2. 0002-Cleanup-negative-cache-of-pg_class-when-dropping-a-s.patch
   Negative entry flushing by catcache invalidation using
   catcache invalidation callback.

3. gen.pl
   a test script for STATRELATTINH bloating.

4. gen2.pl
   a test script for RELNAMENSP bloating.

3 and 4 are used as the following,

./gen.pl | psql postgres > /dev/null



regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

From 203735e13d2b8584e1ddc652b602465a4f839355 Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <[hidden email]>
Date: Thu, 15 Dec 2016 17:43:03 +0900
Subject: [PATCH 1/2] Cleanup negative cache of pg_statistic when dropping a
 relation.

Accessing columns that don't have statistics causes leaves negative
entries in catcache for pg_statstic, but there's no chance to remove
them. Especially when repeatedly creating then dropping temporary
tables bloats catcache so much that memory pressure can be
significant. This patch removes negative entries in STATRELATTINH,
ATTNAME and ATTNUM when corresponding relation is dropped.
---
 src/backend/utils/cache/catcache.c |  57 +++++++-
 src/backend/utils/cache/syscache.c | 274 +++++++++++++++++++++++++++----------
 src/include/utils/catcache.h       |   3 +
 src/include/utils/syscache.h       |   2 +
 4 files changed, 263 insertions(+), 73 deletions(-)

diff --git a/src/backend/utils/cache/catcache.c b/src/backend/utils/cache/catcache.c
index 6016d19..c1d9d2f 100644
--- a/src/backend/utils/cache/catcache.c
+++ b/src/backend/utils/cache/catcache.c
@@ -304,10 +304,11 @@ CatCachePrintStats(int code, Datum arg)
 
  if (cache->cc_ntup == 0 && cache->cc_searches == 0)
  continue; /* don't print unused caches */
- elog(DEBUG2, "catcache %s/%u: %d tup, %ld srch, %ld+%ld=%ld hits, %ld+%ld=%ld loads, %ld invals, %ld lsrch, %ld lhits",
+ elog(DEBUG2, "catcache %s/%u: %d tup, %d negtup, %ld srch, %ld+%ld=%ld hits, %ld+%ld=%ld loads, %ld invals, %ld lsrch, %ld lhits",
  cache->cc_relname,
  cache->cc_indexoid,
  cache->cc_ntup,
+ cache->cc_nnegtup,
  cache->cc_searches,
  cache->cc_hits,
  cache->cc_neg_hits,
@@ -374,6 +375,10 @@ CatCacheRemoveCTup(CatCache *cache, CatCTup *ct)
  /* free associated tuple data */
  if (ct->tuple.t_data != NULL)
  pfree(ct->tuple.t_data);
+
+ if (ct->negative)
+ --cache->cc_nnegtup;
+
  pfree(ct);
 
  --cache->cc_ntup;
@@ -637,6 +642,49 @@ ResetCatalogCache(CatCache *cache)
 }
 
 /*
+ * CleanupCatCacheNegEntries
+ *
+ * Remove negative cache tuples maching a partial key.
+ *
+ */
+void
+CleanupCatCacheNegEntries(CatCache *cache, ScanKeyData *skey)
+{
+ int i;
+
+ /* If this cache has no negative entries, nothing to do */
+ if (cache->cc_nnegtup == 0)
+ return;
+
+ /* searching with a paritial key needs scanning the whole cache */
+ for (i = 0; i < cache->cc_nbuckets; i++)
+ {
+ dlist_head *bucket = &cache->cc_bucket[i];
+ dlist_mutable_iter iter;
+
+ dlist_foreach_modify(iter, bucket)
+ {
+ CatCTup    *ct = dlist_container(CatCTup, cache_elem, iter.cur);
+ bool res;
+
+ if (!ct->negative)
+ continue;
+
+ HeapKeyTest(&ct->tuple, cache->cc_tupdesc, 1, skey, res);
+ if (!res)
+ continue;
+
+ /*
+ * a negative cache entry cannot be refferenced so we can remove
+ * it unconditionally
+ */
+ CatCacheRemoveCTup(cache, ct);
+ }
+ }
+}
+
+
+/*
  * ResetCatalogCaches
  *
  * Reset all caches when a shared cache inval event forces it
@@ -783,6 +831,7 @@ InitCatCache(int id,
  cp->cc_relisshared = false; /* temporary */
  cp->cc_tupdesc = (TupleDesc) NULL;
  cp->cc_ntup = 0;
+ cp->cc_nnegtup = 0;
  cp->cc_nbuckets = nbuckets;
  cp->cc_nkeys = nkeys;
  for (i = 0; i < nkeys; ++i)
@@ -1279,8 +1328,8 @@ SearchCatCache(CatCache *cache,
 
  CACHE4_elog(DEBUG2, "SearchCatCache(%s): Contains %d/%d tuples",
  cache->cc_relname, cache->cc_ntup, CacheHdr->ch_ntup);
- CACHE3_elog(DEBUG2, "SearchCatCache(%s): put neg entry in bucket %d",
- cache->cc_relname, hashIndex);
+ CACHE4_elog(DEBUG2, "SearchCatCache(%s): put neg entry in bucket %d, total %d",
+ cache->cc_relname, hashIndex, cache->cc_nnegtup);
 
  /*
  * We are not returning the negative entry to the caller, so leave its
@@ -1731,6 +1780,8 @@ CatalogCacheCreateEntry(CatCache *cache, HeapTuple ntp,
 
  cache->cc_ntup++;
  CacheHdr->ch_ntup++;
+ if (negative)
+ cache->cc_nnegtup++;
 
  /*
  * If the hash table has become too full, enlarge the buckets array. Quite
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index a3e0517..bc38113 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -69,6 +69,8 @@
 #include "catalog/pg_user_mapping.h"
 #include "utils/rel.h"
 #include "utils/catcache.h"
+#include "utils/fmgroids.h"
+#include "utils/inval.h"
 #include "utils/syscache.h"
 
 
@@ -114,6 +116,9 @@ struct cachedesc
  int nkeys; /* # of keys needed for cache lookup */
  int key[4]; /* attribute numbers of key attrs */
  int nbuckets; /* number of hash buckets for this cache */
+
+ /* relcache invalidation stuff */
+ AttrNumber relattrnum; /* attr number of reloid, 0 if nothing to do */
 };
 
 static const struct cachedesc cacheinfo[] = {
@@ -126,7 +131,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 16
+ 16,
+ 0
  },
  {AccessMethodRelationId, /* AMNAME */
  AmNameIndexId,
@@ -137,7 +143,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 4
+ 4,
+ 0
  },
  {AccessMethodRelationId, /* AMOID */
  AmOidIndexId,
@@ -148,7 +155,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 4
+ 4,
+ 0
  },
  {AccessMethodOperatorRelationId, /* AMOPOPID */
  AccessMethodOperatorIndexId,
@@ -159,7 +167,8 @@ static const struct cachedesc cacheinfo[] = {
  Anum_pg_amop_amopfamily,
  0
  },
- 64
+ 64,
+ 0
  },
  {AccessMethodOperatorRelationId, /* AMOPSTRATEGY */
  AccessMethodStrategyIndexId,
@@ -170,7 +179,8 @@ static const struct cachedesc cacheinfo[] = {
  Anum_pg_amop_amoprighttype,
  Anum_pg_amop_amopstrategy
  },
- 64
+ 64,
+ 0
  },
  {AccessMethodProcedureRelationId, /* AMPROCNUM */
  AccessMethodProcedureIndexId,
@@ -181,7 +191,8 @@ static const struct cachedesc cacheinfo[] = {
  Anum_pg_amproc_amprocrighttype,
  Anum_pg_amproc_amprocnum
  },
- 16
+ 16,
+ 0
  },
  {AttributeRelationId, /* ATTNAME */
  AttributeRelidNameIndexId,
@@ -192,7 +203,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 32
+ 32,
+ Anum_pg_attribute_attrelid
  },
  {AttributeRelationId, /* ATTNUM */
  AttributeRelidNumIndexId,
@@ -203,7 +215,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 128
+ 128,
+ Anum_pg_attribute_attrelid
  },
  {AuthMemRelationId, /* AUTHMEMMEMROLE */
  AuthMemMemRoleIndexId,
@@ -214,7 +227,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 8
+ 8,
+ 0
  },
  {AuthMemRelationId, /* AUTHMEMROLEMEM */
  AuthMemRoleMemIndexId,
@@ -225,7 +239,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 8
+ 8,
+ 0
  },
  {AuthIdRelationId, /* AUTHNAME */
  AuthIdRolnameIndexId,
@@ -236,7 +251,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 8
+ 8,
+ 0
  },
  {AuthIdRelationId, /* AUTHOID */
  AuthIdOidIndexId,
@@ -247,10 +263,10 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 8
+ 8,
+ 0
  },
- {
- CastRelationId, /* CASTSOURCETARGET */
+ {CastRelationId, /* CASTSOURCETARGET */
  CastSourceTargetIndexId,
  2,
  {
@@ -259,7 +275,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 256
+ 256,
+ 0
  },
  {OperatorClassRelationId, /* CLAAMNAMENSP */
  OpclassAmNameNspIndexId,
@@ -270,7 +287,8 @@ static const struct cachedesc cacheinfo[] = {
  Anum_pg_opclass_opcnamespace,
  0
  },
- 8
+ 8,
+ 0
  },
  {OperatorClassRelationId, /* CLAOID */
  OpclassOidIndexId,
@@ -281,7 +299,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 8
+ 8,
+ 0
  },
  {CollationRelationId, /* COLLNAMEENCNSP */
  CollationNameEncNspIndexId,
@@ -292,7 +311,8 @@ static const struct cachedesc cacheinfo[] = {
  Anum_pg_collation_collnamespace,
  0
  },
- 8
+ 8,
+ 0
  },
  {CollationRelationId, /* COLLOID */
  CollationOidIndexId,
@@ -303,7 +323,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 8
+ 8,
+ 0
  },
  {ConversionRelationId, /* CONDEFAULT */
  ConversionDefaultIndexId,
@@ -314,7 +335,8 @@ static const struct cachedesc cacheinfo[] = {
  Anum_pg_conversion_contoencoding,
  ObjectIdAttributeNumber,
  },
- 8
+ 8,
+ 0
  },
  {ConversionRelationId, /* CONNAMENSP */
  ConversionNameNspIndexId,
@@ -325,7 +347,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 8
+ 8,
+ 0
  },
  {ConstraintRelationId, /* CONSTROID */
  ConstraintOidIndexId,
@@ -336,7 +359,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 16
+ 16,
+ 0
  },
  {ConversionRelationId, /* CONVOID */
  ConversionOidIndexId,
@@ -347,7 +371,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 8
+ 8,
+ 0
  },
  {DatabaseRelationId, /* DATABASEOID */
  DatabaseOidIndexId,
@@ -358,7 +383,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 4
+ 4,
+ 0
  },
  {DefaultAclRelationId, /* DEFACLROLENSPOBJ */
  DefaultAclRoleNspObjIndexId,
@@ -369,7 +395,8 @@ static const struct cachedesc cacheinfo[] = {
  Anum_pg_default_acl_defaclobjtype,
  0
  },
- 8
+ 8,
+ 0
  },
  {EnumRelationId, /* ENUMOID */
  EnumOidIndexId,
@@ -380,7 +407,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 8
+ 8,
+ 0
  },
  {EnumRelationId, /* ENUMTYPOIDNAME */
  EnumTypIdLabelIndexId,
@@ -391,7 +419,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 8
+ 8,
+ 0
  },
  {EventTriggerRelationId, /* EVENTTRIGGERNAME */
  EventTriggerNameIndexId,
@@ -402,7 +431,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 8
+ 8,
+ 0
  },
  {EventTriggerRelationId, /* EVENTTRIGGEROID */
  EventTriggerOidIndexId,
@@ -413,7 +443,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 8
+ 8,
+ 0
  },
  {ForeignDataWrapperRelationId, /* FOREIGNDATAWRAPPERNAME */
  ForeignDataWrapperNameIndexId,
@@ -424,7 +455,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 2
+ 2,
+ 0
  },
  {ForeignDataWrapperRelationId, /* FOREIGNDATAWRAPPEROID */
  ForeignDataWrapperOidIndexId,
@@ -435,7 +467,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 2
+ 2,
+ 0
  },
  {ForeignServerRelationId, /* FOREIGNSERVERNAME */
  ForeignServerNameIndexId,
@@ -446,7 +479,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 2
+ 2,
+ 0
  },
  {ForeignServerRelationId, /* FOREIGNSERVEROID */
  ForeignServerOidIndexId,
@@ -457,7 +491,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 2
+ 2,
+ 0
  },
  {ForeignTableRelationId, /* FOREIGNTABLEREL */
  ForeignTableRelidIndexId,
@@ -468,7 +503,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 4
+ 4,
+ 0
  },
  {IndexRelationId, /* INDEXRELID */
  IndexRelidIndexId,
@@ -479,7 +515,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 64
+ 64,
+ 0
  },
  {LanguageRelationId, /* LANGNAME */
  LanguageNameIndexId,
@@ -490,7 +527,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 4
+ 4,
+ 0
  },
  {LanguageRelationId, /* LANGOID */
  LanguageOidIndexId,
@@ -501,7 +539,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 4
+ 4,
+ 0
  },
  {NamespaceRelationId, /* NAMESPACENAME */
  NamespaceNameIndexId,
@@ -512,7 +551,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 4
+ 4,
+ 0
  },
  {NamespaceRelationId, /* NAMESPACEOID */
  NamespaceOidIndexId,
@@ -523,7 +563,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 16
+ 16,
+ 0
  },
  {OperatorRelationId, /* OPERNAMENSP */
  OperatorNameNspIndexId,
@@ -534,7 +575,8 @@ static const struct cachedesc cacheinfo[] = {
  Anum_pg_operator_oprright,
  Anum_pg_operator_oprnamespace
  },
- 256
+ 256,
+ 0
  },
  {OperatorRelationId, /* OPEROID */
  OperatorOidIndexId,
@@ -545,7 +587,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 32
+ 32,
+ 0
  },
  {OperatorFamilyRelationId, /* OPFAMILYAMNAMENSP */
  OpfamilyAmNameNspIndexId,
@@ -556,7 +599,8 @@ static const struct cachedesc cacheinfo[] = {
  Anum_pg_opfamily_opfnamespace,
  0
  },
- 8
+ 8,
+ 0
  },
  {OperatorFamilyRelationId, /* OPFAMILYOID */
  OpfamilyOidIndexId,
@@ -567,7 +611,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 8
+ 8,
+ 0
  },
  {PartitionedRelationId, /* PARTRELID */
  PartitionedRelidIndexId,
@@ -578,7 +623,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 32
+ 32,
+ 0
  },
  {ProcedureRelationId, /* PROCNAMEARGSNSP */
  ProcedureNameArgsNspIndexId,
@@ -589,7 +635,8 @@ static const struct cachedesc cacheinfo[] = {
  Anum_pg_proc_pronamespace,
  0
  },
- 128
+ 128,
+ 0
  },
  {ProcedureRelationId, /* PROCOID */
  ProcedureOidIndexId,
@@ -600,7 +647,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 128
+ 128,
+ 0
  },
  {RangeRelationId, /* RANGETYPE */
  RangeTypidIndexId,
@@ -611,7 +659,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 4
+ 4,
+ 0
  },
  {RelationRelationId, /* RELNAMENSP */
  ClassNameNspIndexId,
@@ -622,7 +671,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 128
+ 128,
+ 0
  },
  {RelationRelationId, /* RELOID */
  ClassOidIndexId,
@@ -633,7 +683,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 128
+ 128,
+ 0
  },
  {ReplicationOriginRelationId, /* REPLORIGIDENT */
  ReplicationOriginIdentIndex,
@@ -644,7 +695,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 16
+ 16,
+ 0
  },
  {ReplicationOriginRelationId, /* REPLORIGNAME */
  ReplicationOriginNameIndex,
@@ -655,7 +707,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 16
+ 16,
+ 0
  },
  {RewriteRelationId, /* RULERELNAME */
  RewriteRelRulenameIndexId,
@@ -666,7 +719,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 8
+ 8,
+ 0
  },
  {StatisticRelationId, /* STATRELATTINH */
  StatisticRelidAttnumInhIndexId,
@@ -677,7 +731,8 @@ static const struct cachedesc cacheinfo[] = {
  Anum_pg_statistic_stainherit,
  0
  },
- 128
+ 128,
+ Anum_pg_statistic_starelid
  },
  {TableSpaceRelationId, /* TABLESPACEOID */
  TablespaceOidIndexId,
@@ -688,7 +743,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0,
  },
- 4
+ 4,
+ 0
  },
  {TransformRelationId, /* TRFOID */
  TransformOidIndexId,
@@ -699,7 +755,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0,
  },
- 16
+ 16,
+ 0
  },
  {TransformRelationId, /* TRFTYPELANG */
  TransformTypeLangIndexId,
@@ -710,7 +767,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0,
  },
- 16
+ 16,
+ 0
  },
  {TSConfigMapRelationId, /* TSCONFIGMAP */
  TSConfigMapIndexId,
@@ -721,7 +779,8 @@ static const struct cachedesc cacheinfo[] = {
  Anum_pg_ts_config_map_mapseqno,
  0
  },
- 2
+ 2,
+ 0
  },
  {TSConfigRelationId, /* TSCONFIGNAMENSP */
  TSConfigNameNspIndexId,
@@ -732,7 +791,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 2
+ 2,
+ 0
  },
  {TSConfigRelationId, /* TSCONFIGOID */
  TSConfigOidIndexId,
@@ -743,7 +803,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 2
+ 2,
+ 0
  },
  {TSDictionaryRelationId, /* TSDICTNAMENSP */
  TSDictionaryNameNspIndexId,
@@ -754,7 +815,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 2
+ 2,
+ 0
  },
  {TSDictionaryRelationId, /* TSDICTOID */
  TSDictionaryOidIndexId,
@@ -765,7 +827,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 2
+ 2,
+ 0
  },
  {TSParserRelationId, /* TSPARSERNAMENSP */
  TSParserNameNspIndexId,
@@ -776,7 +839,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 2
+ 2,
+ 0
  },
  {TSParserRelationId, /* TSPARSEROID */
  TSParserOidIndexId,
@@ -787,7 +851,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 2
+ 2,
+ 0
  },
  {TSTemplateRelationId, /* TSTEMPLATENAMENSP */
  TSTemplateNameNspIndexId,
@@ -798,7 +863,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 2
+ 2,
+ 0
  },
  {TSTemplateRelationId, /* TSTEMPLATEOID */
  TSTemplateOidIndexId,
@@ -809,7 +875,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 2
+ 2,
+ 0
  },
  {TypeRelationId, /* TYPENAMENSP */
  TypeNameNspIndexId,
@@ -820,7 +887,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 64
+ 64,
+ 0
  },
  {TypeRelationId, /* TYPEOID */
  TypeOidIndexId,
@@ -831,7 +899,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 64
+ 64,
+ 0
  },
  {UserMappingRelationId, /* USERMAPPINGOID */
  UserMappingOidIndexId,
@@ -842,7 +911,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 2
+ 2,
+ 0
  },
  {UserMappingRelationId, /* USERMAPPINGUSERSERVER */
  UserMappingUserServerIndexId,
@@ -853,7 +923,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 2
+ 2,
+ 0
  }
 };
 
@@ -871,8 +942,23 @@ static int SysCacheRelationOidSize;
 static Oid SysCacheSupportingRelOid[SysCacheSize * 2];
 static int SysCacheSupportingRelOidSize;
 
-static int oid_compare(const void *a, const void *b);
+/*
+ * stuff for negative cache flushing by relcache invalidation
+ */
+#define MAX_RELINVAL_CALLBACKS 4
+typedef struct RELINVALCBParam
+{
+ CatCache *cache;
+ int  relkeynum;
+}  RELINVALCBParam;
+
+RELINVALCBParam relinval_callback_list[MAX_RELINVAL_CALLBACKS];
+static int relinval_callback_count = 0;
 
+static ScanKeyData oideqscankey; /* ScanKey for reloid match  */
+
+static int oid_compare(const void *a, const void *b);
+static void SysCacheRelInvalCallback(Datum arg, Oid reloid);
 
 /*
  * InitCatalogCache - initialize the caches
@@ -913,6 +999,21 @@ InitCatalogCache(void)
  cacheinfo[cacheId].indoid;
  /* see comments for RelationInvalidatesSnapshotsOnly */
  Assert(!RelationInvalidatesSnapshotsOnly(cacheinfo[cacheId].reloid));
+
+ /*
+ * If this syscache has something to do with relcache invalidation,
+ * register a callback
+ */
+ if (cacheinfo[cacheId].relattrnum > 0)
+ {
+ Assert(relinval_callback_count < MAX_RELINVAL_CALLBACKS);
+
+ relinval_callback_list[relinval_callback_count].cache  =
+ SysCache[cacheId];
+ relinval_callback_list[relinval_callback_count].relkeynum =
+ cacheinfo[cacheId].relattrnum;
+ relinval_callback_count++;
+ }
  }
 
  Assert(SysCacheRelationOidSize <= lengthof(SysCacheRelationOid));
@@ -937,10 +1038,43 @@ InitCatalogCache(void)
  }
  SysCacheSupportingRelOidSize = j + 1;
 
+ /*
+ * If any of syscache needs relcache invalidation callback, prepare the
+ * scankey for reloid comparison and register a relcache inval callback.
+ */
+ if (relinval_callback_count > 0)
+ {
+ oideqscankey.sk_strategy = BTEqualStrategyNumber;
+ oideqscankey.sk_subtype = InvalidOid;
+ oideqscankey.sk_collation = InvalidOid;
+ fmgr_info_cxt(F_OIDEQ, &oideqscankey.sk_func, CacheMemoryContext);
+ CacheRegisterRelcacheCallback(SysCacheRelInvalCallback, (Datum) 0);
+ }
+
  CacheInitialized = true;
 }
 
 /*
+ * Callback function for negative cache flushing by relcache invalidation
+ * scankey for this funciton is prepared in InitCatalogCache.
+ */
+static void
+SysCacheRelInvalCallback(Datum arg, Oid reloid)
+{
+ int i;
+
+ for(i = 0 ; i < relinval_callback_count ; i++)
+ {
+ ScanKeyData skey;
+
+ memcpy(&skey, &oideqscankey, sizeof(skey));
+ skey.sk_attno = relinval_callback_list[i].relkeynum;
+ skey.sk_argument = ObjectIdGetDatum(reloid);
+ CleanupCatCacheNegEntries(relinval_callback_list[i].cache, &skey);
+ }
+}
+
+/*
  * InitCatalogCachePhase2 - finish initializing the caches
  *
  * Finish initializing all the caches, including necessary database
diff --git a/src/include/utils/catcache.h b/src/include/utils/catcache.h
index 253c7b5..cb662c0 100644
--- a/src/include/utils/catcache.h
+++ b/src/include/utils/catcache.h
@@ -44,6 +44,7 @@ typedef struct catcache
  bool cc_relisshared; /* is relation shared across databases? */
  TupleDesc cc_tupdesc; /* tuple descriptor (copied from reldesc) */
  int cc_ntup; /* # of tuples currently in this cache */
+ int cc_nnegtup; /* # of negative tuples */
  int cc_nbuckets; /* # of hash buckets in this cache */
  int cc_nkeys; /* # of keys (1..CATCACHE_MAXKEYS) */
  int cc_key[CATCACHE_MAXKEYS]; /* AttrNumber of each key */
@@ -183,6 +184,8 @@ extern CatCList *SearchCatCacheList(CatCache *cache, int nkeys,
    Datum v3, Datum v4);
 extern void ReleaseCatCacheList(CatCList *list);
 
+extern void
+CleanupCatCacheNegEntries(CatCache *cache, ScanKeyData *skey);
 extern void ResetCatalogCaches(void);
 extern void CatalogCacheFlushCatalog(Oid catId);
 extern void CatalogCacheIdInvalidate(int cacheId, uint32 hashValue);
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index 39fe947..145addf 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -106,6 +106,8 @@ extern void InitCatalogCachePhase2(void);
 extern HeapTuple SearchSysCache(int cacheId,
    Datum key1, Datum key2, Datum key3, Datum key4);
 extern void ReleaseSysCache(HeapTuple tuple);
+extern void CleanupNegativeCache(int cacheid, int nkeys,
+ Datum key1, Datum key2, Datum key3, Datum key4);
 
 /* convenience routines */
 extern HeapTuple SearchSysCacheCopy(int cacheId,
--
2.9.2


From 1b42210b5506a11ffd4d8a87a24f44a75d47c652 Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <[hidden email]>
Date: Fri, 16 Dec 2016 16:44:40 +0900
Subject: [PATCH 2/2] Cleanup negative cache of pg_class when dropping a schema

This feature inturn is triggered by catcache invalidation. This patch
provides a syscache invalidation callback to flush negative cache
entries corresponding to the invalidated object.
---
 src/backend/utils/cache/catcache.c |  42 ++++++
 src/backend/utils/cache/inval.c    |   8 +-
 src/backend/utils/cache/syscache.c | 301 ++++++++++++++++++++++++++++---------
 src/include/utils/catcache.h       |   3 +
 4 files changed, 284 insertions(+), 70 deletions(-)

diff --git a/src/backend/utils/cache/catcache.c b/src/backend/utils/cache/catcache.c
index c1d9d2f..094bc60 100644
--- a/src/backend/utils/cache/catcache.c
+++ b/src/backend/utils/cache/catcache.c
@@ -1424,6 +1424,48 @@ GetCatCacheHashValue(CatCache *cache,
  return CatalogCacheComputeHashValue(cache, cache->cc_nkeys, cur_skey);
 }
 
+/*
+ * CollectOIDsForHashValue
+ *
+ * Collect OIDs corresnpond to a hash value. attnum is the column to retrieve
+ * the OIDs.
+ */
+List *
+CollectOIDsForHashValue(CatCache *cache, uint32 hashValue, int attnum)
+{
+ Index hashIndex = HASH_INDEX(hashValue, cache->cc_nbuckets);
+ dlist_head *bucket = &cache->cc_bucket[hashIndex];
+ dlist_iter iter;
+ List *ret = NIL;
+
+ /* Nothing to return before initialized */
+ if (cache->cc_tupdesc == NULL)
+ return ret;
+
+ /* Currently only OID key is supported */
+ Assert(attnum <= cache->cc_tupdesc->natts);
+ Assert(attnum < 0 ? attnum == ObjectIdAttributeNumber :
+   cache->cc_tupdesc->attrs[attnum]->atttypid == OIDOID);
+
+ dlist_foreach(iter, bucket)
+ {
+ CatCTup *ct = dlist_container(CatCTup, cache_elem, iter.cur);
+ bool isNull;
+ Datum oid;
+
+ if (ct->dead)
+ continue; /* ignore dead entries */
+
+ if (ct->hash_value != hashValue)
+ continue; /* quickly skip entry if wrong hash val */
+
+ oid = heap_getattr(&ct->tuple, attnum, cache->cc_tupdesc, &isNull);
+ if (!isNull)
+ ret = lappend_oid(ret, DatumGetObjectId(oid));
+ }
+
+ return ret;
+}
 
 /*
  * SearchCatCacheList
diff --git a/src/backend/utils/cache/inval.c b/src/backend/utils/cache/inval.c
index 5803518..0290974 100644
--- a/src/backend/utils/cache/inval.c
+++ b/src/backend/utils/cache/inval.c
@@ -543,9 +543,13 @@ LocalExecuteInvalidationMessage(SharedInvalidationMessage *msg)
  {
  InvalidateCatalogSnapshot();
 
- CatalogCacheIdInvalidate(msg->cc.id, msg->cc.hashValue);
-
+ /*
+ * Call the callbacks first so that the callbacks can access the
+ * entries corresponding to the hashValue.
+ */
  CallSyscacheCallbacks(msg->cc.id, msg->cc.hashValue);
+
+ CatalogCacheIdInvalidate(msg->cc.id, msg->cc.hashValue);
  }
  }
  else if (msg->id == SHAREDINVALCATALOG_ID)
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index bc38113..f1fc5f3 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -107,6 +107,16 @@
 */
 
 /*
+ * struct for flushing negative cache by syscache invalidation
+ */
+typedef struct SysCacheCBParam_T
+{
+ int trig_attnum;
+ int target_cacheid;
+ ScanKeyData skey;
+} SysCacheCBParam;
+
+/*
  * struct cachedesc: information defining a single syscache
  */
 struct cachedesc
@@ -119,6 +129,14 @@ struct cachedesc
 
  /* relcache invalidation stuff */
  AttrNumber relattrnum; /* attr number of reloid, 0 if nothing to do */
+
+ /* catcache invalidation stuff */
+ int trig_cacheid; /* cache id of triggering syscache: -1 means
+ * no triggering cache */
+ int16 trig_attnum; /* key column in triggering cache. Must be an
+ * OID */
+ int16 target_attnum; /* corresponding column in this cache. Must be
+ * an OID*/
 };
 
 static const struct cachedesc cacheinfo[] = {
@@ -132,7 +150,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  16,
- 0
+ 0,
+ -1, 0, 0
  },
  {AccessMethodRelationId, /* AMNAME */
  AmNameIndexId,
@@ -144,7 +163,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  4,
- 0
+ 0,
+ -1, 0, 0
  },
  {AccessMethodRelationId, /* AMOID */
  AmOidIndexId,
@@ -156,7 +176,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  4,
- 0
+ 0,
+ -1, 0, 0
  },
  {AccessMethodOperatorRelationId, /* AMOPOPID */
  AccessMethodOperatorIndexId,
@@ -168,7 +189,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  64,
- 0
+ 0,
+ -1, 0, 0
  },
  {AccessMethodOperatorRelationId, /* AMOPSTRATEGY */
  AccessMethodStrategyIndexId,
@@ -180,7 +202,8 @@ static const struct cachedesc cacheinfo[] = {
  Anum_pg_amop_amopstrategy
  },
  64,
- 0
+ 0,
+ -1, 0, 0
  },
  {AccessMethodProcedureRelationId, /* AMPROCNUM */
  AccessMethodProcedureIndexId,
@@ -192,7 +215,8 @@ static const struct cachedesc cacheinfo[] = {
  Anum_pg_amproc_amprocnum
  },
  16,
- 0
+ 0,
+ -1, 0, 0
  },
  {AttributeRelationId, /* ATTNAME */
  AttributeRelidNameIndexId,
@@ -204,7 +228,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  32,
- Anum_pg_attribute_attrelid
+ Anum_pg_attribute_attrelid,
+ -1, 0, 0
  },
  {AttributeRelationId, /* ATTNUM */
  AttributeRelidNumIndexId,
@@ -216,7 +241,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  128,
- Anum_pg_attribute_attrelid
+ Anum_pg_attribute_attrelid,
+ -1, 0, 0
  },
  {AuthMemRelationId, /* AUTHMEMMEMROLE */
  AuthMemMemRoleIndexId,
@@ -228,7 +254,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  8,
- 0
+ 0,
+ -1, 0, 0
  },
  {AuthMemRelationId, /* AUTHMEMROLEMEM */
  AuthMemRoleMemIndexId,
@@ -240,7 +267,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  8,
- 0
+ 0,
+ -1, 0, 0
  },
  {AuthIdRelationId, /* AUTHNAME */
  AuthIdRolnameIndexId,
@@ -252,7 +280,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  8,
- 0
+ 0,
+ -1, 0, 0
  },
  {AuthIdRelationId, /* AUTHOID */
  AuthIdOidIndexId,
@@ -264,7 +293,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  8,
- 0
+ 0,
+ -1, 0, 0
  },
  {CastRelationId, /* CASTSOURCETARGET */
  CastSourceTargetIndexId,
@@ -276,7 +306,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  256,
- 0
+ 0,
+ -1, 0, 0
  },
  {OperatorClassRelationId, /* CLAAMNAMENSP */
  OpclassAmNameNspIndexId,
@@ -288,7 +319,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  8,
- 0
+ 0,
+ -1, 0, 0
  },
  {OperatorClassRelationId, /* CLAOID */
  OpclassOidIndexId,
@@ -300,7 +332,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  8,
- 0
+ 0,
+ -1, 0, 0
  },
  {CollationRelationId, /* COLLNAMEENCNSP */
  CollationNameEncNspIndexId,
@@ -312,7 +345,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  8,
- 0
+ 0,
+ -1, 0, 0
  },
  {CollationRelationId, /* COLLOID */
  CollationOidIndexId,
@@ -324,7 +358,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  8,
- 0
+ 0,
+ -1, 0, 0
  },
  {ConversionRelationId, /* CONDEFAULT */
  ConversionDefaultIndexId,
@@ -336,7 +371,8 @@ static const struct cachedesc cacheinfo[] = {
  ObjectIdAttributeNumber,
  },
  8,
- 0
+ 0,
+ -1, 0, 0
  },
  {ConversionRelationId, /* CONNAMENSP */
  ConversionNameNspIndexId,
@@ -348,7 +384,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  8,
- 0
+ 0,
+ -1, 0, 0
  },
  {ConstraintRelationId, /* CONSTROID */
  ConstraintOidIndexId,
@@ -360,7 +397,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  16,
- 0
+ 0,
+ -1, 0, 0
  },
  {ConversionRelationId, /* CONVOID */
  ConversionOidIndexId,
@@ -372,7 +410,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  8,
- 0
+ 0,
+ -1, 0, 0
  },
  {DatabaseRelationId, /* DATABASEOID */
  DatabaseOidIndexId,
@@ -384,7 +423,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  4,
- 0
+ 0,
+ -1, 0, 0
  },
  {DefaultAclRelationId, /* DEFACLROLENSPOBJ */
  DefaultAclRoleNspObjIndexId,
@@ -396,7 +436,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  8,
- 0
+ 0,
+ -1, 0, 0
  },
  {EnumRelationId, /* ENUMOID */
  EnumOidIndexId,
@@ -408,7 +449,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  8,
- 0
+ 0,
+ -1, 0, 0
  },
  {EnumRelationId, /* ENUMTYPOIDNAME */
  EnumTypIdLabelIndexId,
@@ -420,7 +462,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  8,
- 0
+ 0,
+ -1, 0, 0
  },
  {EventTriggerRelationId, /* EVENTTRIGGERNAME */
  EventTriggerNameIndexId,
@@ -432,7 +475,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  8,
- 0
+ 0,
+ -1, 0, 0
  },
  {EventTriggerRelationId, /* EVENTTRIGGEROID */
  EventTriggerOidIndexId,
@@ -444,7 +488,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  8,
- 0
+ 0,
+ -1, 0, 0
  },
  {ForeignDataWrapperRelationId, /* FOREIGNDATAWRAPPERNAME */
  ForeignDataWrapperNameIndexId,
@@ -456,7 +501,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  2,
- 0
+ 0,
+ -1, 0, 0
  },
  {ForeignDataWrapperRelationId, /* FOREIGNDATAWRAPPEROID */
  ForeignDataWrapperOidIndexId,
@@ -468,7 +514,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  2,
- 0
+ 0,
+ -1, 0, 0
  },
  {ForeignServerRelationId, /* FOREIGNSERVERNAME */
  ForeignServerNameIndexId,
@@ -480,7 +527,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  2,
- 0
+ 0,
+ -1, 0, 0
  },
  {ForeignServerRelationId, /* FOREIGNSERVEROID */
  ForeignServerOidIndexId,
@@ -492,7 +540,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  2,
- 0
+ 0,
+ -1, 0, 0
  },
  {ForeignTableRelationId, /* FOREIGNTABLEREL */
  ForeignTableRelidIndexId,
@@ -504,7 +553,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  4,
- 0
+ 0,
+ -1, 0, 0
  },
  {IndexRelationId, /* INDEXRELID */
  IndexRelidIndexId,
@@ -516,7 +566,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  64,
- 0
+ 0,
+ -1, 0, 0
  },
  {LanguageRelationId, /* LANGNAME */
  LanguageNameIndexId,
@@ -528,7 +579,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  4,
- 0
+ 0,
+ -1, 0, 0
  },
  {LanguageRelationId, /* LANGOID */
  LanguageOidIndexId,
@@ -540,7 +592,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  4,
- 0
+ 0,
+ -1, 0, 0
  },
  {NamespaceRelationId, /* NAMESPACENAME */
  NamespaceNameIndexId,
@@ -552,7 +605,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  4,
- 0
+ 0,
+ -1, 0, 0
  },
  {NamespaceRelationId, /* NAMESPACEOID */
  NamespaceOidIndexId,
@@ -564,7 +618,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  16,
- 0
+ 0,
+ -1, 0, 0
  },
  {OperatorRelationId, /* OPERNAMENSP */
  OperatorNameNspIndexId,
@@ -576,7 +631,8 @@ static const struct cachedesc cacheinfo[] = {
  Anum_pg_operator_oprnamespace
  },
  256,
- 0
+ 0,
+ -1, 0, 0
  },
  {OperatorRelationId, /* OPEROID */
  OperatorOidIndexId,
@@ -588,7 +644,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  32,
- 0
+ 0,
+ -1, 0, 0
  },
  {OperatorFamilyRelationId, /* OPFAMILYAMNAMENSP */
  OpfamilyAmNameNspIndexId,
@@ -600,7 +657,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  8,
- 0
+ 0,
+ -1, 0, 0
  },
  {OperatorFamilyRelationId, /* OPFAMILYOID */
  OpfamilyOidIndexId,
@@ -612,7 +670,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  8,
- 0
+ 0,
+ -1, 0, 0
  },
  {PartitionedRelationId, /* PARTRELID */
  PartitionedRelidIndexId,
@@ -624,7 +683,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  32,
- 0
+ 0,
+ -1, 0, 0
  },
  {ProcedureRelationId, /* PROCNAMEARGSNSP */
  ProcedureNameArgsNspIndexId,
@@ -636,7 +696,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  128,
- 0
+ 0,
+ -1, 0, 0
  },
  {ProcedureRelationId, /* PROCOID */
  ProcedureOidIndexId,
@@ -648,7 +709,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  128,
- 0
+ 0,
+ -1, 0, 0
  },
  {RangeRelationId, /* RANGETYPE */
  RangeTypidIndexId,
@@ -660,7 +722,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  4,
- 0
+ 0,
+ -1, 0, 0
  },
  {RelationRelationId, /* RELNAMENSP */
  ClassNameNspIndexId,
@@ -672,7 +735,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  128,
- 0
+ 0,
+ NAMESPACEOID, ObjectIdAttributeNumber, Anum_pg_class_relnamespace
  },
  {RelationRelationId, /* RELOID */
  ClassOidIndexId,
@@ -684,7 +748,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  128,
- 0
+ 0,
+ -1, 0, 0
  },
  {ReplicationOriginRelationId, /* REPLORIGIDENT */
  ReplicationOriginIdentIndex,
@@ -696,7 +761,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  16,
- 0
+ 0,
+ -1, 0, 0
  },
  {ReplicationOriginRelationId, /* REPLORIGNAME */
  ReplicationOriginNameIndex,
@@ -708,7 +774,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  16,
- 0
+ 0,
+ -1, 0, 0
  },
  {RewriteRelationId, /* RULERELNAME */
  RewriteRelRulenameIndexId,
@@ -720,7 +787,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  8,
- 0
+ 0,
+ -1, 0, 0
  },
  {StatisticRelationId, /* STATRELATTINH */
  StatisticRelidAttnumInhIndexId,
@@ -732,7 +800,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  128,
- Anum_pg_statistic_starelid
+ Anum_pg_statistic_starelid,
+ -1, 0, 0
  },
  {TableSpaceRelationId, /* TABLESPACEOID */
  TablespaceOidIndexId,
@@ -744,7 +813,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  },
  4,
- 0
+ 0,
+ -1, 0, 0
  },
  {TransformRelationId, /* TRFOID */
  TransformOidIndexId,
@@ -756,7 +826,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  },
  16,
- 0
+ 0,
+ -1, 0, 0
  },
  {TransformRelationId, /* TRFTYPELANG */
  TransformTypeLangIndexId,
@@ -768,7 +839,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  },
  16,
- 0
+ 0,
+ -1, 0, 0
  },
  {TSConfigMapRelationId, /* TSCONFIGMAP */
  TSConfigMapIndexId,
@@ -780,7 +852,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  2,
- 0
+ 0,
+ -1, 0, 0
  },
  {TSConfigRelationId, /* TSCONFIGNAMENSP */
  TSConfigNameNspIndexId,
@@ -792,7 +865,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  2,
- 0
+ 0,
+ -1, 0, 0
  },
  {TSConfigRelationId, /* TSCONFIGOID */
  TSConfigOidIndexId,
@@ -804,7 +878,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  2,
- 0
+ 0,
+ -1, 0, 0
  },
  {TSDictionaryRelationId, /* TSDICTNAMENSP */
  TSDictionaryNameNspIndexId,
@@ -816,7 +891,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  2,
- 0
+ 0,
+ -1, 0, 0
  },
  {TSDictionaryRelationId, /* TSDICTOID */
  TSDictionaryOidIndexId,
@@ -828,7 +904,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  2,
- 0
+ 0,
+ -1, 0, 0
  },
  {TSParserRelationId, /* TSPARSERNAMENSP */
  TSParserNameNspIndexId,
@@ -840,7 +917,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  2,
- 0
+ 0,
+ -1, 0, 0
  },
  {TSParserRelationId, /* TSPARSEROID */
  TSParserOidIndexId,
@@ -852,7 +930,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  2,
- 0
+ 0,
+ -1, 0, 0
  },
  {TSTemplateRelationId, /* TSTEMPLATENAMENSP */
  TSTemplateNameNspIndexId,
@@ -864,7 +943,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  2,
- 0
+ 0,
+ -1, 0, 0
  },
  {TSTemplateRelationId, /* TSTEMPLATEOID */
  TSTemplateOidIndexId,
@@ -876,7 +956,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  2,
- 0
+ 0,
+ -1, 0, 0
  },
  {TypeRelationId, /* TYPENAMENSP */
  TypeNameNspIndexId,
@@ -888,7 +969,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  64,
- 0
+ 0,
+ -1, 0, 0
  },
  {TypeRelationId, /* TYPEOID */
  TypeOidIndexId,
@@ -900,7 +982,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  64,
- 0
+ 0,
+ -1, 0, 0
  },
  {UserMappingRelationId, /* USERMAPPINGOID */
  UserMappingOidIndexId,
@@ -912,7 +995,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  2,
- 0
+ 0,
+ -1, 0, 0
  },
  {UserMappingRelationId, /* USERMAPPINGUSERSERVER */
  UserMappingUserServerIndexId,
@@ -924,7 +1008,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  2,
- 0
+ 0,
+ -1, 0, 0
  }
 };
 
@@ -959,7 +1044,8 @@ static ScanKeyData oideqscankey; /* ScanKey for reloid match  */
 
 static int oid_compare(const void *a, const void *b);
 static void SysCacheRelInvalCallback(Datum arg, Oid reloid);
-
+static void SysCacheSysCacheInvalCallback(Datum arg, int cacheid,
+  uint32 hashvalue);
 /*
  * InitCatalogCache - initialize the caches
  *
@@ -1014,6 +1100,37 @@ InitCatalogCache(void)
  cacheinfo[cacheId].relattrnum;
  relinval_callback_count++;
  }
+
+ /*
+ * If this syscache has syscache invalidation trigger, register
+ * it.
+ */
+ if (cacheinfo[cacheId].trig_cacheid >= 0)
+ {
+ SysCacheCBParam *param;
+ RegProcedure eqfunc;
+
+ param = MemoryContextAlloc(CacheMemoryContext,
+   sizeof(SysCacheCBParam));
+ param->target_cacheid = cacheId;
+
+ /*
+ * XXXX: Create a scankeydata for OID comparison. We don't have a
+ * means to check the type of the column in the system catalog at
+ * this time. So we have to belive the definition is correct.
+ */
+ eqfunc = F_OIDEQ;
+
+ fmgr_info_cxt(eqfunc, &param->skey.sk_func, CacheMemoryContext);
+ param->skey.sk_attno = cacheinfo[cacheId].target_attnum;
+ param->trig_attnum = cacheinfo[cacheId].trig_attnum;
+ param->skey.sk_strategy = BTEqualStrategyNumber;
+ param->skey.sk_subtype = InvalidOid;
+ param->skey.sk_collation = InvalidOid;
+ CacheRegisterSyscacheCallback(cacheinfo[cacheId].trig_cacheid,
+  SysCacheSysCacheInvalCallback,
+  PointerGetDatum(param));
+ }
  }
 
  Assert(SysCacheRelationOidSize <= lengthof(SysCacheRelationOid));
@@ -1390,6 +1507,54 @@ RelationInvalidatesSnapshotsOnly(Oid relid)
 }
 
 /*
+ * SysCacheSysCacheInvalCallback
+ *
+ * Callback function for negative cache flushing by syscache invalidation.
+ * Fetches an OID (not restricted to system oid column) from the invalidated
+ * tuple and flushes negative entries that matches the OID in the target
+ * syscache.
+ */
+static void
+SysCacheSysCacheInvalCallback(Datum arg, int cacheid, uint32 hashValue)
+{
+ SysCacheCBParam *param;
+ CatCache *trigger_cache; /* triggering catcache */
+ CatCache *target_cache; /* target catcache */
+ List *oids;
+ ListCell *lc;
+ int trigger_cacheid = cacheid;
+ int target_cacheid;
+
+ param = (SysCacheCBParam *)DatumGetPointer(arg);
+ target_cacheid = param->target_cacheid;
+
+ trigger_cache = SysCache[trigger_cacheid];
+ target_cache = SysCache[target_cacheid];
+
+ /*
+ * collect OIDs for target syscache entries. oids contains one value for
+ * most cases, but two or more for the case hashvalue has synonyms. At
+ * least one of them is the right OID but is cannot be distinguished using
+ * the given hash value.
+ *
+ * As the result some unnecessary entries may be flushed but it won't harm
+ * so much than letting them bloat catcaches.
+ */
+ oids =
+ CollectOIDsForHashValue(trigger_cache, hashValue, param->trig_attnum);
+
+ foreach (lc, oids)
+ {
+ ScanKeyData skey;
+ Oid oid = lfirst_oid (lc);
+
+ memcpy(&skey, &param->skey, sizeof(skey));
+ skey.sk_argument = ObjectIdGetDatum(oid);
+ CleanupCatCacheNegEntries(target_cache, &skey);
+ }
+}
+
+/*
  * Test whether a relation has a system cache.
  */
 bool
diff --git a/src/include/utils/catcache.h b/src/include/utils/catcache.h
index cb662c0..b279174 100644
--- a/src/include/utils/catcache.h
+++ b/src/include/utils/catcache.h
@@ -179,6 +179,9 @@ extern uint32 GetCatCacheHashValue(CatCache *cache,
  Datum v1, Datum v2,
  Datum v3, Datum v4);
 
+extern List *CollectOIDsForHashValue(CatCache *cache,
+ uint32 hashValue, int attnum);
+
 extern CatCList *SearchCatCacheList(CatCache *cache, int nkeys,
    Datum v1, Datum v2,
    Datum v3, Datum v4);
--
2.9.2


#! /usr/bin/perl

while (1) {
        print "begin; create temp table t1 (a int, b int, c int, d int, e int, f int, g int, h int, i int, j int) on commit drop; insert into t1 values (1, 2, 3, 4, 5, 6, 7, 8, 9, 10); select * from t1; commit;\n";
}

#! /usr/bin/perl

while (1) {
        print "set log_min_messages=fatal; set client_min_messages=fatal;create schema foo; select * from foo.invalid; drop schema foo;\n";
}


--
Sent via pgsql-hackers mailing list ([hidden email])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Reply | Threaded
Open this post in threaded view
|

Re: Protect syscache from bloating with negative cache entries

Robert Haas
On Mon, Dec 19, 2016 at 6:15 AM, Kyotaro HORIGUCHI
<[hidden email]> wrote:
> Hello, recently one of my customer stumbled over an immoderate
> catcache bloat.

This isn't only an issue for negative catcache entries.  A long time
ago, there was a limit on the size of the relcache, which was removed
because if you have a workload where the working set of relations is
just larger than the limit, performance is terrible.  But the problem
now is that backend memory usage can grow without bound, and that's
also bad, especially on systems with hundreds of long-lived backends.
In connection-pooling environments, the problem is worse, because
every connection in the pool eventually caches references to
everything of interest to any client.

Your patches seem to me to have some merit, but I wonder if we should
also consider having a time-based threshold of some kind.  If, say, a
backend hasn't accessed a catcache or relcache entry for many minutes,
it becomes eligible to be flushed.  We could implement this by having
some process, like the background writer,
SendProcSignal(PROCSIG_HOUSEKEEPING) to every process in the system
every 10 minutes or so.  When a process receives this signal, it sets
a flag that is checked before going idle.  When it sees the flag set,
it makes a pass over every catcache and relcache entry.  All the ones
that are unmarked get marked, and all of the ones that are marked get
removed.  Access to an entry clears any mark.  So anything that's not
touched for more than 10 minutes starts dropping out of backend
caches.

Anyway, that would be a much bigger change from what you are proposing
here, and what you are proposing here seems reasonable so I guess I
shouldn't distract from it.  Your email just made me think of it,
because I agree that catcache/relcache bloat is a serious issue.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company


--
Sent via pgsql-hackers mailing list ([hidden email])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Reply | Threaded
Open this post in threaded view
|

Re: Protect syscache from bloating with negative cache entries

Craig Ringer-3
On 20 December 2016 at 21:59, Robert Haas <[hidden email]> wrote:

> We could implement this by having
> some process, like the background writer,
> SendProcSignal(PROCSIG_HOUSEKEEPING) to every process in the system
> every 10 minutes or so.

... on a rolling basis.

Otherwise that'll be no fun at all, especially with some of those
lovely "we kept getting errors so we raised max_connections to 5000"
systems out there. But also on more sensibly configured ones that're
busy and want nice smooth performance without stalls.

--
 Craig Ringer                   http://www.2ndQuadrant.com/
 PostgreSQL Development, 24x7 Support, Training & Services


--
Sent via pgsql-hackers mailing list ([hidden email])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Reply | Threaded
Open this post in threaded view
|

Re: Protect syscache from bloating with negative cache entries

Tom Lane-2
Craig Ringer <[hidden email]> writes:
> On 20 December 2016 at 21:59, Robert Haas <[hidden email]> wrote:
>> We could implement this by having
>> some process, like the background writer,
>> SendProcSignal(PROCSIG_HOUSEKEEPING) to every process in the system
>> every 10 minutes or so.

> ... on a rolling basis.

I don't understand why we'd make that a system-wide behavior at all,
rather than expecting each process to manage its own cache.

                        regards, tom lane


--
Sent via pgsql-hackers mailing list ([hidden email])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Reply | Threaded
Open this post in threaded view
|

Re: Protect syscache from bloating with negative cache entries

Robert Haas
On Tue, Dec 20, 2016 at 10:09 AM, Tom Lane <[hidden email]> wrote:

> Craig Ringer <[hidden email]> writes:
>> On 20 December 2016 at 21:59, Robert Haas <[hidden email]> wrote:
>>> We could implement this by having
>>> some process, like the background writer,
>>> SendProcSignal(PROCSIG_HOUSEKEEPING) to every process in the system
>>> every 10 minutes or so.
>
>> ... on a rolling basis.
>
> I don't understand why we'd make that a system-wide behavior at all,
> rather than expecting each process to manage its own cache.

Individual backends don't have a really great way to do time-based
stuff, do they?  I mean, yes, there is enable_timeout() and friends,
but I think that requires quite a bit of bookkeeping.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company


--
Sent via pgsql-hackers mailing list ([hidden email])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Reply | Threaded
Open this post in threaded view
|

Re: Protect syscache from bloating with negative cache entries

Tom Lane-2
Robert Haas <[hidden email]> writes:
> On Tue, Dec 20, 2016 at 10:09 AM, Tom Lane <[hidden email]> wrote:
>> I don't understand why we'd make that a system-wide behavior at all,
>> rather than expecting each process to manage its own cache.

> Individual backends don't have a really great way to do time-based
> stuff, do they?  I mean, yes, there is enable_timeout() and friends,
> but I think that requires quite a bit of bookkeeping.

If I thought that "every ten minutes" was an ideal way to manage this,
I might worry about that, but it doesn't really sound promising at all.
Every so many queries would likely work better, or better yet make it
self-adaptive depending on how much is in the local syscache.

The bigger picture here though is that we used to have limits on syscache
size, and we got rid of them (commit 8b9bc234a, see also
https://www.postgresql.org/message-id/flat/5141.1150327541%40sss.pgh.pa.us)
not only because of the problem you mentioned about performance falling
off a cliff once the working-set size exceeded the arbitrary limit, but
also because enforcing the limit added significant overhead --- and did so
whether or not you got any benefit from it, ie even if the limit is never
reached.  Maybe the present patch avoids imposing a pile of overhead in
situations where no pruning is needed, but it doesn't really look very
promising from that angle in a quick once-over.

BTW, I don't see the point of the second patch at all?  Surely, if
an object is deleted or updated, we already have code that flushes
related catcache entries.  Otherwise the caches would deliver wrong
data.

                        regards, tom lane


--
Sent via pgsql-hackers mailing list ([hidden email])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Reply | Threaded
Open this post in threaded view
|

Re: Protect syscache from bloating with negative cache entries

Robert Haas
On Tue, Dec 20, 2016 at 3:10 PM, Tom Lane <[hidden email]> wrote:

> Robert Haas <[hidden email]> writes:
>> On Tue, Dec 20, 2016 at 10:09 AM, Tom Lane <[hidden email]> wrote:
>>> I don't understand why we'd make that a system-wide behavior at all,
>>> rather than expecting each process to manage its own cache.
>
>> Individual backends don't have a really great way to do time-based
>> stuff, do they?  I mean, yes, there is enable_timeout() and friends,
>> but I think that requires quite a bit of bookkeeping.
>
> If I thought that "every ten minutes" was an ideal way to manage this,
> I might worry about that, but it doesn't really sound promising at all.
> Every so many queries would likely work better, or better yet make it
> self-adaptive depending on how much is in the local syscache.

I don't think "every so many queries" is very promising at all.
First, it has the same problem as a fixed cap on the number of
entries: if you're doing a round-robin just slightly bigger than that
value, performance will be poor.  Second, what's really important here
is to keep the percentage of wall-clock time spent populating the
system caches small.  If a backend is doing 4000 queries/second and
each of those 4000 queries touches a different table, it really needs
a cache of at least 4000 entries or it will thrash and slow way down.
But if it's doing a query every 10 minutes and those queries
round-robin between 4000 different tables, it doesn't really need a
4000-entry cache.  If those queries are long-running, the time to
repopulate the cache will only be a tiny fraction of runtime.  If the
queries are short-running, then the effect is, percentage-wise, just
the same as for the high-volume system, but in practice it isn't
likely to be felt as much.  I mean, if we keep a bunch of old cache
entries around on a mostly-idle backend, they are going to be pushed
out of CPU caches and maybe even paged out.  One can't expect a
backend that is woken up after a long sleep to be quite as snappy as
one that's continuously active.

Which gets to my third point: anything that's based on number of
queries won't do anything to help the case where backends sometimes go
idle and sit there for long periods.  Reducing resource utilization in
that case would be beneficial.  Ideally I'd like to get rid of not
only the backend-local cache contents but the backend itself, but
that's a much harder project.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company


--
Sent via pgsql-hackers mailing list ([hidden email])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Reply | Threaded
Open this post in threaded view
|

Re: Protect syscache from bloating with negative cache entries

Kyotaro HORIGUCHI-2
In reply to this post by Tom Lane-2
Thank you for the discussion.

At Tue, 20 Dec 2016 15:10:21 -0500, Tom Lane <[hidden email]> wrote in <[hidden email]>

> The bigger picture here though is that we used to have limits on syscache
> size, and we got rid of them (commit 8b9bc234a, see also
> https://www.postgresql.org/message-id/flat/5141.1150327541%40sss.pgh.pa.us)
> not only because of the problem you mentioned about performance falling
> off a cliff once the working-set size exceeded the arbitrary limit, but
> also because enforcing the limit added significant overhead --- and did so
> whether or not you got any benefit from it, ie even if the limit is never
> reached.  Maybe the present patch avoids imposing a pile of overhead in
> situations where no pruning is needed, but it doesn't really look very
> promising from that angle in a quick once-over.

Indeed. As mentioned in the mail at the beginning of this thread,
it hits the whole-cache scanning if at least one negative cache
exists even it is not in a relation with the target relid, and it
can be significantly long on a fat cache.

Lists of negative entries like CatCacheList would help but needs
additional memeory.

> BTW, I don't see the point of the second patch at all?  Surely, if
> an object is deleted or updated, we already have code that flushes
> related catcache entries.  Otherwise the caches would deliver wrong
> data.

Maybe you take the patch wrongly. Negative entires won't be
flushed by any means. Deletion of a namespace causes cascaded
object deletion according to dependency then finaly goes to
non-neative cache invalidation. But a removal of *negative
entries* in RELNAMENSP won't happen.

The test script for the case (gen2.pl) does the following thing,

CREATE SCHEMA foo;
SELECT * FROM foo.invalid;
DROP SCHEMA foo;

Removing the schema foo leaves a negative cache entry for
'foo.invalid' in RELNAMENSP.

However, I'm not sure the above situation happens so frequent
that it is worthwhile to amend.


regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center




--
Sent via pgsql-hackers mailing list ([hidden email])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Reply | Threaded
Open this post in threaded view
|

Re: Protect syscache from bloating with negative cache entries

Kyotaro HORIGUCHI-2
At Wed, 21 Dec 2016 10:21:09 +0900 (Tokyo Standard Time), Kyotaro HORIGUCHI <[hidden email]> wrote in <[hidden email]>

> At Tue, 20 Dec 2016 15:10:21 -0500, Tom Lane <[hidden email]> wrote in <[hidden email]>
> > The bigger picture here though is that we used to have limits on syscache
> > size, and we got rid of them (commit 8b9bc234a, see also
> > https://www.postgresql.org/message-id/flat/5141.1150327541%40sss.pgh.pa.us)
> > not only because of the problem you mentioned about performance falling
> > off a cliff once the working-set size exceeded the arbitrary limit, but
> > also because enforcing the limit added significant overhead --- and did so
> > whether or not you got any benefit from it, ie even if the limit is never
> > reached.  Maybe the present patch avoids imposing a pile of overhead in
> > situations where no pruning is needed, but it doesn't really look very
> > promising from that angle in a quick once-over.
>
> Indeed. As mentioned in the mail at the beginning of this thread,
> it hits the whole-cache scanning if at least one negative cache
> exists even it is not in a relation with the target relid, and it
> can be significantly long on a fat cache.
>
> Lists of negative entries like CatCacheList would help but needs
> additional memeory.
>
> > BTW, I don't see the point of the second patch at all?  Surely, if
> > an object is deleted or updated, we already have code that flushes
> > related catcache entries.  Otherwise the caches would deliver wrong
> > data.
>
> Maybe you take the patch wrongly. Negative entires won't be
> flushed by any means. Deletion of a namespace causes cascaded
> object deletion according to dependency then finaly goes to
> non-neative cache invalidation. But a removal of *negative
> entries* in RELNAMENSP won't happen.
>
> The test script for the case (gen2.pl) does the following thing,
>
> CREATE SCHEMA foo;
> SELECT * FROM foo.invalid;
> DROP SCHEMA foo;
>
> Removing the schema foo leaves a negative cache entry for
> 'foo.invalid' in RELNAMENSP.
>
> However, I'm not sure the above situation happens so frequent
> that it is worthwhile to amend.
Since 1753b1b conflicts this patch, I rebased this onto the
current master HEAD. I'll register this to the next CF.

The points of discussion are the following, I think.

1. The first patch seems working well. It costs the time to scan
   the whole of a catcache that have negative entries for other
   reloids. However, such negative entries are created by rather
   unusual usages. Accesing to undefined columns, and accessing
   columns on which no statistics have created. The
   whole-catcache scan occurs on ATTNAME, ATTNUM and
   STATRELATTINH for every invalidation of a relcache entry.

2. The second patch also works, but flushing negative entries by
   hash values is inefficient. It scans the bucket corresponding
   to given hash value for OIDs, then flushing negative entries
   iterating over all the collected OIDs. So this costs more time
   than 1 and flushes involving entries that is not necessary to
   be removed. If this feature is valuable but such side effects
   are not acceptable, new invalidation category based on
   cacheid-oid pair would be needed.

regards,

--
Kyotaro Horiguchi
NTT Open Source Software Center

From ee0cc13f70d79f23ec9507cf977228bba091bc49 Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <[hidden email]>
Date: Thu, 15 Dec 2016 17:43:03 +0900
Subject: [PATCH 1/2] Cleanup negative cache of pg_statistic when dropping a
 relation.

Accessing columns that don't have statistics causes leaves negative
entries in catcache for pg_statstic, but there's no chance to remove
them. Especially when repeatedly creating then dropping temporary
tables bloats catcache so much that memory pressure can be
significant. This patch removes negative entries in STATRELATTINH,
ATTNAME and ATTNUM when corresponding relation is dropped.
---
 src/backend/utils/cache/catcache.c |  57 +++++++-
 src/backend/utils/cache/syscache.c | 277 +++++++++++++++++++++++++++----------
 src/include/utils/catcache.h       |   3 +
 src/include/utils/syscache.h       |   2 +
 4 files changed, 265 insertions(+), 74 deletions(-)

diff --git a/src/backend/utils/cache/catcache.c b/src/backend/utils/cache/catcache.c
index 6016d19..c1d9d2f 100644
--- a/src/backend/utils/cache/catcache.c
+++ b/src/backend/utils/cache/catcache.c
@@ -304,10 +304,11 @@ CatCachePrintStats(int code, Datum arg)
 
  if (cache->cc_ntup == 0 && cache->cc_searches == 0)
  continue; /* don't print unused caches */
- elog(DEBUG2, "catcache %s/%u: %d tup, %ld srch, %ld+%ld=%ld hits, %ld+%ld=%ld loads, %ld invals, %ld lsrch, %ld lhits",
+ elog(DEBUG2, "catcache %s/%u: %d tup, %d negtup, %ld srch, %ld+%ld=%ld hits, %ld+%ld=%ld loads, %ld invals, %ld lsrch, %ld lhits",
  cache->cc_relname,
  cache->cc_indexoid,
  cache->cc_ntup,
+ cache->cc_nnegtup,
  cache->cc_searches,
  cache->cc_hits,
  cache->cc_neg_hits,
@@ -374,6 +375,10 @@ CatCacheRemoveCTup(CatCache *cache, CatCTup *ct)
  /* free associated tuple data */
  if (ct->tuple.t_data != NULL)
  pfree(ct->tuple.t_data);
+
+ if (ct->negative)
+ --cache->cc_nnegtup;
+
  pfree(ct);
 
  --cache->cc_ntup;
@@ -637,6 +642,49 @@ ResetCatalogCache(CatCache *cache)
 }
 
 /*
+ * CleanupCatCacheNegEntries
+ *
+ * Remove negative cache tuples maching a partial key.
+ *
+ */
+void
+CleanupCatCacheNegEntries(CatCache *cache, ScanKeyData *skey)
+{
+ int i;
+
+ /* If this cache has no negative entries, nothing to do */
+ if (cache->cc_nnegtup == 0)
+ return;
+
+ /* searching with a paritial key needs scanning the whole cache */
+ for (i = 0; i < cache->cc_nbuckets; i++)
+ {
+ dlist_head *bucket = &cache->cc_bucket[i];
+ dlist_mutable_iter iter;
+
+ dlist_foreach_modify(iter, bucket)
+ {
+ CatCTup    *ct = dlist_container(CatCTup, cache_elem, iter.cur);
+ bool res;
+
+ if (!ct->negative)
+ continue;
+
+ HeapKeyTest(&ct->tuple, cache->cc_tupdesc, 1, skey, res);
+ if (!res)
+ continue;
+
+ /*
+ * a negative cache entry cannot be refferenced so we can remove
+ * it unconditionally
+ */
+ CatCacheRemoveCTup(cache, ct);
+ }
+ }
+}
+
+
+/*
  * ResetCatalogCaches
  *
  * Reset all caches when a shared cache inval event forces it
@@ -783,6 +831,7 @@ InitCatCache(int id,
  cp->cc_relisshared = false; /* temporary */
  cp->cc_tupdesc = (TupleDesc) NULL;
  cp->cc_ntup = 0;
+ cp->cc_nnegtup = 0;
  cp->cc_nbuckets = nbuckets;
  cp->cc_nkeys = nkeys;
  for (i = 0; i < nkeys; ++i)
@@ -1279,8 +1328,8 @@ SearchCatCache(CatCache *cache,
 
  CACHE4_elog(DEBUG2, "SearchCatCache(%s): Contains %d/%d tuples",
  cache->cc_relname, cache->cc_ntup, CacheHdr->ch_ntup);
- CACHE3_elog(DEBUG2, "SearchCatCache(%s): put neg entry in bucket %d",
- cache->cc_relname, hashIndex);
+ CACHE4_elog(DEBUG2, "SearchCatCache(%s): put neg entry in bucket %d, total %d",
+ cache->cc_relname, hashIndex, cache->cc_nnegtup);
 
  /*
  * We are not returning the negative entry to the caller, so leave its
@@ -1731,6 +1780,8 @@ CatalogCacheCreateEntry(CatCache *cache, HeapTuple ntp,
 
  cache->cc_ntup++;
  CacheHdr->ch_ntup++;
+ if (negative)
+ cache->cc_nnegtup++;
 
  /*
  * If the hash table has become too full, enlarge the buckets array. Quite
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index e87fe0e..e1ba693 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -70,6 +70,8 @@
 #include "catalog/pg_user_mapping.h"
 #include "utils/rel.h"
 #include "utils/catcache.h"
+#include "utils/fmgroids.h"
+#include "utils/inval.h"
 #include "utils/syscache.h"
 
 
@@ -115,6 +117,9 @@ struct cachedesc
  int nkeys; /* # of keys needed for cache lookup */
  int key[4]; /* attribute numbers of key attrs */
  int nbuckets; /* number of hash buckets for this cache */
+
+ /* relcache invalidation stuff */
+ AttrNumber relattrnum; /* attr number of reloid, 0 if nothing to do */
 };
 
 static const struct cachedesc cacheinfo[] = {
@@ -127,7 +132,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 16
+ 16,
+ 0
  },
  {AccessMethodRelationId, /* AMNAME */
  AmNameIndexId,
@@ -138,7 +144,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 4
+ 4,
+ 0
  },
  {AccessMethodRelationId, /* AMOID */
  AmOidIndexId,
@@ -149,7 +156,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 4
+ 4,
+ 0
  },
  {AccessMethodOperatorRelationId, /* AMOPOPID */
  AccessMethodOperatorIndexId,
@@ -160,7 +168,8 @@ static const struct cachedesc cacheinfo[] = {
  Anum_pg_amop_amopfamily,
  0
  },
- 64
+ 64,
+ 0
  },
  {AccessMethodOperatorRelationId, /* AMOPSTRATEGY */
  AccessMethodStrategyIndexId,
@@ -171,7 +180,8 @@ static const struct cachedesc cacheinfo[] = {
  Anum_pg_amop_amoprighttype,
  Anum_pg_amop_amopstrategy
  },
- 64
+ 64,
+ 0
  },
  {AccessMethodProcedureRelationId, /* AMPROCNUM */
  AccessMethodProcedureIndexId,
@@ -182,7 +192,8 @@ static const struct cachedesc cacheinfo[] = {
  Anum_pg_amproc_amprocrighttype,
  Anum_pg_amproc_amprocnum
  },
- 16
+ 16,
+ 0
  },
  {AttributeRelationId, /* ATTNAME */
  AttributeRelidNameIndexId,
@@ -193,7 +204,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 32
+ 32,
+ Anum_pg_attribute_attrelid
  },
  {AttributeRelationId, /* ATTNUM */
  AttributeRelidNumIndexId,
@@ -204,7 +216,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 128
+ 128,
+ Anum_pg_attribute_attrelid
  },
  {AuthMemRelationId, /* AUTHMEMMEMROLE */
  AuthMemMemRoleIndexId,
@@ -215,7 +228,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 8
+ 8,
+ 0
  },
  {AuthMemRelationId, /* AUTHMEMROLEMEM */
  AuthMemRoleMemIndexId,
@@ -226,7 +240,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 8
+ 8,
+ 0
  },
  {AuthIdRelationId, /* AUTHNAME */
  AuthIdRolnameIndexId,
@@ -237,7 +252,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 8
+ 8,
+ 0
  },
  {AuthIdRelationId, /* AUTHOID */
  AuthIdOidIndexId,
@@ -248,10 +264,10 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 8
+ 8,
+ 0
  },
- {
- CastRelationId, /* CASTSOURCETARGET */
+ {CastRelationId, /* CASTSOURCETARGET */
  CastSourceTargetIndexId,
  2,
  {
@@ -260,7 +276,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 256
+ 256,
+ 0
  },
  {OperatorClassRelationId, /* CLAAMNAMENSP */
  OpclassAmNameNspIndexId,
@@ -271,7 +288,8 @@ static const struct cachedesc cacheinfo[] = {
  Anum_pg_opclass_opcnamespace,
  0
  },
- 8
+ 8,
+ 0
  },
  {OperatorClassRelationId, /* CLAOID */
  OpclassOidIndexId,
@@ -282,7 +300,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 8
+ 8,
+ 0
  },
  {CollationRelationId, /* COLLNAMEENCNSP */
  CollationNameEncNspIndexId,
@@ -293,7 +312,8 @@ static const struct cachedesc cacheinfo[] = {
  Anum_pg_collation_collnamespace,
  0
  },
- 8
+ 8,
+ 0
  },
  {CollationRelationId, /* COLLOID */
  CollationOidIndexId,
@@ -304,7 +324,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 8
+ 8,
+ 0
  },
  {ConversionRelationId, /* CONDEFAULT */
  ConversionDefaultIndexId,
@@ -315,7 +336,8 @@ static const struct cachedesc cacheinfo[] = {
  Anum_pg_conversion_contoencoding,
  ObjectIdAttributeNumber,
  },
- 8
+ 8,
+ 0
  },
  {ConversionRelationId, /* CONNAMENSP */
  ConversionNameNspIndexId,
@@ -326,7 +348,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 8
+ 8,
+ 0
  },
  {ConstraintRelationId, /* CONSTROID */
  ConstraintOidIndexId,
@@ -337,7 +360,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 16
+ 16,
+ 0
  },
  {ConversionRelationId, /* CONVOID */
  ConversionOidIndexId,
@@ -348,7 +372,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 8
+ 8,
+ 0
  },
  {DatabaseRelationId, /* DATABASEOID */
  DatabaseOidIndexId,
@@ -359,7 +384,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 4
+ 4,
+ 0
  },
  {DefaultAclRelationId, /* DEFACLROLENSPOBJ */
  DefaultAclRoleNspObjIndexId,
@@ -370,7 +396,8 @@ static const struct cachedesc cacheinfo[] = {
  Anum_pg_default_acl_defaclobjtype,
  0
  },
- 8
+ 8,
+ 0
  },
  {EnumRelationId, /* ENUMOID */
  EnumOidIndexId,
@@ -381,7 +408,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 8
+ 8,
+ 0
  },
  {EnumRelationId, /* ENUMTYPOIDNAME */
  EnumTypIdLabelIndexId,
@@ -392,7 +420,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 8
+ 8,
+ 0
  },
  {EventTriggerRelationId, /* EVENTTRIGGERNAME */
  EventTriggerNameIndexId,
@@ -403,7 +432,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 8
+ 8,
+ 0
  },
  {EventTriggerRelationId, /* EVENTTRIGGEROID */
  EventTriggerOidIndexId,
@@ -414,7 +444,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 8
+ 8,
+ 0
  },
  {ForeignDataWrapperRelationId, /* FOREIGNDATAWRAPPERNAME */
  ForeignDataWrapperNameIndexId,
@@ -425,7 +456,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 2
+ 2,
+ 0
  },
  {ForeignDataWrapperRelationId, /* FOREIGNDATAWRAPPEROID */
  ForeignDataWrapperOidIndexId,
@@ -436,7 +468,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 2
+ 2,
+ 0
  },
  {ForeignServerRelationId, /* FOREIGNSERVERNAME */
  ForeignServerNameIndexId,
@@ -447,7 +480,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 2
+ 2,
+ 0
  },
  {ForeignServerRelationId, /* FOREIGNSERVEROID */
  ForeignServerOidIndexId,
@@ -458,7 +492,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 2
+ 2,
+ 0
  },
  {ForeignTableRelationId, /* FOREIGNTABLEREL */
  ForeignTableRelidIndexId,
@@ -469,7 +504,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 4
+ 4,
+ 0
  },
  {IndexRelationId, /* INDEXRELID */
  IndexRelidIndexId,
@@ -480,7 +516,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 64
+ 64,
+ 0
  },
  {LanguageRelationId, /* LANGNAME */
  LanguageNameIndexId,
@@ -491,7 +528,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 4
+ 4,
+ 0
  },
  {LanguageRelationId, /* LANGOID */
  LanguageOidIndexId,
@@ -502,7 +540,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 4
+ 4,
+ 0
  },
  {NamespaceRelationId, /* NAMESPACENAME */
  NamespaceNameIndexId,
@@ -513,7 +552,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 4
+ 4,
+ 0
  },
  {NamespaceRelationId, /* NAMESPACEOID */
  NamespaceOidIndexId,
@@ -524,7 +564,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 16
+ 16,
+ 0
  },
  {OperatorRelationId, /* OPERNAMENSP */
  OperatorNameNspIndexId,
@@ -535,7 +576,8 @@ static const struct cachedesc cacheinfo[] = {
  Anum_pg_operator_oprright,
  Anum_pg_operator_oprnamespace
  },
- 256
+ 256,
+ 0
  },
  {OperatorRelationId, /* OPEROID */
  OperatorOidIndexId,
@@ -546,7 +588,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 32
+ 32,
+ 0
  },
  {OperatorFamilyRelationId, /* OPFAMILYAMNAMENSP */
  OpfamilyAmNameNspIndexId,
@@ -557,7 +600,8 @@ static const struct cachedesc cacheinfo[] = {
  Anum_pg_opfamily_opfnamespace,
  0
  },
- 8
+ 8,
+ 0
  },
  {OperatorFamilyRelationId, /* OPFAMILYOID */
  OpfamilyOidIndexId,
@@ -568,7 +612,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 8
+ 8,
+ 0
  },
  {PartitionedRelationId, /* PARTRELID */
  PartitionedRelidIndexId,
@@ -579,7 +624,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 32
+ 32,
+ 0
  },
  {ProcedureRelationId, /* PROCNAMEARGSNSP */
  ProcedureNameArgsNspIndexId,
@@ -590,7 +636,8 @@ static const struct cachedesc cacheinfo[] = {
  Anum_pg_proc_pronamespace,
  0
  },
- 128
+ 128,
+ 0
  },
  {ProcedureRelationId, /* PROCOID */
  ProcedureOidIndexId,
@@ -601,7 +648,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 128
+ 128,
+ 0
  },
  {RangeRelationId, /* RANGETYPE */
  RangeTypidIndexId,
@@ -612,7 +660,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 4
+ 4,
+ 0
  },
  {RelationRelationId, /* RELNAMENSP */
  ClassNameNspIndexId,
@@ -623,7 +672,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 128
+ 128,
+ 0
  },
  {RelationRelationId, /* RELOID */
  ClassOidIndexId,
@@ -634,7 +684,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 128
+ 128,
+ 0
  },
  {ReplicationOriginRelationId, /* REPLORIGIDENT */
  ReplicationOriginIdentIndex,
@@ -645,7 +696,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 16
+ 16,
+ 0
  },
  {ReplicationOriginRelationId, /* REPLORIGNAME */
  ReplicationOriginNameIndex,
@@ -656,7 +708,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 16
+ 16,
+ 0
  },
  {RewriteRelationId, /* RULERELNAME */
  RewriteRelRulenameIndexId,
@@ -667,7 +720,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 8
+ 8,
+ 0
  },
  {SequenceRelationId, /* SEQRELID */
  SequenceRelidIndexId,
@@ -678,7 +732,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 32
+ 32,
+ 0
  },
  {StatisticRelationId, /* STATRELATTINH */
  StatisticRelidAttnumInhIndexId,
@@ -689,7 +744,8 @@ static const struct cachedesc cacheinfo[] = {
  Anum_pg_statistic_stainherit,
  0
  },
- 128
+ 128,
+ Anum_pg_statistic_starelid
  },
  {TableSpaceRelationId, /* TABLESPACEOID */
  TablespaceOidIndexId,
@@ -700,7 +756,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0,
  },
- 4
+ 4,
+ 0
  },
  {TransformRelationId, /* TRFOID */
  TransformOidIndexId,
@@ -711,7 +768,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0,
  },
- 16
+ 16,
+ 0
  },
  {TransformRelationId, /* TRFTYPELANG */
  TransformTypeLangIndexId,
@@ -722,7 +780,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0,
  },
- 16
+ 16,
+ 0
  },
  {TSConfigMapRelationId, /* TSCONFIGMAP */
  TSConfigMapIndexId,
@@ -733,7 +792,8 @@ static const struct cachedesc cacheinfo[] = {
  Anum_pg_ts_config_map_mapseqno,
  0
  },
- 2
+ 2,
+ 0
  },
  {TSConfigRelationId, /* TSCONFIGNAMENSP */
  TSConfigNameNspIndexId,
@@ -744,7 +804,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 2
+ 2,
+ 0
  },
  {TSConfigRelationId, /* TSCONFIGOID */
  TSConfigOidIndexId,
@@ -755,7 +816,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 2
+ 2,
+ 0
  },
  {TSDictionaryRelationId, /* TSDICTNAMENSP */
  TSDictionaryNameNspIndexId,
@@ -766,7 +828,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 2
+ 2,
+ 0
  },
  {TSDictionaryRelationId, /* TSDICTOID */
  TSDictionaryOidIndexId,
@@ -777,7 +840,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 2
+ 2,
+ 0
  },
  {TSParserRelationId, /* TSPARSERNAMENSP */
  TSParserNameNspIndexId,
@@ -788,7 +852,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 2
+ 2,
+ 0
  },
  {TSParserRelationId, /* TSPARSEROID */
  TSParserOidIndexId,
@@ -799,7 +864,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 2
+ 2,
+ 0
  },
  {TSTemplateRelationId, /* TSTEMPLATENAMENSP */
  TSTemplateNameNspIndexId,
@@ -810,7 +876,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 2
+ 2,
+ 0
  },
  {TSTemplateRelationId, /* TSTEMPLATEOID */
  TSTemplateOidIndexId,
@@ -821,7 +888,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 2
+ 2,
+ 0
  },
  {TypeRelationId, /* TYPENAMENSP */
  TypeNameNspIndexId,
@@ -832,7 +900,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 64
+ 64,
+ 0
  },
  {TypeRelationId, /* TYPEOID */
  TypeOidIndexId,
@@ -843,7 +912,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 64
+ 64,
+ 0
  },
  {UserMappingRelationId, /* USERMAPPINGOID */
  UserMappingOidIndexId,
@@ -854,7 +924,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 2
+ 2,
+ 0
  },
  {UserMappingRelationId, /* USERMAPPINGUSERSERVER */
  UserMappingUserServerIndexId,
@@ -865,7 +936,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  0
  },
- 2
+ 2,
+ 0
  }
 };
 
@@ -883,8 +955,23 @@ static int SysCacheRelationOidSize;
 static Oid SysCacheSupportingRelOid[SysCacheSize * 2];
 static int SysCacheSupportingRelOidSize;
 
-static int oid_compare(const void *a, const void *b);
+/*
+ * stuff for negative cache flushing by relcache invalidation
+ */
+#define MAX_RELINVAL_CALLBACKS 4
+typedef struct RELINVALCBParam
+{
+ CatCache *cache;
+ int  relkeynum;
+}  RELINVALCBParam;
+
+RELINVALCBParam relinval_callback_list[MAX_RELINVAL_CALLBACKS];
+static int relinval_callback_count = 0;
 
+static ScanKeyData oideqscankey; /* ScanKey for reloid match  */
+
+static int oid_compare(const void *a, const void *b);
+static void SysCacheRelInvalCallback(Datum arg, Oid reloid);
 
 /*
  * InitCatalogCache - initialize the caches
@@ -925,6 +1012,21 @@ InitCatalogCache(void)
  cacheinfo[cacheId].indoid;
  /* see comments for RelationInvalidatesSnapshotsOnly */
  Assert(!RelationInvalidatesSnapshotsOnly(cacheinfo[cacheId].reloid));
+
+ /*
+ * If this syscache has something to do with relcache invalidation,
+ * register a callback
+ */
+ if (cacheinfo[cacheId].relattrnum > 0)
+ {
+ Assert(relinval_callback_count < MAX_RELINVAL_CALLBACKS);
+
+ relinval_callback_list[relinval_callback_count].cache  =
+ SysCache[cacheId];
+ relinval_callback_list[relinval_callback_count].relkeynum =
+ cacheinfo[cacheId].relattrnum;
+ relinval_callback_count++;
+ }
  }
 
  Assert(SysCacheRelationOidSize <= lengthof(SysCacheRelationOid));
@@ -949,10 +1051,43 @@ InitCatalogCache(void)
  }
  SysCacheSupportingRelOidSize = j + 1;
 
+ /*
+ * If any of syscache needs relcache invalidation callback, prepare the
+ * scankey for reloid comparison and register a relcache inval callback.
+ */
+ if (relinval_callback_count > 0)
+ {
+ oideqscankey.sk_strategy = BTEqualStrategyNumber;
+ oideqscankey.sk_subtype = InvalidOid;
+ oideqscankey.sk_collation = InvalidOid;
+ fmgr_info_cxt(F_OIDEQ, &oideqscankey.sk_func, CacheMemoryContext);
+ CacheRegisterRelcacheCallback(SysCacheRelInvalCallback, (Datum) 0);
+ }
+
  CacheInitialized = true;
 }
 
 /*
+ * Callback function for negative cache flushing by relcache invalidation
+ * scankey for this funciton is prepared in InitCatalogCache.
+ */
+static void
+SysCacheRelInvalCallback(Datum arg, Oid reloid)
+{
+ int i;
+
+ for(i = 0 ; i < relinval_callback_count ; i++)
+ {
+ ScanKeyData skey;
+
+ memcpy(&skey, &oideqscankey, sizeof(skey));
+ skey.sk_attno = relinval_callback_list[i].relkeynum;
+ skey.sk_argument = ObjectIdGetDatum(reloid);
+ CleanupCatCacheNegEntries(relinval_callback_list[i].cache, &skey);
+ }
+}
+
+/*
  * InitCatalogCachePhase2 - finish initializing the caches
  *
  * Finish initializing all the caches, including necessary database
diff --git a/src/include/utils/catcache.h b/src/include/utils/catcache.h
index 253c7b5..cb662c0 100644
--- a/src/include/utils/catcache.h
+++ b/src/include/utils/catcache.h
@@ -44,6 +44,7 @@ typedef struct catcache
  bool cc_relisshared; /* is relation shared across databases? */
  TupleDesc cc_tupdesc; /* tuple descriptor (copied from reldesc) */
  int cc_ntup; /* # of tuples currently in this cache */
+ int cc_nnegtup; /* # of negative tuples */
  int cc_nbuckets; /* # of hash buckets in this cache */
  int cc_nkeys; /* # of keys (1..CATCACHE_MAXKEYS) */
  int cc_key[CATCACHE_MAXKEYS]; /* AttrNumber of each key */
@@ -183,6 +184,8 @@ extern CatCList *SearchCatCacheList(CatCache *cache, int nkeys,
    Datum v3, Datum v4);
 extern void ReleaseCatCacheList(CatCList *list);
 
+extern void
+CleanupCatCacheNegEntries(CatCache *cache, ScanKeyData *skey);
 extern void ResetCatalogCaches(void);
 extern void CatalogCacheFlushCatalog(Oid catId);
 extern void CatalogCacheIdInvalidate(int cacheId, uint32 hashValue);
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index 4b7631e..22fea26 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -107,6 +107,8 @@ extern void InitCatalogCachePhase2(void);
 extern HeapTuple SearchSysCache(int cacheId,
    Datum key1, Datum key2, Datum key3, Datum key4);
 extern void ReleaseSysCache(HeapTuple tuple);
+extern void CleanupNegativeCache(int cacheid, int nkeys,
+ Datum key1, Datum key2, Datum key3, Datum key4);
 
 /* convenience routines */
 extern HeapTuple SearchSysCacheCopy(int cacheId,
--
2.9.2


From 78e201522b53c1e11111f3ad411cff62a4516d10 Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <[hidden email]>
Date: Fri, 16 Dec 2016 16:44:40 +0900
Subject: [PATCH 2/2] Cleanup negative cache of pg_class when dropping a schema

This feature in turn is triggered by catcache invalidation. This patch
provides a syscache invalidation callback to flush negative cache
entries corresponding to an invalidated object.
---
 src/backend/utils/cache/catcache.c |  42 ++++++
 src/backend/utils/cache/inval.c    |   8 +-
 src/backend/utils/cache/syscache.c | 301 ++++++++++++++++++++++++++++---------
 src/include/utils/catcache.h       |   3 +
 4 files changed, 283 insertions(+), 71 deletions(-)

diff --git a/src/backend/utils/cache/catcache.c b/src/backend/utils/cache/catcache.c
index c1d9d2f..094bc60 100644
--- a/src/backend/utils/cache/catcache.c
+++ b/src/backend/utils/cache/catcache.c
@@ -1424,6 +1424,48 @@ GetCatCacheHashValue(CatCache *cache,
  return CatalogCacheComputeHashValue(cache, cache->cc_nkeys, cur_skey);
 }
 
+/*
+ * CollectOIDsForHashValue
+ *
+ * Collect OIDs corresnpond to a hash value. attnum is the column to retrieve
+ * the OIDs.
+ */
+List *
+CollectOIDsForHashValue(CatCache *cache, uint32 hashValue, int attnum)
+{
+ Index hashIndex = HASH_INDEX(hashValue, cache->cc_nbuckets);
+ dlist_head *bucket = &cache->cc_bucket[hashIndex];
+ dlist_iter iter;
+ List *ret = NIL;
+
+ /* Nothing to return before initialized */
+ if (cache->cc_tupdesc == NULL)
+ return ret;
+
+ /* Currently only OID key is supported */
+ Assert(attnum <= cache->cc_tupdesc->natts);
+ Assert(attnum < 0 ? attnum == ObjectIdAttributeNumber :
+   cache->cc_tupdesc->attrs[attnum]->atttypid == OIDOID);
+
+ dlist_foreach(iter, bucket)
+ {
+ CatCTup *ct = dlist_container(CatCTup, cache_elem, iter.cur);
+ bool isNull;
+ Datum oid;
+
+ if (ct->dead)
+ continue; /* ignore dead entries */
+
+ if (ct->hash_value != hashValue)
+ continue; /* quickly skip entry if wrong hash val */
+
+ oid = heap_getattr(&ct->tuple, attnum, cache->cc_tupdesc, &isNull);
+ if (!isNull)
+ ret = lappend_oid(ret, DatumGetObjectId(oid));
+ }
+
+ return ret;
+}
 
 /*
  * SearchCatCacheList
diff --git a/src/backend/utils/cache/inval.c b/src/backend/utils/cache/inval.c
index 5803518..0290974 100644
--- a/src/backend/utils/cache/inval.c
+++ b/src/backend/utils/cache/inval.c
@@ -543,9 +543,13 @@ LocalExecuteInvalidationMessage(SharedInvalidationMessage *msg)
  {
  InvalidateCatalogSnapshot();
 
- CatalogCacheIdInvalidate(msg->cc.id, msg->cc.hashValue);
-
+ /*
+ * Call the callbacks first so that the callbacks can access the
+ * entries corresponding to the hashValue.
+ */
  CallSyscacheCallbacks(msg->cc.id, msg->cc.hashValue);
+
+ CatalogCacheIdInvalidate(msg->cc.id, msg->cc.hashValue);
  }
  }
  else if (msg->id == SHAREDINVALCATALOG_ID)
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index e1ba693..cfcf4cd 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -108,6 +108,16 @@
 */
 
 /*
+ * struct for flushing negative cache by syscache invalidation
+ */
+typedef struct SysCacheCBParam_T
+{
+ int trig_attnum;
+ int target_cacheid;
+ ScanKeyData skey;
+} SysCacheCBParam;
+
+/*
  * struct cachedesc: information defining a single syscache
  */
 struct cachedesc
@@ -120,6 +130,14 @@ struct cachedesc
 
  /* relcache invalidation stuff */
  AttrNumber relattrnum; /* attr number of reloid, 0 if nothing to do */
+
+ /* catcache invalidation stuff */
+ int trig_cacheid; /* cache id of triggering syscache: -1 means
+ * no triggering cache */
+ int16 trig_attnum; /* key column in triggering cache. Must be an
+ * OID */
+ int16 target_attnum; /* corresponding column in this cache. Must be
+ * an OID*/
 };
 
 static const struct cachedesc cacheinfo[] = {
@@ -133,7 +151,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  16,
- 0
+ 0,
+ -1, 0, 0
  },
  {AccessMethodRelationId, /* AMNAME */
  AmNameIndexId,
@@ -145,7 +164,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  4,
- 0
+ 0,
+ -1, 0, 0
  },
  {AccessMethodRelationId, /* AMOID */
  AmOidIndexId,
@@ -157,7 +177,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  4,
- 0
+ 0,
+ -1, 0, 0
  },
  {AccessMethodOperatorRelationId, /* AMOPOPID */
  AccessMethodOperatorIndexId,
@@ -169,7 +190,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  64,
- 0
+ 0,
+ -1, 0, 0
  },
  {AccessMethodOperatorRelationId, /* AMOPSTRATEGY */
  AccessMethodStrategyIndexId,
@@ -181,7 +203,8 @@ static const struct cachedesc cacheinfo[] = {
  Anum_pg_amop_amopstrategy
  },
  64,
- 0
+ 0,
+ -1, 0, 0
  },
  {AccessMethodProcedureRelationId, /* AMPROCNUM */
  AccessMethodProcedureIndexId,
@@ -193,7 +216,8 @@ static const struct cachedesc cacheinfo[] = {
  Anum_pg_amproc_amprocnum
  },
  16,
- 0
+ 0,
+ -1, 0, 0
  },
  {AttributeRelationId, /* ATTNAME */
  AttributeRelidNameIndexId,
@@ -205,7 +229,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  32,
- Anum_pg_attribute_attrelid
+ Anum_pg_attribute_attrelid,
+ -1, 0, 0
  },
  {AttributeRelationId, /* ATTNUM */
  AttributeRelidNumIndexId,
@@ -217,7 +242,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  128,
- Anum_pg_attribute_attrelid
+ Anum_pg_attribute_attrelid,
+ -1, 0, 0
  },
  {AuthMemRelationId, /* AUTHMEMMEMROLE */
  AuthMemMemRoleIndexId,
@@ -229,7 +255,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  8,
- 0
+ 0,
+ -1, 0, 0
  },
  {AuthMemRelationId, /* AUTHMEMROLEMEM */
  AuthMemRoleMemIndexId,
@@ -241,7 +268,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  8,
- 0
+ 0,
+ -1, 0, 0
  },
  {AuthIdRelationId, /* AUTHNAME */
  AuthIdRolnameIndexId,
@@ -253,7 +281,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  8,
- 0
+ 0,
+ -1, 0, 0
  },
  {AuthIdRelationId, /* AUTHOID */
  AuthIdOidIndexId,
@@ -265,7 +294,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  8,
- 0
+ 0,
+ -1, 0, 0
  },
  {CastRelationId, /* CASTSOURCETARGET */
  CastSourceTargetIndexId,
@@ -277,7 +307,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  256,
- 0
+ 0,
+ -1, 0, 0
  },
  {OperatorClassRelationId, /* CLAAMNAMENSP */
  OpclassAmNameNspIndexId,
@@ -289,7 +320,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  8,
- 0
+ 0,
+ -1, 0, 0
  },
  {OperatorClassRelationId, /* CLAOID */
  OpclassOidIndexId,
@@ -301,7 +333,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  8,
- 0
+ 0,
+ -1, 0, 0
  },
  {CollationRelationId, /* COLLNAMEENCNSP */
  CollationNameEncNspIndexId,
@@ -313,7 +346,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  8,
- 0
+ 0,
+ -1, 0, 0
  },
  {CollationRelationId, /* COLLOID */
  CollationOidIndexId,
@@ -325,7 +359,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  8,
- 0
+ 0,
+ -1, 0, 0
  },
  {ConversionRelationId, /* CONDEFAULT */
  ConversionDefaultIndexId,
@@ -337,7 +372,8 @@ static const struct cachedesc cacheinfo[] = {
  ObjectIdAttributeNumber,
  },
  8,
- 0
+ 0,
+ -1, 0, 0
  },
  {ConversionRelationId, /* CONNAMENSP */
  ConversionNameNspIndexId,
@@ -349,7 +385,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  8,
- 0
+ 0,
+ -1, 0, 0
  },
  {ConstraintRelationId, /* CONSTROID */
  ConstraintOidIndexId,
@@ -361,7 +398,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  16,
- 0
+ 0,
+ -1, 0, 0
  },
  {ConversionRelationId, /* CONVOID */
  ConversionOidIndexId,
@@ -373,7 +411,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  8,
- 0
+ 0,
+ -1, 0, 0
  },
  {DatabaseRelationId, /* DATABASEOID */
  DatabaseOidIndexId,
@@ -385,7 +424,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  4,
- 0
+ 0,
+ -1, 0, 0
  },
  {DefaultAclRelationId, /* DEFACLROLENSPOBJ */
  DefaultAclRoleNspObjIndexId,
@@ -397,7 +437,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  8,
- 0
+ 0,
+ -1, 0, 0
  },
  {EnumRelationId, /* ENUMOID */
  EnumOidIndexId,
@@ -409,7 +450,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  8,
- 0
+ 0,
+ -1, 0, 0
  },
  {EnumRelationId, /* ENUMTYPOIDNAME */
  EnumTypIdLabelIndexId,
@@ -421,7 +463,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  8,
- 0
+ 0,
+ -1, 0, 0
  },
  {EventTriggerRelationId, /* EVENTTRIGGERNAME */
  EventTriggerNameIndexId,
@@ -433,7 +476,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  8,
- 0
+ 0,
+ -1, 0, 0
  },
  {EventTriggerRelationId, /* EVENTTRIGGEROID */
  EventTriggerOidIndexId,
@@ -445,7 +489,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  8,
- 0
+ 0,
+ -1, 0, 0
  },
  {ForeignDataWrapperRelationId, /* FOREIGNDATAWRAPPERNAME */
  ForeignDataWrapperNameIndexId,
@@ -457,7 +502,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  2,
- 0
+ 0,
+ -1, 0, 0
  },
  {ForeignDataWrapperRelationId, /* FOREIGNDATAWRAPPEROID */
  ForeignDataWrapperOidIndexId,
@@ -469,7 +515,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  2,
- 0
+ 0,
+ -1, 0, 0
  },
  {ForeignServerRelationId, /* FOREIGNSERVERNAME */
  ForeignServerNameIndexId,
@@ -481,7 +528,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  2,
- 0
+ 0,
+ -1, 0, 0
  },
  {ForeignServerRelationId, /* FOREIGNSERVEROID */
  ForeignServerOidIndexId,
@@ -493,7 +541,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  2,
- 0
+ 0,
+ -1, 0, 0
  },
  {ForeignTableRelationId, /* FOREIGNTABLEREL */
  ForeignTableRelidIndexId,
@@ -505,7 +554,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  4,
- 0
+ 0,
+ -1, 0, 0
  },
  {IndexRelationId, /* INDEXRELID */
  IndexRelidIndexId,
@@ -517,7 +567,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  64,
- 0
+ 0,
+ -1, 0, 0
  },
  {LanguageRelationId, /* LANGNAME */
  LanguageNameIndexId,
@@ -529,7 +580,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  4,
- 0
+ 0,
+ -1, 0, 0
  },
  {LanguageRelationId, /* LANGOID */
  LanguageOidIndexId,
@@ -541,7 +593,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  4,
- 0
+ 0,
+ -1, 0, 0
  },
  {NamespaceRelationId, /* NAMESPACENAME */
  NamespaceNameIndexId,
@@ -553,7 +606,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  4,
- 0
+ 0,
+ -1, 0, 0
  },
  {NamespaceRelationId, /* NAMESPACEOID */
  NamespaceOidIndexId,
@@ -565,7 +619,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  16,
- 0
+ 0,
+ -1, 0, 0
  },
  {OperatorRelationId, /* OPERNAMENSP */
  OperatorNameNspIndexId,
@@ -577,7 +632,8 @@ static const struct cachedesc cacheinfo[] = {
  Anum_pg_operator_oprnamespace
  },
  256,
- 0
+ 0,
+ -1, 0, 0
  },
  {OperatorRelationId, /* OPEROID */
  OperatorOidIndexId,
@@ -589,7 +645,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  32,
- 0
+ 0,
+ -1, 0, 0
  },
  {OperatorFamilyRelationId, /* OPFAMILYAMNAMENSP */
  OpfamilyAmNameNspIndexId,
@@ -601,7 +658,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  8,
- 0
+ 0,
+ -1, 0, 0
  },
  {OperatorFamilyRelationId, /* OPFAMILYOID */
  OpfamilyOidIndexId,
@@ -613,7 +671,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  8,
- 0
+ 0,
+ -1, 0, 0
  },
  {PartitionedRelationId, /* PARTRELID */
  PartitionedRelidIndexId,
@@ -625,7 +684,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  32,
- 0
+ 0,
+ -1, 0, 0
  },
  {ProcedureRelationId, /* PROCNAMEARGSNSP */
  ProcedureNameArgsNspIndexId,
@@ -637,7 +697,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  128,
- 0
+ 0,
+ -1, 0, 0
  },
  {ProcedureRelationId, /* PROCOID */
  ProcedureOidIndexId,
@@ -649,7 +710,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  128,
- 0
+ 0,
+ -1, 0, 0
  },
  {RangeRelationId, /* RANGETYPE */
  RangeTypidIndexId,
@@ -661,7 +723,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  4,
- 0
+ 0,
+ -1, 0, 0
  },
  {RelationRelationId, /* RELNAMENSP */
  ClassNameNspIndexId,
@@ -673,7 +736,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  128,
- 0
+ 0,
+ NAMESPACEOID, ObjectIdAttributeNumber, Anum_pg_class_relnamespace
  },
  {RelationRelationId, /* RELOID */
  ClassOidIndexId,
@@ -685,7 +749,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  128,
- 0
+ 0,
+ -1, 0, 0
  },
  {ReplicationOriginRelationId, /* REPLORIGIDENT */
  ReplicationOriginIdentIndex,
@@ -697,7 +762,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  16,
- 0
+ 0,
+ -1, 0, 0
  },
  {ReplicationOriginRelationId, /* REPLORIGNAME */
  ReplicationOriginNameIndex,
@@ -709,7 +775,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  16,
- 0
+ 0,
+ -1, 0, 0
  },
  {RewriteRelationId, /* RULERELNAME */
  RewriteRelRulenameIndexId,
@@ -721,7 +788,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  8,
- 0
+ 0,
+ -1, 0, 0
  },
  {SequenceRelationId, /* SEQRELID */
  SequenceRelidIndexId,
@@ -733,7 +801,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  32,
- 0
+ 0,
+ -1, 0, 0
  },
  {StatisticRelationId, /* STATRELATTINH */
  StatisticRelidAttnumInhIndexId,
@@ -745,7 +814,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  128,
- Anum_pg_statistic_starelid
+ Anum_pg_statistic_starelid,
+ -1, 0, 0
  },
  {TableSpaceRelationId, /* TABLESPACEOID */
  TablespaceOidIndexId,
@@ -757,7 +827,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  },
  4,
- 0
+ 0,
+ -1, 0, 0
  },
  {TransformRelationId, /* TRFOID */
  TransformOidIndexId,
@@ -769,7 +840,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  },
  16,
- 0
+ 0,
+ -1, 0, 0
  },
  {TransformRelationId, /* TRFTYPELANG */
  TransformTypeLangIndexId,
@@ -781,7 +853,8 @@ static const struct cachedesc cacheinfo[] = {
  0,
  },
  16,
- 0
+ 0,
+ -1, 0, 0
  },
  {TSConfigMapRelationId, /* TSCONFIGMAP */
  TSConfigMapIndexId,
@@ -793,7 +866,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  2,
- 0
+ 0,
+ -1, 0, 0
  },
  {TSConfigRelationId, /* TSCONFIGNAMENSP */
  TSConfigNameNspIndexId,
@@ -805,7 +879,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  2,
- 0
+ 0,
+ -1, 0, 0
  },
  {TSConfigRelationId, /* TSCONFIGOID */
  TSConfigOidIndexId,
@@ -817,7 +892,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  2,
- 0
+ 0,
+ -1, 0, 0
  },
  {TSDictionaryRelationId, /* TSDICTNAMENSP */
  TSDictionaryNameNspIndexId,
@@ -829,7 +905,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  2,
- 0
+ 0,
+ -1, 0, 0
  },
  {TSDictionaryRelationId, /* TSDICTOID */
  TSDictionaryOidIndexId,
@@ -841,7 +918,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  2,
- 0
+ 0,
+ -1, 0, 0
  },
  {TSParserRelationId, /* TSPARSERNAMENSP */
  TSParserNameNspIndexId,
@@ -853,7 +931,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  2,
- 0
+ 0,
+ -1, 0, 0
  },
  {TSParserRelationId, /* TSPARSEROID */
  TSParserOidIndexId,
@@ -865,7 +944,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  2,
- 0
+ 0,
+ -1, 0, 0
  },
  {TSTemplateRelationId, /* TSTEMPLATENAMENSP */
  TSTemplateNameNspIndexId,
@@ -877,7 +957,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  2,
- 0
+ 0,
+ -1, 0, 0
  },
  {TSTemplateRelationId, /* TSTEMPLATEOID */
  TSTemplateOidIndexId,
@@ -889,7 +970,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  2,
- 0
+ 0,
+ -1, 0, 0
  },
  {TypeRelationId, /* TYPENAMENSP */
  TypeNameNspIndexId,
@@ -901,7 +983,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  64,
- 0
+ 0,
+ -1, 0, 0
  },
  {TypeRelationId, /* TYPEOID */
  TypeOidIndexId,
@@ -913,7 +996,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  64,
- 0
+ 0,
+ -1, 0, 0
  },
  {UserMappingRelationId, /* USERMAPPINGOID */
  UserMappingOidIndexId,
@@ -925,7 +1009,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  2,
- 0
+ 0,
+ -1, 0, 0
  },
  {UserMappingRelationId, /* USERMAPPINGUSERSERVER */
  UserMappingUserServerIndexId,
@@ -937,7 +1022,8 @@ static const struct cachedesc cacheinfo[] = {
  0
  },
  2,
- 0
+ 0,
+ -1, 0, 0
  }
 };
 
@@ -972,7 +1058,8 @@ static ScanKeyData oideqscankey; /* ScanKey for reloid match  */
 
 static int oid_compare(const void *a, const void *b);
 static void SysCacheRelInvalCallback(Datum arg, Oid reloid);
-
+static void SysCacheSysCacheInvalCallback(Datum arg, int cacheid,
+  uint32 hashvalue);
 /*
  * InitCatalogCache - initialize the caches
  *
@@ -1027,6 +1114,34 @@ InitCatalogCache(void)
  cacheinfo[cacheId].relattrnum;
  relinval_callback_count++;
  }
+
+ /*
+ * If this syscache has syscache invalidation trigger, register
+ * it.
+ */
+ if (cacheinfo[cacheId].trig_cacheid >= 0)
+ {
+ SysCacheCBParam *param;
+
+ param = MemoryContextAlloc(CacheMemoryContext,
+   sizeof(SysCacheCBParam));
+ param->target_cacheid = cacheId;
+
+ /*
+ * XXXX: Create a scankeydata for OID comparison. We don't have a
+ * means to check the type of the column in the system catalog at
+ * this time. So we have to belive the definition.
+ */
+ fmgr_info_cxt(F_OIDEQ, &param->skey.sk_func, CacheMemoryContext);
+ param->skey.sk_attno = cacheinfo[cacheId].target_attnum;
+ param->trig_attnum = cacheinfo[cacheId].trig_attnum;
+ param->skey.sk_strategy = BTEqualStrategyNumber;
+ param->skey.sk_subtype = InvalidOid;
+ param->skey.sk_collation = InvalidOid;
+ CacheRegisterSyscacheCallback(cacheinfo[cacheId].trig_cacheid,
+  SysCacheSysCacheInvalCallback,
+  PointerGetDatum(param));
+ }
  }
 
  Assert(SysCacheRelationOidSize <= lengthof(SysCacheRelationOid));
@@ -1403,6 +1518,54 @@ RelationInvalidatesSnapshotsOnly(Oid relid)
 }
 
 /*
+ * SysCacheSysCacheInvalCallback
+ *
+ * Callback function for negative cache flushing by syscache invalidation.
+ * Fetches an OID (not restricted to system oid column) from the invalidated
+ * tuple and flushes negative entries that matches the OID in the target
+ * syscache.
+ */
+static void
+SysCacheSysCacheInvalCallback(Datum arg, int cacheid, uint32 hashValue)
+{
+ SysCacheCBParam *param;
+ CatCache *trigger_cache; /* triggering catcache */
+ CatCache *target_cache; /* target catcache */
+ List *oids;
+ ListCell *lc;
+ int trigger_cacheid = cacheid;
+ int target_cacheid;
+
+ param = (SysCacheCBParam *)DatumGetPointer(arg);
+ target_cacheid = param->target_cacheid;
+
+ trigger_cache = SysCache[trigger_cacheid];
+ target_cache = SysCache[target_cacheid];
+
+ /*
+ * collect OIDs for target syscache entries. oids contains one value for
+ * most cases, but two or more for the case hashvalue has synonyms. At
+ * least one of them is the right OID but is cannot be distinguished using
+ * the given hash value.
+ *
+ * As the result some unnecessary entries may be flushed but it won't harm
+ * so much than letting them bloat catcaches.
+ */
+ oids =
+ CollectOIDsForHashValue(trigger_cache, hashValue, param->trig_attnum);
+
+ foreach (lc, oids)
+ {
+ ScanKeyData skey;
+ Oid oid = lfirst_oid (lc);
+
+ memcpy(&skey, &param->skey, sizeof(skey));
+ skey.sk_argument = ObjectIdGetDatum(oid);
+ CleanupCatCacheNegEntries(target_cache, &skey);
+ }
+}
+
+/*
  * Test whether a relation has a system cache.
  */
 bool
diff --git a/src/include/utils/catcache.h b/src/include/utils/catcache.h
index cb662c0..b279174 100644
--- a/src/include/utils/catcache.h
+++ b/src/include/utils/catcache.h
@@ -179,6 +179,9 @@ extern uint32 GetCatCacheHashValue(CatCache *cache,
  Datum v1, Datum v2,
  Datum v3, Datum v4);
 
+extern List *CollectOIDsForHashValue(CatCache *cache,
+ uint32 hashValue, int attnum);
+
 extern CatCList *SearchCatCacheList(CatCache *cache, int nkeys,
    Datum v1, Datum v2,
    Datum v3, Datum v4);
--
2.9.2



--
Sent via pgsql-hackers mailing list ([hidden email])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Reply | Threaded
Open this post in threaded view
|

Re: Protect syscache from bloating with negative cache entries

Michael Paquier
In reply to this post by Tom Lane-2
On Wed, Dec 21, 2016 at 5:10 AM, Tom Lane <[hidden email]> wrote:

> If I thought that "every ten minutes" was an ideal way to manage this,
> I might worry about that, but it doesn't really sound promising at all.
> Every so many queries would likely work better, or better yet make it
> self-adaptive depending on how much is in the local syscache.
>
> The bigger picture here though is that we used to have limits on syscache
> size, and we got rid of them (commit 8b9bc234a, see also
> https://www.postgresql.org/message-id/flat/5141.1150327541%40sss.pgh.pa.us)
> not only because of the problem you mentioned about performance falling
> off a cliff once the working-set size exceeded the arbitrary limit, but
> also because enforcing the limit added significant overhead --- and did so
> whether or not you got any benefit from it, ie even if the limit is never
> reached.  Maybe the present patch avoids imposing a pile of overhead in
> situations where no pruning is needed, but it doesn't really look very
> promising from that angle in a quick once-over.

Have there been ever discussions about having catcache entries in a
shared memory area? This does not sound much performance-wise, I am
just wondering about the concept and I cannot find references to such
discussions.
--
Michael


--
Sent via pgsql-hackers mailing list ([hidden email])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Reply | Threaded
Open this post in threaded view
|

Re: Protect syscache from bloating with negative cache entries

Tom Lane-2
Michael Paquier <[hidden email]> writes:
> Have there been ever discussions about having catcache entries in a
> shared memory area? This does not sound much performance-wise, I am
> just wondering about the concept and I cannot find references to such
> discussions.

I'm sure it's been discussed.  Offhand I remember the following issues:

* A shared cache would create locking and contention overhead.

* A shared cache would have a very hard size limit, at least if it's
in SysV-style shared memory (perhaps DSM would let us relax that).

* Transactions that are doing DDL have a requirement for the catcache
to reflect changes that they've made locally but not yet committed,
so said changes mustn't be visible globally.

You could possibly get around the third point with a local catcache that's
searched before the shared one, but tuning that to be performant sounds
like a mess.  Also, I'm not sure how such a structure could cope with
uncommitted deletions: delete A -> remove A from local catcache, but not
the shared one -> search for A in local catcache -> not found -> search
for A in shared catcache -> found -> oops.

                        regards, tom lane


--
Sent via pgsql-hackers mailing list ([hidden email])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Reply | Threaded
Open this post in threaded view
|

Re: Protect syscache from bloating with negative cache entries

Robert Haas
On Fri, Jan 13, 2017 at 8:58 AM, Tom Lane <[hidden email]> wrote:

> Michael Paquier <[hidden email]> writes:
>> Have there been ever discussions about having catcache entries in a
>> shared memory area? This does not sound much performance-wise, I am
>> just wondering about the concept and I cannot find references to such
>> discussions.
>
> I'm sure it's been discussed.  Offhand I remember the following issues:
>
> * A shared cache would create locking and contention overhead.
>
> * A shared cache would have a very hard size limit, at least if it's
> in SysV-style shared memory (perhaps DSM would let us relax that).
>
> * Transactions that are doing DDL have a requirement for the catcache
> to reflect changes that they've made locally but not yet committed,
> so said changes mustn't be visible globally.
>
> You could possibly get around the third point with a local catcache that's
> searched before the shared one, but tuning that to be performant sounds
> like a mess.  Also, I'm not sure how such a structure could cope with
> uncommitted deletions: delete A -> remove A from local catcache, but not
> the shared one -> search for A in local catcache -> not found -> search
> for A in shared catcache -> found -> oops.

I think the first of those concerns is the key one.  If searching the
system catalogs costs $100 and searching the private catcache costs
$1, what's the cost of searching a hypothetical shared catcache?  If
the answer is $80, it's not worth doing.  If the answer is $5, it's
probably still not worth doing.  If the answer is $1.25, then it's
probably worth investing some energy into trying to solve the other
problems you list.  For some users, the memory cost of catcache and
syscache entries multiplied by N backends are a very serious problem,
so it would be nice to have some other options.  But we do so many
syscache lookups that a shared cache won't be viable unless it's
almost as fast as a backend-private cache, or at least that's my
hunch.

I think it would be interested for somebody to build a prototype here
that ignores all the problems but the first and uses some
straightforward, relatively unoptimized locking strategy for the first
problem.  Then benchmark it.  If the results show that the idea has
legs, then we can try to figure out what a real implementation would
look like.

(One possible approach: use Thomas Munro's DHT stuff to build the shared cache.)

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company


--
Sent via pgsql-hackers mailing list ([hidden email])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Reply | Threaded
Open this post in threaded view
|

Re: Protect syscache from bloating with negative cache entries

Michael Paquier
On Sat, Jan 14, 2017 at 12:32 AM, Robert Haas <[hidden email]> wrote:

> On Fri, Jan 13, 2017 at 8:58 AM, Tom Lane <[hidden email]> wrote:
>> Michael Paquier <[hidden email]> writes:
>>> Have there been ever discussions about having catcache entries in a
>>> shared memory area? This does not sound much performance-wise, I am
>>> just wondering about the concept and I cannot find references to such
>>> discussions.
>>
>> I'm sure it's been discussed.  Offhand I remember the following issues:
>>
>> * A shared cache would create locking and contention overhead.
>>
>> * A shared cache would have a very hard size limit, at least if it's
>> in SysV-style shared memory (perhaps DSM would let us relax that).
>>
>> * Transactions that are doing DDL have a requirement for the catcache
>> to reflect changes that they've made locally but not yet committed,
>> so said changes mustn't be visible globally.
>>
>> You could possibly get around the third point with a local catcache that's
>> searched before the shared one, but tuning that to be performant sounds
>> like a mess.  Also, I'm not sure how such a structure could cope with
>> uncommitted deletions: delete A -> remove A from local catcache, but not
>> the shared one -> search for A in local catcache -> not found -> search
>> for A in shared catcache -> found -> oops.
>
> I think the first of those concerns is the key one.  If searching the
> system catalogs costs $100 and searching the private catcache costs
> $1, what's the cost of searching a hypothetical shared catcache?  If
> the answer is $80, it's not worth doing.  If the answer is $5, it's
> probably still not worth doing.  If the answer is $1.25, then it's
> probably worth investing some energy into trying to solve the other
> problems you list.  For some users, the memory cost of catcache and
> syscache entries multiplied by N backends are a very serious problem,
> so it would be nice to have some other options.  But we do so many
> syscache lookups that a shared cache won't be viable unless it's
> almost as fast as a backend-private cache, or at least that's my
> hunch.

Being able to switch from one mode to another would be interesting.
Applications using extensing DDLs that require to change the catcache
with an exclusive lock would clearly pay the lock contention cost, but
do you think that be really the case of a shared lock? A bunch of
applications that I work with deploy Postgres once, then don't change
the schema except when an upgrade happens. So that would be benefitial
for that. There are even some apps that do not use pgbouncer, but drop
sessions after a timeout of inactivity to avoid a memory bloat because
of the problem of this thread. That won't solve the problem of the
local catcache bloat, but some users using few DDLs may be fine to pay
some extra concurrency cost if the session handling gets easied.

> I think it would be interested for somebody to build a prototype here
> that ignores all the problems but the first and uses some
> straightforward, relatively unoptimized locking strategy for the first
> problem.  Then benchmark it.  If the results show that the idea has
> legs, then we can try to figure out what a real implementation would
> look like.
> (One possible approach: use Thomas Munro's DHT stuff to build the shared cache.)

Yeah, I'd bet on a couple of days of focus to sort that out.
--
Michael


--
Sent via pgsql-hackers mailing list ([hidden email])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Reply | Threaded
Open this post in threaded view
|

Re: Protect syscache from bloating with negative cache entries

Tom Lane-2
Michael Paquier <[hidden email]> writes:
> ... There are even some apps that do not use pgbouncer, but drop
> sessions after a timeout of inactivity to avoid a memory bloat because
> of the problem of this thread.

Yeah, a certain company I used to work for had to do that, though their
problem had more to do with bloat in plpgsql's compiled-functions cache
(and ensuing bloat in the plancache), I believe.

Still, I'm pretty suspicious of anything that will add overhead to
catcache lookups.  If you think the performance of those is not absolutely
critical, turning off the caches via -DCLOBBER_CACHE_ALWAYS will soon
disabuse you of the error.

I'm inclined to think that a more profitable direction to look in is
finding a way to limit the cache size.  I know we got rid of exactly that
years ago, but the problems with it were (a) the mechanism was itself
pretty expensive --- a global-to-all-caches LRU list IIRC, and (b) there
wasn't a way to tune the limit.  Possibly somebody can think of some
cheaper, perhaps less precise way of aging out old entries.  As for
(b), this is the sort of problem we made GUCs for.

But, again, the catcache isn't the only source of per-process bloat
and I'm not even sure it's the main one.  A more holistic approach
might be called for.

                        regards, tom lane


--
Sent via pgsql-hackers mailing list ([hidden email])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Reply | Threaded
Open this post in threaded view
|

Re: Protect syscache from bloating with negative cache entries

Andres Freund
Hi,


On 2017-01-13 17:58:41 -0500, Tom Lane wrote:
> But, again, the catcache isn't the only source of per-process bloat
> and I'm not even sure it's the main one.  A more holistic approach
> might be called for.

It'd be helpful if we'd find a way to make it easy to get statistics
about the size of various caches in production systems. Right now that's
kinda hard, resulting in us having to make a lot of guesses...

Andres


--
Sent via pgsql-hackers mailing list ([hidden email])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Reply | Threaded
Open this post in threaded view
|

Re: Protect syscache from bloating with negative cache entries

Tomas Vondra-4
On 01/14/2017 12:06 AM, Andres Freund wrote:

> Hi,
>
>
> On 2017-01-13 17:58:41 -0500, Tom Lane wrote:
>> But, again, the catcache isn't the only source of per-process bloat
>> and I'm not even sure it's the main one.  A more holistic approach
>> might be called for.
>
> It'd be helpful if we'd find a way to make it easy to get statistics
> about the size of various caches in production systems. Right now
> that's kinda hard, resulting in us having to make a lot of
> guesses...
>

What about a simple C extension, that could inspect those caches?
Assuming it could be loaded into a single backend, that should be
relatively acceptable way (compared to loading it to all backends using
shared_preload_libraries).


--
Tomas Vondra                  http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services


--
Sent via pgsql-hackers mailing list ([hidden email])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Reply | Threaded
Open this post in threaded view
|

Re: Protect syscache from bloating with negative cache entries

Michael Paquier
On Sat, Jan 14, 2017 at 9:36 AM, Tomas Vondra
<[hidden email]> wrote:

> On 01/14/2017 12:06 AM, Andres Freund wrote:
>> On 2017-01-13 17:58:41 -0500, Tom Lane wrote:
>>>
>>> But, again, the catcache isn't the only source of per-process bloat
>>> and I'm not even sure it's the main one.  A more holistic approach
>>> might be called for.
>>
>> It'd be helpful if we'd find a way to make it easy to get statistics
>> about the size of various caches in production systems. Right now
>> that's kinda hard, resulting in us having to make a lot of
>> guesses...
>
> What about a simple C extension, that could inspect those caches? Assuming
> it could be loaded into a single backend, that should be relatively
> acceptable way (compared to loading it to all backends using
> shared_preload_libraries).

This extension could do a small amount of work on a portion of the
syscache entries at each query loop, still I am wondering if that
would not be nicer to get that in-core and configurable, which is
basically the approach proposed by Horiguchi-san. At least it seems to
me that it has some merit, and if we could make that behavior
switchable, disabled by default, that would be a win for some class of
applications. What do others think?
--
Michael


--
Sent via pgsql-hackers mailing list ([hidden email])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Reply | Threaded
Open this post in threaded view
|

Re: Protect syscache from bloating with negative cache entries

Jim Nasby-5
In reply to this post by Kyotaro HORIGUCHI-2
On 12/26/16 2:31 AM, Kyotaro HORIGUCHI wrote:
> The points of discussion are the following, I think.
>
> 1. The first patch seems working well. It costs the time to scan
>    the whole of a catcache that have negative entries for other
>    reloids. However, such negative entries are created by rather
>    unusual usages. Accesing to undefined columns, and accessing
>    columns on which no statistics have created. The
>    whole-catcache scan occurs on ATTNAME, ATTNUM and
>    STATRELATTINH for every invalidation of a relcache entry.

I took a look at this. It looks sane, though I've got a few minor
comment tweaks:

+ * Remove negative cache tuples maching a partial key.
s/maching/matching/

+/* searching with a paritial key needs scanning the whole cache */

s/needs/means/

+ * a negative cache entry cannot be referenced so we can remove

s/referenced/referenced,/

I was wondering if there's a way to test the performance impact of
deleting negative entries.

> 2. The second patch also works, but flushing negative entries by
>    hash values is inefficient. It scans the bucket corresponding
>    to given hash value for OIDs, then flushing negative entries
>    iterating over all the collected OIDs. So this costs more time
>    than 1 and flushes involving entries that is not necessary to
>    be removed. If this feature is valuable but such side effects
>    are not acceptable, new invalidation category based on
>    cacheid-oid pair would be needed.

I glanced at this and it looks sane. Didn't go any farther since this
one's pretty up in the air. ISTM it'd be better to do some kind of aging
instead of patch 2.

The other (possibly naive) question I have is how useful negative
entries really are? Will Postgres regularly incur negative lookups, or
will these only happen due to user activity? I can't think of a case
where an app would need to depend on fast negative lookup (in other
words, it should be considered a bug in the app). I can see where
getting rid of them completely might be problematic, but maybe we can
just keep a relatively small number of them around. I'm thinking a
simple LRU list of X number of negative entries; when that fills you
reuse the oldest one. You'd have to pay the LRU maintenance cost on
every negative hit, but if those shouldn't be that common it shouldn't
be bad.

That might well necessitate another GUC, but it seems a lot simpler than
most of the other ideas.
--
Jim Nasby, Data Architect, Blue Treble Consulting, Austin TX
Experts in Analytics, Data Architecture and PostgreSQL
Data in Trouble? Get it in Treble! http://BlueTreble.com
855-TREBLE2 (855-873-2532)


--
Sent via pgsql-hackers mailing list ([hidden email])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Reply | Threaded
Open this post in threaded view
|

Re: Protect syscache from bloating with negative cache entries

Tom Lane-2
Jim Nasby <[hidden email]> writes:
> The other (possibly naive) question I have is how useful negative
> entries really are? Will Postgres regularly incur negative lookups, or
> will these only happen due to user activity?

It varies depending on the particular syscache, but in at least some
of them, negative cache entries are critical for performance.
See for example RelnameGetRelid(), which basically does a RELNAMENSP
cache lookup for each schema down the search path until it finds a
match.  For any user table name with the standard search_path, there's
a guaranteed failure in pg_catalog before you can hope to find a match.
If we don't have negative cache entries, then *every invocation of this
function has to go to disk* (or at least to shared buffers).

It's possible that we could revise all our lookup patterns to avoid this
sort of thing.  But I don't have much faith in that always being possible,
and exactly none that we won't introduce new lookup patterns that need it
in future.  I spent some time, for instance, wondering if RelnameGetRelid
could use a SearchSysCacheList lookup instead, doing the lookup on table
name only and then inspecting the whole list to see which entry is
frontmost according to the current search path.  But that has performance
failure modes of its own, for example if you have identical table names in
a boatload of different schemas.  We do it that way for some other cases
such as function lookups, but I think it's much less likely that people
have identical function names in N schemas than that they have identical
table names in N schemas.

If you want to poke into this for particular test scenarios, building with
CATCACHE_STATS defined will yield a bunch of numbers dumped to the
postmaster log at each backend exit.

                        regards, tom lane


--
Sent via pgsql-hackers mailing list ([hidden email])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Reply | Threaded
Open this post in threaded view
|

Re: Protect syscache from bloating with negative cache entries

Jim Nasby-5
On 1/21/17 8:54 PM, Tom Lane wrote:
> Jim Nasby <[hidden email]> writes:
>> The other (possibly naive) question I have is how useful negative
>> entries really are? Will Postgres regularly incur negative lookups, or
>> will these only happen due to user activity?
> It varies depending on the particular syscache, but in at least some
> of them, negative cache entries are critical for performance.
> See for example RelnameGetRelid(), which basically does a RELNAMENSP
> cache lookup for each schema down the search path until it finds a
> match.

Ahh, I hadn't considered that. So one idea would be to only track
negative entries on caches where we know they're actually useful. That
might make the performance hit of some of the other ideas more
tolerable. Presumably you're much less likely to pollute the namespace
cache than some of the others.
--
Jim Nasby, Data Architect, Blue Treble Consulting, Austin TX
Experts in Analytics, Data Architecture and PostgreSQL
Data in Trouble? Get it in Treble! http://BlueTreble.com
855-TREBLE2 (855-873-2532)


--
Sent via pgsql-hackers mailing list ([hidden email])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
1234 ... 14