1 /*-------------------------------------------------------------------------
2 *
3 * spccache.c
4 * Tablespace cache management.
5 *
6 * We cache the parsed version of spcoptions for each tablespace to avoid
7 * needing to reparse on every lookup. Right now, there doesn't appear to
8 * be a measurable performance gain from doing this, but that might change
9 * in the future as we add more options.
10 *
11 * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
12 * Portions Copyright (c) 1994, Regents of the University of California
13 *
14 * IDENTIFICATION
15 * src/backend/utils/cache/spccache.c
16 *
17 *-------------------------------------------------------------------------
18 */
19 #include "postgres.h"
20
21 #include "access/reloptions.h"
22 #include "catalog/pg_tablespace.h"
23 #include "commands/tablespace.h"
24 #include "miscadmin.h"
25 #include "optimizer/optimizer.h"
26 #include "storage/bufmgr.h"
27 #include "utils/catcache.h"
28 #include "utils/hsearch.h"
29 #include "utils/inval.h"
30 #include "utils/spccache.h"
31 #include "utils/syscache.h"
32
33
34 /* Hash table for information about each tablespace */
35 static HTAB *TableSpaceCacheHash = NULL;
36
37 typedef struct
38 {
39 Oid oid; /* lookup key - must be first */
40 TableSpaceOpts *opts; /* options, or NULL if none */
41 } TableSpaceCacheEntry;
42
43
44 /*
45 * InvalidateTableSpaceCacheCallback
46 * Flush all cache entries when pg_tablespace is updated.
47 *
48 * When pg_tablespace is updated, we must flush the cache entry at least
49 * for that tablespace. Currently, we just flush them all. This is quick
50 * and easy and doesn't cost much, since there shouldn't be terribly many
51 * tablespaces, nor do we expect them to be frequently modified.
52 */
53 static void
InvalidateTableSpaceCacheCallback(Datum arg,int cacheid,uint32 hashvalue)54 InvalidateTableSpaceCacheCallback(Datum arg, int cacheid, uint32 hashvalue)
55 {
56 HASH_SEQ_STATUS status;
57 TableSpaceCacheEntry *spc;
58
59 hash_seq_init(&status, TableSpaceCacheHash);
60 while ((spc = (TableSpaceCacheEntry *) hash_seq_search(&status)) != NULL)
61 {
62 if (spc->opts)
63 pfree(spc->opts);
64 if (hash_search(TableSpaceCacheHash,
65 (void *) &spc->oid,
66 HASH_REMOVE,
67 NULL) == NULL)
68 elog(ERROR, "hash table corrupted");
69 }
70 }
71
72 /*
73 * InitializeTableSpaceCache
74 * Initialize the tablespace cache.
75 */
76 static void
InitializeTableSpaceCache(void)77 InitializeTableSpaceCache(void)
78 {
79 HASHCTL ctl;
80
81 /* Initialize the hash table. */
82 ctl.keysize = sizeof(Oid);
83 ctl.entrysize = sizeof(TableSpaceCacheEntry);
84 TableSpaceCacheHash =
85 hash_create("TableSpace cache", 16, &ctl,
86 HASH_ELEM | HASH_BLOBS);
87
88 /* Make sure we've initialized CacheMemoryContext. */
89 if (!CacheMemoryContext)
90 CreateCacheMemoryContext();
91
92 /* Watch for invalidation events. */
93 CacheRegisterSyscacheCallback(TABLESPACEOID,
94 InvalidateTableSpaceCacheCallback,
95 (Datum) 0);
96 }
97
98 /*
99 * get_tablespace
100 * Fetch TableSpaceCacheEntry structure for a specified table OID.
101 *
102 * Pointers returned by this function should not be stored, since a cache
103 * flush will invalidate them.
104 */
105 static TableSpaceCacheEntry *
get_tablespace(Oid spcid)106 get_tablespace(Oid spcid)
107 {
108 TableSpaceCacheEntry *spc;
109 HeapTuple tp;
110 TableSpaceOpts *opts;
111
112 /*
113 * Since spcid is always from a pg_class tuple, InvalidOid implies the
114 * default.
115 */
116 if (spcid == InvalidOid)
117 spcid = MyDatabaseTableSpace;
118
119 /* Find existing cache entry, if any. */
120 if (!TableSpaceCacheHash)
121 InitializeTableSpaceCache();
122 spc = (TableSpaceCacheEntry *) hash_search(TableSpaceCacheHash,
123 (void *) &spcid,
124 HASH_FIND,
125 NULL);
126 if (spc)
127 return spc;
128
129 /*
130 * Not found in TableSpace cache. Check catcache. If we don't find a
131 * valid HeapTuple, it must mean someone has managed to request tablespace
132 * details for a non-existent tablespace. We'll just treat that case as
133 * if no options were specified.
134 */
135 tp = SearchSysCache1(TABLESPACEOID, ObjectIdGetDatum(spcid));
136 if (!HeapTupleIsValid(tp))
137 opts = NULL;
138 else
139 {
140 Datum datum;
141 bool isNull;
142
143 datum = SysCacheGetAttr(TABLESPACEOID,
144 tp,
145 Anum_pg_tablespace_spcoptions,
146 &isNull);
147 if (isNull)
148 opts = NULL;
149 else
150 {
151 bytea *bytea_opts = tablespace_reloptions(datum, false);
152
153 opts = MemoryContextAlloc(CacheMemoryContext, VARSIZE(bytea_opts));
154 memcpy(opts, bytea_opts, VARSIZE(bytea_opts));
155 }
156 ReleaseSysCache(tp);
157 }
158
159 /*
160 * Now create the cache entry. It's important to do this only after
161 * reading the pg_tablespace entry, since doing so could cause a cache
162 * flush.
163 */
164 spc = (TableSpaceCacheEntry *) hash_search(TableSpaceCacheHash,
165 (void *) &spcid,
166 HASH_ENTER,
167 NULL);
168 spc->opts = opts;
169 return spc;
170 }
171
172 /*
173 * get_tablespace_page_costs
174 * Return random and/or sequential page costs for a given tablespace.
175 *
176 * This value is not locked by the transaction, so this value may
177 * be changed while a SELECT that has used these values for planning
178 * is still executing.
179 */
180 void
get_tablespace_page_costs(Oid spcid,double * spc_random_page_cost,double * spc_seq_page_cost)181 get_tablespace_page_costs(Oid spcid,
182 double *spc_random_page_cost,
183 double *spc_seq_page_cost)
184 {
185 TableSpaceCacheEntry *spc = get_tablespace(spcid);
186
187 Assert(spc != NULL);
188
189 if (spc_random_page_cost)
190 {
191 if (!spc->opts || spc->opts->random_page_cost < 0)
192 *spc_random_page_cost = random_page_cost;
193 else
194 *spc_random_page_cost = spc->opts->random_page_cost;
195 }
196
197 if (spc_seq_page_cost)
198 {
199 if (!spc->opts || spc->opts->seq_page_cost < 0)
200 *spc_seq_page_cost = seq_page_cost;
201 else
202 *spc_seq_page_cost = spc->opts->seq_page_cost;
203 }
204 }
205
206 /*
207 * get_tablespace_io_concurrency
208 *
209 * This value is not locked by the transaction, so this value may
210 * be changed while a SELECT that has used these values for planning
211 * is still executing.
212 */
213 int
get_tablespace_io_concurrency(Oid spcid)214 get_tablespace_io_concurrency(Oid spcid)
215 {
216 TableSpaceCacheEntry *spc = get_tablespace(spcid);
217
218 if (!spc->opts || spc->opts->effective_io_concurrency < 0)
219 return effective_io_concurrency;
220 else
221 return spc->opts->effective_io_concurrency;
222 }
223
224 /*
225 * get_tablespace_maintenance_io_concurrency
226 */
227 int
get_tablespace_maintenance_io_concurrency(Oid spcid)228 get_tablespace_maintenance_io_concurrency(Oid spcid)
229 {
230 TableSpaceCacheEntry *spc = get_tablespace(spcid);
231
232 if (!spc->opts || spc->opts->maintenance_io_concurrency < 0)
233 return maintenance_io_concurrency;
234 else
235 return spc->opts->maintenance_io_concurrency;
236 }
237