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