Re: Patch: plan invalidation vs stored procedures - Mailing list pgsql-hackers

From Martin Pihlak
Subject Re: Patch: plan invalidation vs stored procedures
Date
Msg-id 48B6709E.4090903@gmail.com
Whole thread Raw
In response to Re: Patch: plan invalidation vs stored procedures  (Tom Lane <tgl@sss.pgh.pa.us>)
Responses Re: Patch: plan invalidation vs stored procedures  (Tom Lane <tgl@sss.pgh.pa.us>)
List pgsql-hackers
Tom Lane wrote:
> I hadn't read it yet, but that makes it wrong already.  There's no need
> for any new inval traffic --- the existing syscache inval messages on
> pg_proc entries should serve fine.

Yes, creating a new message type was a bit short sighted -- attached is a patch
that uses syscache invalidation messages instead. This also adds additional
tupleId field to SharedInvalCatcacheMsg. This is used to identify the
invalidated tuple in PROC messages, for now others still pass InvalidOid.

> More generally, if we are to try to invalidate on the strength of
> pg_proc changes, what of other DDL changes?  Operators, operator
> classes, maybe?  How about renaming a schema?  I would like to see a
> line drawn between things we find worth trying to track and things we
> don't.  If there is no such line, we're going to need a patch a lot
> larger than this one.

The attached patch registers callbacks for namespace, operator and op family
catalog changes. PlanCacheCallback now takes catalog id as arg and can
take actions depending on the catalog type. Adding new catalogs is just a
matter of registering the callback in InitPlanCache. Of course, only tables
and functions have exact tracking -- other changes just invalidate all.

I'm wondering if the list of catalogs to be tracked should be fixed at all.
Maybe it would be better to call PlanCacheCallback directly on any syscache
entry invalidation? This way no catalog would be overlooked and the
cache_callback_list could be kept nice and short. PlanCacheCallback would
receive the catalog id and OID of the invalidated tuple and could then
decide whether it can do precise invalidation, flush the cache or just
skip the event. What do you think?

regards,
Martin

Index: src/backend/commands/prepare.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/backend/commands/prepare.c,v
retrieving revision 1.90
diff -c -r1.90 prepare.c
*** src/backend/commands/prepare.c    25 Aug 2008 22:42:32 -0000    1.90
--- src/backend/commands/prepare.c    28 Aug 2008 09:20:21 -0000
***************
*** 189,197 ****
      /* Shouldn't have a non-fully-planned plancache entry */
      if (!entry->plansource->fully_planned)
          elog(ERROR, "EXECUTE does not support unplanned prepared statements");
-     /* Shouldn't get any non-fixed-result cached plan, either */
-     if (!entry->plansource->fixed_result)
-         elog(ERROR, "EXECUTE does not support variable-result cached plans");

      /* Evaluate parameters, if any */
      if (entry->plansource->num_params > 0)
--- 189,194 ----
***************
*** 463,469 ****
                                    cursor_options,
                                    stmt_list,
                                    true,
!                                   true);

      /* Now we can add entry to hash table */
      entry = (PreparedStatement *) hash_search(prepared_queries,
--- 460,466 ----
                                    cursor_options,
                                    stmt_list,
                                    true,
!                                   false);

      /* Now we can add entry to hash table */
      entry = (PreparedStatement *) hash_search(prepared_queries,
***************
*** 524,534 ****
  TupleDesc
  FetchPreparedStatementResultDesc(PreparedStatement *stmt)
  {
!     /*
!      * Since we don't allow prepared statements' result tupdescs to change,
!      * there's no need for a revalidate call here.
!      */
!     Assert(stmt->plansource->fixed_result);
      if (stmt->plansource->resultDesc)
          return CreateTupleDescCopy(stmt->plansource->resultDesc);
      else
--- 521,529 ----
  TupleDesc
  FetchPreparedStatementResultDesc(PreparedStatement *stmt)
  {
!     /* Revalidate the plan to allow changes in tupdescs. */
!     RevalidateCachedPlan(stmt->plansource, false);
!
      if (stmt->plansource->resultDesc)
          return CreateTupleDescCopy(stmt->plansource->resultDesc);
      else
***************
*** 650,658 ****
      /* Shouldn't have a non-fully-planned plancache entry */
      if (!entry->plansource->fully_planned)
          elog(ERROR, "EXPLAIN EXECUTE does not support unplanned prepared statements");
-     /* Shouldn't get any non-fixed-result cached plan, either */
-     if (!entry->plansource->fixed_result)
-         elog(ERROR, "EXPLAIN EXECUTE does not support variable-result cached plans");

      /* Replan if needed, and acquire a transient refcount */
      cplan = RevalidateCachedPlan(entry->plansource, true);
--- 645,650 ----
Index: src/backend/nodes/copyfuncs.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v
retrieving revision 1.401
diff -c -r1.401 copyfuncs.c
*** src/backend/nodes/copyfuncs.c    22 Aug 2008 00:16:03 -0000    1.401
--- src/backend/nodes/copyfuncs.c    28 Aug 2008 09:20:21 -0000
***************
*** 84,89 ****
--- 84,90 ----
      COPY_NODE_FIELD(returningLists);
      COPY_NODE_FIELD(rowMarks);
      COPY_NODE_FIELD(relationOids);
+     COPY_NODE_FIELD(functionOids);
      COPY_SCALAR_FIELD(nParamExec);

      return newnode;
***************
*** 1882,1887 ****
--- 1883,1889 ----
      COPY_NODE_FIELD(limitCount);
      COPY_NODE_FIELD(rowMarks);
      COPY_NODE_FIELD(setOperations);
+     COPY_NODE_FIELD(functionOids);

      return newnode;
  }
Index: src/backend/optimizer/plan/planner.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/backend/optimizer/plan/planner.c,v
retrieving revision 1.242
diff -c -r1.242 planner.c
*** src/backend/optimizer/plan/planner.c    17 Aug 2008 01:19:59 -0000    1.242
--- src/backend/optimizer/plan/planner.c    28 Aug 2008 09:20:22 -0000
***************
*** 214,219 ****
--- 214,220 ----
      result->rowMarks = parse->rowMarks;
      result->relationOids = glob->relationOids;
      result->nParamExec = list_length(glob->paramlist);
+     result->functionOids = parse->functionOids;

      return result;
  }
Index: src/backend/parser/analyze.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/backend/parser/analyze.c,v
retrieving revision 1.377
diff -c -r1.377 analyze.c
*** src/backend/parser/analyze.c    25 Aug 2008 22:42:33 -0000    1.377
--- src/backend/parser/analyze.c    28 Aug 2008 09:20:23 -0000
***************
*** 226,231 ****
--- 226,234 ----
      result->querySource = QSRC_ORIGINAL;
      result->canSetTag = true;

+     /* Add the function oid list to Query */
+     result->functionOids = pstate->p_functionOids;
+
      return result;
  }

Index: src/backend/parser/parse_func.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/backend/parser/parse_func.c,v
retrieving revision 1.205
diff -c -r1.205 parse_func.c
*** src/backend/parser/parse_func.c    25 Aug 2008 22:42:33 -0000    1.205
--- src/backend/parser/parse_func.c    28 Aug 2008 09:20:23 -0000
***************
*** 323,328 ****
--- 323,332 ----
                       parser_errposition(pstate, location)));
      }

+     /* add the function Oid to ParseState - this is later copied to Query */
+     if (!list_member_oid(pstate->p_functionOids, funcid))
+         pstate->p_functionOids = lappend_oid(pstate->p_functionOids, funcid);
+
      return retval;
  }

Index: src/backend/tcop/postgres.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/backend/tcop/postgres.c,v
retrieving revision 1.556
diff -c -r1.556 postgres.c
*** src/backend/tcop/postgres.c    19 Aug 2008 18:30:04 -0000    1.556
--- src/backend/tcop/postgres.c    28 Aug 2008 09:20:25 -0000
***************
*** 2177,2184 ****
                       errmsg("unnamed prepared statement does not exist")));
      }

!     /* Prepared statements shouldn't have changeable result descs */
!     Assert(psrc->fixed_result);

      /*
       * If we are in aborted transaction state, we can't run
--- 2177,2184 ----
                       errmsg("unnamed prepared statement does not exist")));
      }

!     /* Revalidate the plan to allow tupdesc changes */
!     RevalidateCachedPlan(psrc, true);

      /*
       * If we are in aborted transaction state, we can't run
Index: src/backend/utils/cache/catcache.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/backend/utils/cache/catcache.c,v
retrieving revision 1.144
diff -c -r1.144 catcache.c
*** src/backend/utils/cache/catcache.c    19 Jun 2008 00:46:05 -0000    1.144
--- src/backend/utils/cache/catcache.c    28 Aug 2008 09:20:25 -0000
***************
*** 1757,1763 ****
  void
  PrepareToInvalidateCacheTuple(Relation relation,
                                HeapTuple tuple,
!                             void (*function) (int, uint32, ItemPointer, Oid))
  {
      CatCache   *ccp;
      Oid            reloid;
--- 1757,1763 ----
  void
  PrepareToInvalidateCacheTuple(Relation relation,
                                HeapTuple tuple,
!                             void (*function) (int, uint32, ItemPointer, Oid, Oid))
  {
      CatCache   *ccp;
      Oid            reloid;
***************
*** 1794,1800 ****
          (*function) (ccp->id,
                       CatalogCacheComputeTupleHashValue(ccp, tuple),
                       &tuple->t_self,
!                      ccp->cc_relisshared ? (Oid) 0 : MyDatabaseId);
      }
  }

--- 1794,1801 ----
          (*function) (ccp->id,
                       CatalogCacheComputeTupleHashValue(ccp, tuple),
                       &tuple->t_self,
!                      ccp->cc_relisshared ? (Oid) 0 : MyDatabaseId,
!                      (ccp->id == PROCOID) ? HeapTupleGetOid(tuple) : InvalidOid);
      }
  }

Index: src/backend/utils/cache/inval.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/backend/utils/cache/inval.c,v
retrieving revision 1.86
diff -c -r1.86 inval.c
*** src/backend/utils/cache/inval.c    19 Jun 2008 21:32:56 -0000    1.86
--- src/backend/utils/cache/inval.c    28 Aug 2008 09:20:26 -0000
***************
*** 307,319 ****
  static void
  AddCatcacheInvalidationMessage(InvalidationListHeader *hdr,
                                 int id, uint32 hashValue,
!                                ItemPointer tuplePtr, Oid dbId)
  {
      SharedInvalidationMessage msg;

      msg.cc.id = (int16) id;
      msg.cc.tuplePtr = *tuplePtr;
      msg.cc.dbId = dbId;
      msg.cc.hashValue = hashValue;
      AddInvalidationMessage(&hdr->cclist, &msg);
  }
--- 307,320 ----
  static void
  AddCatcacheInvalidationMessage(InvalidationListHeader *hdr,
                                 int id, uint32 hashValue,
!                                ItemPointer tuplePtr, Oid dbId, Oid tupleId)
  {
      SharedInvalidationMessage msg;

      msg.cc.id = (int16) id;
      msg.cc.tuplePtr = *tuplePtr;
      msg.cc.dbId = dbId;
+     msg.cc.tupleId = tupleId;
      msg.cc.hashValue = hashValue;
      AddInvalidationMessage(&hdr->cclist, &msg);
  }
***************
*** 414,423 ****
  RegisterCatcacheInvalidation(int cacheId,
                               uint32 hashValue,
                               ItemPointer tuplePtr,
!                              Oid dbId)
  {
      AddCatcacheInvalidationMessage(&transInvalInfo->CurrentCmdInvalidMsgs,
!                                    cacheId, hashValue, tuplePtr, dbId);
  }

  /*
--- 415,424 ----
  RegisterCatcacheInvalidation(int cacheId,
                               uint32 hashValue,
                               ItemPointer tuplePtr,
!                              Oid dbId, Oid tupleId)
  {
      AddCatcacheInvalidationMessage(&transInvalInfo->CurrentCmdInvalidMsgs,
!                                    cacheId, hashValue, tuplePtr, dbId, tupleId);
  }

  /*
***************
*** 489,495 ****
                  struct CACHECALLBACK *ccitem = cache_callback_list + i;

                  if (ccitem->id == msg->cc.id)
!                     (*ccitem->function) (ccitem->arg, InvalidOid);
              }
          }
      }
--- 490,496 ----
                  struct CACHECALLBACK *ccitem = cache_callback_list + i;

                  if (ccitem->id == msg->cc.id)
!                     (*ccitem->function) (ccitem->arg, msg->cc.tupleId);
              }
          }
      }
Index: src/backend/utils/cache/plancache.c
===================================================================
RCS file: /projects/cvsroot/pgsql/src/backend/utils/cache/plancache.c,v
retrieving revision 1.20
diff -c -r1.20 plancache.c
*** src/backend/utils/cache/plancache.c    25 Aug 2008 22:42:34 -0000    1.20
--- src/backend/utils/cache/plancache.c    28 Aug 2008 09:20:26 -0000
***************
*** 52,57 ****
--- 52,58 ----
  #include "utils/memutils.h"
  #include "utils/resowner.h"
  #include "utils/snapmgr.h"
+ #include "utils/syscache.h"


  typedef struct
***************
*** 81,87 ****
  static bool ScanQueryWalker(Node *node, ScanQueryWalkerContext *context);
  static bool rowmark_member(List *rowMarks, int rt_index);
  static bool plan_list_is_transient(List *stmt_list);
! static void PlanCacheCallback(Datum arg, Oid relid);
  static void InvalRelid(Oid relid, LOCKMODE lockmode,
             InvalRelidContext *context);

--- 82,88 ----
  static bool ScanQueryWalker(Node *node, ScanQueryWalkerContext *context);
  static bool rowmark_member(List *rowMarks, int rt_index);
  static bool plan_list_is_transient(List *stmt_list);
! static void PlanCacheCallback(Datum arg, Oid objId);
  static void InvalRelid(Oid relid, LOCKMODE lockmode,
             InvalRelidContext *context);

***************
*** 94,100 ****
  void
  InitPlanCache(void)
  {
!     CacheRegisterRelcacheCallback(PlanCacheCallback, (Datum) 0);
  }

  /*
--- 95,105 ----
  void
  InitPlanCache(void)
  {
!     CacheRegisterRelcacheCallback(PlanCacheCallback, Int32GetDatum(RELOID));
!     CacheRegisterSyscacheCallback(PROCOID, PlanCacheCallback, Int32GetDatum(PROCOID));
!     CacheRegisterSyscacheCallback(NAMESPACEOID, PlanCacheCallback, Int32GetDatum(NAMESPACEOID));
!     CacheRegisterSyscacheCallback(OPEROID, PlanCacheCallback, Int32GetDatum(OPEROID));
!     CacheRegisterSyscacheCallback(OPFAMILYOID, PlanCacheCallback, Int32GetDatum(OPFAMILYOID));
  }

  /*
***************
*** 866,877 ****
   * PlanCacheCallback
   *        Relcache inval callback function
   *
!  * Invalidate all plans mentioning the given rel, or all plans mentioning
!  * any rel at all if relid == InvalidOid.
   */
  static void
! PlanCacheCallback(Datum arg, Oid relid)
  {
      ListCell   *lc1;
      ListCell   *lc2;

--- 871,883 ----
   * PlanCacheCallback
   *        Relcache inval callback function
   *
!  * Invalidate all plans mentioning the given OID, or all plans
!  * if objId == InvalidOid.
   */
  static void
! PlanCacheCallback(Datum arg, Oid objId)
  {
+     int            catId = DatumGetInt32(arg);
      ListCell   *lc1;
      ListCell   *lc2;

***************
*** 883,925 ****
          /* No work if it's already invalidated */
          if (!plan || plan->dead)
              continue;
-         if (plan->fully_planned)
-         {
-             foreach(lc2, plan->stmt_list)
-             {
-                 PlannedStmt *plannedstmt = (PlannedStmt *) lfirst(lc2);

!                 Assert(!IsA(plannedstmt, Query));
!                 if (!IsA(plannedstmt, PlannedStmt))
!                     continue;    /* Ignore utility statements */
!                 if ((relid == InvalidOid) ? plannedstmt->relationOids != NIL :
!                     list_member_oid(plannedstmt->relationOids, relid))
!                 {
!                     /* Invalidate the plan! */
!                     plan->dead = true;
!                     break;        /* out of stmt_list scan */
!                 }
!             }
!         }
!         else
          {
!             /*
!              * For not-fully-planned entries we use ScanQueryForRelids, since
!              * a recursive traversal is needed.  The callback API is a bit
!              * tedious but avoids duplication of coding.
!              */
!             InvalRelidContext context;

!             context.inval_relid = relid;
!             context.plan = plan;
!
!             foreach(lc2, plan->stmt_list)
              {
!                 Query       *query = (Query *) lfirst(lc2);

!                 Assert(IsA(query, Query));
!                 ScanQueryForRelids(query, InvalRelid, (void *) &context);
              }
          }
      }
  }
--- 889,934 ----
          /* No work if it's already invalidated */
          if (!plan || plan->dead)
              continue;

!         foreach(lc2, plan->stmt_list)
          {
!             PlannedStmt *plannedstmt = (PlannedStmt *) lfirst(lc2);

!             Assert(!IsA(plannedstmt, Query));
!             if (!IsA(plannedstmt, PlannedStmt))
!                 continue;    /* Ignore utility statements */
!
!             if (objId == InvalidOid)
!                 /* InvalidOid - invalidate all */
!                 plan->dead = true;
!             else if (catId == PROCOID)
!                 /* Invalidate if we have this function */
!                 plan->dead = list_member_oid(plannedstmt->functionOids, objId);
!             else if (catId == RELOID)
              {
!                 /* Invalidate if we have this relation */
!                 if (plan->fully_planned)
!                     plan->dead = list_member_oid(plannedstmt->relationOids, objId);
!                 else
!                 {
!                     /*
!                      * For not-fully-planned entries we use ScanQueryForRelids,
!                      * since a recursive traversal is needed.  The callback API
!                      * is a bit tedious but avoids duplication of coding.
!                      */
!                     InvalRelidContext context;
!                     Query *query = (Query *) lfirst(lc2);
!
!                     context.inval_relid = objId;
!                     context.plan = plan;

!                     Assert(IsA(query, Query));
!                     ScanQueryForRelids(query, InvalRelid, (void *) &context);
!                 }
              }
+
+             if (plan->dead)
+                 break;        /* out of stmt_list scan */
          }
      }
  }
Index: src/include/nodes/parsenodes.h
===================================================================
RCS file: /projects/cvsroot/pgsql/src/include/nodes/parsenodes.h,v
retrieving revision 1.371
diff -c -r1.371 parsenodes.h
*** src/include/nodes/parsenodes.h    7 Aug 2008 01:11:51 -0000    1.371
--- src/include/nodes/parsenodes.h    28 Aug 2008 09:20:28 -0000
***************
*** 132,137 ****
--- 132,139 ----

      Node       *setOperations;    /* set-operation tree if this is top level of
                                   * a UNION/INTERSECT/EXCEPT query */
+
+     List       *functionOids;    /* OIDs of functions the query references */
  } Query;


Index: src/include/nodes/plannodes.h
===================================================================
RCS file: /projects/cvsroot/pgsql/src/include/nodes/plannodes.h,v
retrieving revision 1.102
diff -c -r1.102 plannodes.h
*** src/include/nodes/plannodes.h    7 Aug 2008 19:35:02 -0000    1.102
--- src/include/nodes/plannodes.h    28 Aug 2008 09:20:28 -0000
***************
*** 72,77 ****
--- 72,79 ----

      List       *relationOids;    /* OIDs of relations the plan depends on */

+     List       *functionOids;    /* OIDs of functions the plan depends on */
+
      int            nParamExec;        /* number of PARAM_EXEC Params used */
  } PlannedStmt;

Index: src/include/parser/parse_node.h
===================================================================
RCS file: /projects/cvsroot/pgsql/src/include/parser/parse_node.h,v
retrieving revision 1.54
diff -c -r1.54 parse_node.h
*** src/include/parser/parse_node.h    19 Jun 2008 00:46:06 -0000    1.54
--- src/include/parser/parse_node.h    28 Aug 2008 09:20:28 -0000
***************
*** 80,85 ****
--- 80,86 ----
      bool        p_is_update;
      Relation    p_target_relation;
      RangeTblEntry *p_target_rangetblentry;
+     List        *p_functionOids;
  } ParseState;

  extern ParseState *make_parsestate(ParseState *parentParseState);
Index: src/include/storage/sinval.h
===================================================================
RCS file: /projects/cvsroot/pgsql/src/include/storage/sinval.h,v
retrieving revision 1.48
diff -c -r1.48 sinval.h
*** src/include/storage/sinval.h    19 Jun 2008 21:32:56 -0000    1.48
--- src/include/storage/sinval.h    28 Aug 2008 09:20:28 -0000
***************
*** 54,59 ****
--- 54,60 ----
      int16        id;                /* cache ID --- must be first */
      ItemPointerData tuplePtr;    /* tuple identifier in cached relation */
      Oid            dbId;            /* database ID, or 0 if a shared relation */
+     Oid            tupleId;        /* OID of the invalidated tuple */
      uint32        hashValue;        /* hash value of key for this catcache */
  } SharedInvalCatcacheMsg;

Index: src/include/utils/catcache.h
===================================================================
RCS file: /projects/cvsroot/pgsql/src/include/utils/catcache.h,v
retrieving revision 1.67
diff -c -r1.67 catcache.h
*** src/include/utils/catcache.h    19 Jun 2008 00:46:06 -0000    1.67
--- src/include/utils/catcache.h    28 Aug 2008 09:20:28 -0000
***************
*** 184,190 ****
                           ItemPointer pointer);
  extern void PrepareToInvalidateCacheTuple(Relation relation,
                                HeapTuple tuple,
!                            void (*function) (int, uint32, ItemPointer, Oid));

  extern void PrintCatCacheLeakWarning(HeapTuple tuple);
  extern void PrintCatCacheListLeakWarning(CatCList *list);
--- 184,190 ----
                           ItemPointer pointer);
  extern void PrepareToInvalidateCacheTuple(Relation relation,
                                HeapTuple tuple,
!                            void (*function) (int, uint32, ItemPointer, Oid, Oid));

  extern void PrintCatCacheLeakWarning(HeapTuple tuple);
  extern void PrintCatCacheListLeakWarning(CatCList *list);
Index: src/test/regress/expected/plancache.out
===================================================================
RCS file: /projects/cvsroot/pgsql/src/test/regress/expected/plancache.out,v
retrieving revision 1.7
diff -c -r1.7 plancache.out
*** src/test/regress/expected/plancache.out    8 Jun 2008 22:41:04 -0000    1.7
--- src/test/regress/expected/plancache.out    28 Aug 2008 09:20:29 -0000
***************
*** 53,61 ****
  -- since clients probably aren't expecting that to change on the fly
  ALTER TABLE pcachetest ADD COLUMN q3 bigint;
  EXECUTE prepstmt;
! ERROR:  cached plan must not change result type
  EXECUTE prepstmt2(123);
! ERROR:  cached plan must not change result type
  -- but we're nice guys and will let you undo your mistake
  ALTER TABLE pcachetest DROP COLUMN q3;
  EXECUTE prepstmt;
--- 53,74 ----
  -- since clients probably aren't expecting that to change on the fly
  ALTER TABLE pcachetest ADD COLUMN q3 bigint;
  EXECUTE prepstmt;
!         q1        |        q2         | q3
! ------------------+-------------------+----
!  4567890123456789 | -4567890123456789 |
!  4567890123456789 |               123 |
!               123 |               456 |
!               123 |  4567890123456789 |
!  4567890123456789 |  4567890123456789 |
! (5 rows)
!
  EXECUTE prepstmt2(123);
!  q1  |        q2        | q3
! -----+------------------+----
!  123 |              456 |
!  123 | 4567890123456789 |
! (2 rows)
!
  -- but we're nice guys and will let you undo your mistake
  ALTER TABLE pcachetest DROP COLUMN q3;
  EXECUTE prepstmt;

pgsql-hackers by date:

Previous
From: Grant Finnemore
Date:
Subject: Re: Proposal to sync SET ROLE and pg_stat_activity
Next
From: Bernd Helmle
Date:
Subject: What happend to equality_oper()