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-2016, 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/cost.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 	MemSet(&ctl, 0, sizeof(ctl));
83 	ctl.keysize = sizeof(Oid);
84 	ctl.entrysize = sizeof(TableSpaceCacheEntry);
85 	TableSpaceCacheHash =
86 		hash_create("TableSpace cache", 16, &ctl,
87 					HASH_ELEM | HASH_BLOBS);
88 
89 	/* Make sure we've initialized CacheMemoryContext. */
90 	if (!CacheMemoryContext)
91 		CreateCacheMemoryContext();
92 
93 	/* Watch for invalidation events. */
94 	CacheRegisterSyscacheCallback(TABLESPACEOID,
95 								  InvalidateTableSpaceCacheCallback,
96 								  (Datum) 0);
97 }
98 
99 /*
100  * get_tablespace
101  *		Fetch TableSpaceCacheEntry structure for a specified table OID.
102  *
103  * Pointers returned by this function should not be stored, since a cache
104  * flush will invalidate them.
105  */
106 static TableSpaceCacheEntry *
get_tablespace(Oid spcid)107 get_tablespace(Oid spcid)
108 {
109 	TableSpaceCacheEntry *spc;
110 	HeapTuple	tp;
111 	TableSpaceOpts *opts;
112 
113 	/*
114 	 * Since spcid is always from a pg_class tuple, InvalidOid implies the
115 	 * default.
116 	 */
117 	if (spcid == InvalidOid)
118 		spcid = MyDatabaseTableSpace;
119 
120 	/* Find existing cache entry, if any. */
121 	if (!TableSpaceCacheHash)
122 		InitializeTableSpaceCache();
123 	spc = (TableSpaceCacheEntry *) hash_search(TableSpaceCacheHash,
124 											   (void *) &spcid,
125 											   HASH_FIND,
126 											   NULL);
127 	if (spc)
128 		return spc;
129 
130 	/*
131 	 * Not found in TableSpace cache.  Check catcache.  If we don't find a
132 	 * valid HeapTuple, it must mean someone has managed to request tablespace
133 	 * details for a non-existent tablespace.  We'll just treat that case as
134 	 * if no options were specified.
135 	 */
136 	tp = SearchSysCache1(TABLESPACEOID, ObjectIdGetDatum(spcid));
137 	if (!HeapTupleIsValid(tp))
138 		opts = NULL;
139 	else
140 	{
141 		Datum		datum;
142 		bool		isNull;
143 
144 		datum = SysCacheGetAttr(TABLESPACEOID,
145 								tp,
146 								Anum_pg_tablespace_spcoptions,
147 								&isNull);
148 		if (isNull)
149 			opts = NULL;
150 		else
151 		{
152 			bytea	   *bytea_opts = tablespace_reloptions(datum, false);
153 
154 			opts = MemoryContextAlloc(CacheMemoryContext, VARSIZE(bytea_opts));
155 			memcpy(opts, bytea_opts, VARSIZE(bytea_opts));
156 		}
157 		ReleaseSysCache(tp);
158 	}
159 
160 	/*
161 	 * Now create the cache entry.  It's important to do this only after
162 	 * reading the pg_tablespace entry, since doing so could cause a cache
163 	 * flush.
164 	 */
165 	spc = (TableSpaceCacheEntry *) hash_search(TableSpaceCacheHash,
166 											   (void *) &spcid,
167 											   HASH_ENTER,
168 											   NULL);
169 	spc->opts = opts;
170 	return spc;
171 }
172 
173 /*
174  * get_tablespace_page_costs
175  *		Return random and/or sequential page costs for a given tablespace.
176  */
177 void
get_tablespace_page_costs(Oid spcid,double * spc_random_page_cost,double * spc_seq_page_cost)178 get_tablespace_page_costs(Oid spcid,
179 						  double *spc_random_page_cost,
180 						  double *spc_seq_page_cost)
181 {
182 	TableSpaceCacheEntry *spc = get_tablespace(spcid);
183 
184 	Assert(spc != NULL);
185 
186 	if (spc_random_page_cost)
187 	{
188 		if (!spc->opts || spc->opts->random_page_cost < 0)
189 			*spc_random_page_cost = random_page_cost;
190 		else
191 			*spc_random_page_cost = spc->opts->random_page_cost;
192 	}
193 
194 	if (spc_seq_page_cost)
195 	{
196 		if (!spc->opts || spc->opts->seq_page_cost < 0)
197 			*spc_seq_page_cost = seq_page_cost;
198 		else
199 			*spc_seq_page_cost = spc->opts->seq_page_cost;
200 	}
201 }
202 
203 int
get_tablespace_io_concurrency(Oid spcid)204 get_tablespace_io_concurrency(Oid spcid)
205 {
206 	TableSpaceCacheEntry *spc = get_tablespace(spcid);
207 
208 	if (!spc->opts || spc->opts->effective_io_concurrency < 0)
209 		return effective_io_concurrency;
210 	else
211 		return spc->opts->effective_io_concurrency;
212 }
213