リビジョン | 3e617777c1d3c7aa208b492ee561625947deb400 (tree) |
---|---|
日時 | 2018-04-05 17:13:03 |
作者 | Kyotaro Horiguchi <horiguchi.kyotaro@lab....> |
コミッター | Kyotaro Horiguchi |
Fix relcache invalidation handling
Relcache invalidation callback can be called at every chance while
planning. Planner can crash if the invalidation callblack frees
statistics cache that have been passed to planner. This patch let
pg_dbms_stats refrain from freeing statistics cache immediately by
relcache invalidation. Invalidated cache entries are removed before
and after planner runs.
@@ -18,6 +18,7 @@ | ||
18 | 18 | #include "executor/spi.h" |
19 | 19 | #include "funcapi.h" |
20 | 20 | #include "optimizer/plancat.h" |
21 | +#include "optimizer/planner.h" | |
21 | 22 | #include "parser/parse_oper.h" |
22 | 23 | #include "parser/parsetree.h" |
23 | 24 | #include "storage/bufmgr.h" |
@@ -79,15 +80,14 @@ static bool set_acl_ok = false; | ||
79 | 80 | typedef struct StatsRelationEntry |
80 | 81 | { |
81 | 82 | Oid relid; /* hash key must be at the head */ |
82 | - | |
83 | 83 | bool valid; /* T if the entry has valid stats */ |
84 | - | |
84 | + bool invalidated; /* T if this relation has been | |
85 | + * invalidated */ | |
85 | 86 | BlockNumber relpages; /* # of pages as of last ANALYZE */ |
86 | 87 | double reltuples; /* # of tuples as of last ANALYZE */ |
87 | 88 | BlockNumber relallvisible; /* # of all-visible pages as of last |
88 | 89 | * ANALYZE */ |
89 | 90 | BlockNumber curpages; /* # of pages as of lock/restore */ |
90 | - | |
91 | 91 | List *col_stats; /* list of StatsColumnEntry, each element |
92 | 92 | of which is pg_statistic record of this |
93 | 93 | relation. */ |
@@ -110,6 +110,7 @@ get_relation_info_hook_type prev_get_relation_info = NULL; | ||
110 | 110 | get_attavgwidth_hook_type prev_get_attavgwidth = NULL; |
111 | 111 | get_relation_stats_hook_type prev_get_relation_stats = NULL; |
112 | 112 | get_index_stats_hook_type prev_get_index_stats = NULL; |
113 | +planner_hook_type prev_planner_hook = NULL; | |
113 | 114 | |
114 | 115 | /* namings */ |
115 | 116 | #define NSPNAME "dbms_stats" |
@@ -140,8 +141,10 @@ static int nested_level = 0; | ||
140 | 141 | |
141 | 142 | /* |
142 | 143 | * The relation_stats_effective statistic cache is stored in hash table. |
144 | + * rel_invalidated is set true if the hash has invalidated entries. | |
143 | 145 | */ |
144 | 146 | static HTAB *rel_stats; |
147 | +static bool rel_invalidated = false; | |
145 | 148 | |
146 | 149 | /* |
147 | 150 | * The owner of pg_dbms_stats statistic tables. |
@@ -179,6 +182,7 @@ static void dbms_stats_invalidate_cache_internal(Oid relid, bool sta_col); | ||
179 | 182 | void _PG_init(void); |
180 | 183 | void _PG_fini(void); |
181 | 184 | |
185 | +/* hook functions */ | |
182 | 186 | static void dbms_stats_get_relation_info(PlannerInfo *root, Oid relid, |
183 | 187 | bool inhparent, RelOptInfo *rel); |
184 | 188 | static int32 dbms_stats_get_attavgwidth(Oid relid, AttrNumber attnum); |
@@ -186,7 +190,10 @@ static bool dbms_stats_get_relation_stats(PlannerInfo *root, RangeTblEntry *rte, | ||
186 | 190 | AttrNumber attnum, VariableStatData *vardata); |
187 | 191 | static bool dbms_stats_get_index_stats(PlannerInfo *root, Oid indexOid, |
188 | 192 | AttrNumber indexattnum, VariableStatData *vardata); |
193 | +static PlannedStmt *dbms_stats_planner(Query *parse, int cursorOptions, | |
194 | + ParamListInfo boundParams); | |
189 | 195 | |
196 | +/* internal functions */ | |
190 | 197 | static void get_merged_relation_stats(Oid relid, BlockNumber *pages, |
191 | 198 | double *tuples, double *allvisfrac, bool estimate); |
192 | 199 | static int32 get_merged_avgwidth(Oid relid, AttrNumber attnum); |
@@ -198,9 +205,11 @@ static HeapTuple column_cache_enter(Oid relid, int32 attnum, bool inh, | ||
198 | 205 | HeapTuple tuple); |
199 | 206 | static bool execute_plan(SPIPlanPtr *plan, const char *query, Oid relid, |
200 | 207 | const AttrNumber *attnum, bool inh); |
201 | -static void StatsCacheRelCallback(Datum arg, Oid relid); | |
208 | +static void statscache_rel_callback(Datum arg, Oid relid); | |
209 | +static void cleanup_invalidated_cache(void); | |
202 | 210 | static void init_rel_stats(void); |
203 | 211 | static void init_rel_stats_entry(StatsRelationEntry *entry, Oid relid); |
212 | + | |
204 | 213 | /* copied from PG core source tree */ |
205 | 214 | static void dbms_stats_estimate_rel_size(Relation rel, int32 *attr_widths, |
206 | 215 | BlockNumber *pages, double *tuples, double *allvisfrac, |
@@ -276,12 +285,14 @@ _PG_init(void) | ||
276 | 285 | get_relation_stats_hook = dbms_stats_get_relation_stats; |
277 | 286 | prev_get_index_stats = get_index_stats_hook; |
278 | 287 | get_index_stats_hook = dbms_stats_get_index_stats; |
288 | + prev_planner_hook = planner_hook; | |
289 | + planner_hook = dbms_stats_planner; | |
279 | 290 | |
280 | 291 | /* Initialize hash table for statistics caching. */ |
281 | 292 | init_rel_stats(); |
282 | 293 | |
283 | 294 | /* Also set up a callback for relcache SI invalidations */ |
284 | - CacheRegisterRelcacheCallback(StatsCacheRelCallback, (Datum) 0); | |
295 | + CacheRegisterRelcacheCallback(statscache_rel_callback, (Datum) 0); | |
285 | 296 | } |
286 | 297 | |
287 | 298 | /* |
@@ -295,6 +306,7 @@ _PG_fini(void) | ||
295 | 306 | get_attavgwidth_hook = prev_get_attavgwidth; |
296 | 307 | get_relation_stats_hook = prev_get_relation_stats; |
297 | 308 | get_index_stats_hook = prev_get_index_stats; |
309 | + planner_hook = prev_planner_hook; | |
298 | 310 | |
299 | 311 | /* A function to unregister callback for relcache is NOT provided. */ |
300 | 312 | } |
@@ -1007,7 +1019,7 @@ dbms_stats_get_index_stats(PlannerInfo *root, | ||
1007 | 1019 | |
1008 | 1020 | /* |
1009 | 1021 | * scan over simple_rel_array_size to find the owner relation of the |
1010 | - * index that its oid is indexOid | |
1022 | + * index with the oid | |
1011 | 1023 | */ |
1012 | 1024 | for (i = 1 ; i < root->simple_rel_array_size ; i++) |
1013 | 1025 | { |
@@ -1048,6 +1060,29 @@ next_plugin: | ||
1048 | 1060 | return false; |
1049 | 1061 | } |
1050 | 1062 | |
1063 | + | |
1064 | +/* | |
1065 | + * dbms_stats_planner | |
1066 | + * Hook function for planner_hook which cleans up invalidated statistics. | |
1067 | + */ | |
1068 | +static PlannedStmt * | |
1069 | +dbms_stats_planner(Query *parse, int cursorOptions, ParamListInfo boundParams) | |
1070 | +{ | |
1071 | + PlannedStmt *ret; | |
1072 | + | |
1073 | + cleanup_invalidated_cache(); | |
1074 | + | |
1075 | + if (prev_planner_hook) | |
1076 | + ret = (*prev_planner_hook) (parse, cursorOptions, boundParams); | |
1077 | + else | |
1078 | + ret = standard_planner(parse, cursorOptions, boundParams); | |
1079 | + | |
1080 | + cleanup_invalidated_cache(); | |
1081 | + | |
1082 | + return ret; | |
1083 | +} | |
1084 | + | |
1085 | + | |
1051 | 1086 | /* |
1052 | 1087 | * Extract binary value from given column. |
1053 | 1088 | */ |
@@ -1476,46 +1511,91 @@ execute_plan(SPIPlanPtr *plan, | ||
1476 | 1511 | } |
1477 | 1512 | |
1478 | 1513 | /* |
1479 | - * StatsCacheRelCallback | |
1514 | + * statscache_rel_callback | |
1480 | 1515 | * Relcache inval callback function |
1481 | 1516 | * |
1482 | - * Invalidate cached statistic info of the given relid, or all cached statistic | |
1483 | - * info if relid == InvalidOid. We don't complain even when we don't have such | |
1484 | - * statistics. | |
1517 | + * Invalidates cached statistics of the given relid, or all cached statistics | |
1518 | + * if relid == InvalidOid. The statsTuple in the hash entries are directly | |
1519 | + * passed to planner so we cannot remove them until planner ends. Just mark | |
1520 | + * here then cleanup after planner finishes work. | |
1521 | + */ | |
1522 | +static void | |
1523 | +statscache_rel_callback(Datum arg, Oid relid) | |
1524 | +{ | |
1525 | + StatsRelationEntry *entry; | |
1526 | + | |
1527 | + if (relid != InvalidOid) | |
1528 | + { | |
1529 | + bool found; | |
1530 | + | |
1531 | + /* | |
1532 | + * invalidate the entry for the specfied relation. Don't mind if found. | |
1533 | + */ | |
1534 | + entry = hash_search(rel_stats, &relid, HASH_FIND, &found); | |
1535 | + if (found) | |
1536 | + { | |
1537 | + entry->invalidated = true; | |
1538 | + rel_invalidated = true; | |
1539 | + } | |
1540 | + } | |
1541 | + else | |
1542 | + { | |
1543 | + /* invalidate all the entries of the hash */ | |
1544 | + HASH_SEQ_STATUS status; | |
1545 | + | |
1546 | + hash_seq_init(&status, rel_stats); | |
1547 | + while ((entry = hash_seq_search(&status)) != NULL) | |
1548 | + { | |
1549 | + entry->invalidated = true; | |
1550 | + rel_invalidated = true; | |
1551 | + } | |
1552 | + } | |
1553 | +} | |
1554 | + | |
1555 | +/* | |
1556 | + * cleanup_invalidated_cache() | |
1557 | + * Cleanup invalidated stats cache | |
1485 | 1558 | * |
1486 | - * Note: arg is not used. | |
1559 | + * removes invalidated cache entries. | |
1487 | 1560 | */ |
1488 | 1561 | static void |
1489 | -StatsCacheRelCallback(Datum arg, Oid relid) | |
1562 | +cleanup_invalidated_cache(void) | |
1490 | 1563 | { |
1491 | 1564 | HASH_SEQ_STATUS status; |
1492 | 1565 | StatsRelationEntry *entry; |
1493 | 1566 | |
1567 | + /* Return immediately if nothing to do */ | |
1568 | + if (!rel_invalidated) | |
1569 | + return; | |
1570 | + | |
1571 | + /* | |
1572 | + * Reset rel_invalidated first so that we don't lose invalidations that | |
1573 | + * happens during this round of cleanup. | |
1574 | + */ | |
1575 | + rel_invalidated = false; | |
1576 | + | |
1494 | 1577 | hash_seq_init(&status, rel_stats); |
1495 | 1578 | while ((entry = hash_seq_search(&status)) != NULL) |
1496 | 1579 | { |
1497 | - if (relid == InvalidOid || relid == entry->relid) | |
1498 | - { | |
1499 | - ListCell *lc; | |
1580 | + ListCell *lc; | |
1500 | 1581 | |
1501 | - /* Mark the relation entry as INVALID */ | |
1502 | - entry->valid = false; | |
1582 | + if (!entry->invalidated) | |
1583 | + continue; | |
1503 | 1584 | |
1504 | - /* Discard every column statistics */ | |
1505 | - foreach (lc, entry->col_stats) | |
1506 | - { | |
1507 | - StatsColumnEntry *ent = (StatsColumnEntry*) lfirst(lc); | |
1585 | + /* Discard every column statistics */ | |
1586 | + foreach (lc, entry->col_stats) | |
1587 | + { | |
1588 | + StatsColumnEntry *ent = (StatsColumnEntry*) lfirst(lc); | |
1508 | 1589 | |
1509 | - if (!ent->negative) | |
1510 | - pfree(ent->tuple); | |
1511 | - pfree(ent); | |
1512 | - } | |
1513 | - list_free(entry->col_stats); | |
1514 | - entry->col_stats = NIL; | |
1590 | + if (!ent->negative) | |
1591 | + pfree(ent->tuple); | |
1592 | + pfree(ent); | |
1515 | 1593 | } |
1516 | - } | |
1594 | + list_free(entry->col_stats); | |
1517 | 1595 | |
1518 | - /* We always check throughout the list, so hash_seq_term is not necessary */ | |
1596 | + /* Finally remove the hash entry. */ | |
1597 | + hash_search(rel_stats, &entry->relid, HASH_REMOVE, NULL); | |
1598 | + } | |
1519 | 1599 | } |
1520 | 1600 | |
1521 | 1601 | /* |
@@ -1552,6 +1632,7 @@ init_rel_stats_entry(StatsRelationEntry *entry, Oid relid) | ||
1552 | 1632 | { |
1553 | 1633 | entry->relid = relid; |
1554 | 1634 | entry->valid = false; |
1635 | + entry->invalidated = false; | |
1555 | 1636 | entry->relpages = InvalidBlockNumber; |
1556 | 1637 | entry->reltuples = 0.0; |
1557 | 1638 | entry->relallvisible = InvalidBlockNumber; |