Coverage Report

Created: 2025-08-12 06:43

/src/postgres/src/backend/utils/cache/spccache.c
Line
Count
Source (jump to first uncovered line)
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-2025, 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
#include "varatt.h"
33
34
35
/* Hash table for information about each tablespace */
36
static HTAB *TableSpaceCacheHash = NULL;
37
38
typedef struct
39
{
40
  Oid     oid;      /* lookup key - must be first */
41
  TableSpaceOpts *opts;   /* options, or NULL if none */
42
} TableSpaceCacheEntry;
43
44
45
/*
46
 * InvalidateTableSpaceCacheCallback
47
 *    Flush all cache entries when pg_tablespace is updated.
48
 *
49
 * When pg_tablespace is updated, we must flush the cache entry at least
50
 * for that tablespace.  Currently, we just flush them all.  This is quick
51
 * and easy and doesn't cost much, since there shouldn't be terribly many
52
 * tablespaces, nor do we expect them to be frequently modified.
53
 */
54
static void
55
InvalidateTableSpaceCacheCallback(Datum arg, int cacheid, uint32 hashvalue)
56
0
{
57
0
  HASH_SEQ_STATUS status;
58
0
  TableSpaceCacheEntry *spc;
59
60
0
  hash_seq_init(&status, TableSpaceCacheHash);
61
0
  while ((spc = (TableSpaceCacheEntry *) hash_seq_search(&status)) != NULL)
62
0
  {
63
0
    if (spc->opts)
64
0
      pfree(spc->opts);
65
0
    if (hash_search(TableSpaceCacheHash,
66
0
            &spc->oid,
67
0
            HASH_REMOVE,
68
0
            NULL) == NULL)
69
0
      elog(ERROR, "hash table corrupted");
70
0
  }
71
0
}
72
73
/*
74
 * InitializeTableSpaceCache
75
 *    Initialize the tablespace cache.
76
 */
77
static void
78
InitializeTableSpaceCache(void)
79
0
{
80
0
  HASHCTL   ctl;
81
82
  /* Initialize the hash table. */
83
0
  ctl.keysize = sizeof(Oid);
84
0
  ctl.entrysize = sizeof(TableSpaceCacheEntry);
85
0
  TableSpaceCacheHash =
86
0
    hash_create("TableSpace cache", 16, &ctl,
87
0
          HASH_ELEM | HASH_BLOBS);
88
89
  /* Make sure we've initialized CacheMemoryContext. */
90
0
  if (!CacheMemoryContext)
91
0
    CreateCacheMemoryContext();
92
93
  /* Watch for invalidation events. */
94
0
  CacheRegisterSyscacheCallback(TABLESPACEOID,
95
0
                  InvalidateTableSpaceCacheCallback,
96
0
                  (Datum) 0);
97
0
}
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 *
107
get_tablespace(Oid spcid)
108
0
{
109
0
  TableSpaceCacheEntry *spc;
110
0
  HeapTuple tp;
111
0
  TableSpaceOpts *opts;
112
113
  /*
114
   * Since spcid is always from a pg_class tuple, InvalidOid implies the
115
   * default.
116
   */
117
0
  if (spcid == InvalidOid)
118
0
    spcid = MyDatabaseTableSpace;
119
120
  /* Find existing cache entry, if any. */
121
0
  if (!TableSpaceCacheHash)
122
0
    InitializeTableSpaceCache();
123
0
  spc = (TableSpaceCacheEntry *) hash_search(TableSpaceCacheHash,
124
0
                         &spcid,
125
0
                         HASH_FIND,
126
0
                         NULL);
127
0
  if (spc)
128
0
    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
0
  tp = SearchSysCache1(TABLESPACEOID, ObjectIdGetDatum(spcid));
137
0
  if (!HeapTupleIsValid(tp))
138
0
    opts = NULL;
139
0
  else
140
0
  {
141
0
    Datum   datum;
142
0
    bool    isNull;
143
144
0
    datum = SysCacheGetAttr(TABLESPACEOID,
145
0
                tp,
146
0
                Anum_pg_tablespace_spcoptions,
147
0
                &isNull);
148
0
    if (isNull)
149
0
      opts = NULL;
150
0
    else
151
0
    {
152
0
      bytea    *bytea_opts = tablespace_reloptions(datum, false);
153
154
0
      opts = MemoryContextAlloc(CacheMemoryContext, VARSIZE(bytea_opts));
155
0
      memcpy(opts, bytea_opts, VARSIZE(bytea_opts));
156
0
    }
157
0
    ReleaseSysCache(tp);
158
0
  }
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
0
  spc = (TableSpaceCacheEntry *) hash_search(TableSpaceCacheHash,
166
0
                         &spcid,
167
0
                         HASH_ENTER,
168
0
                         NULL);
169
0
  spc->opts = opts;
170
0
  return spc;
171
0
}
172
173
/*
174
 * get_tablespace_page_costs
175
 *    Return random and/or sequential page costs for a given tablespace.
176
 *
177
 *    This value is not locked by the transaction, so this value may
178
 *    be changed while a SELECT that has used these values for planning
179
 *    is still executing.
180
 */
181
void
182
get_tablespace_page_costs(Oid spcid,
183
              double *spc_random_page_cost,
184
              double *spc_seq_page_cost)
185
0
{
186
0
  TableSpaceCacheEntry *spc = get_tablespace(spcid);
187
188
0
  Assert(spc != NULL);
189
190
0
  if (spc_random_page_cost)
191
0
  {
192
0
    if (!spc->opts || spc->opts->random_page_cost < 0)
193
0
      *spc_random_page_cost = random_page_cost;
194
0
    else
195
0
      *spc_random_page_cost = spc->opts->random_page_cost;
196
0
  }
197
198
0
  if (spc_seq_page_cost)
199
0
  {
200
0
    if (!spc->opts || spc->opts->seq_page_cost < 0)
201
0
      *spc_seq_page_cost = seq_page_cost;
202
0
    else
203
0
      *spc_seq_page_cost = spc->opts->seq_page_cost;
204
0
  }
205
0
}
206
207
/*
208
 * get_tablespace_io_concurrency
209
 *
210
 *    This value is not locked by the transaction, so this value may
211
 *    be changed while a SELECT that has used these values for planning
212
 *    is still executing.
213
 */
214
int
215
get_tablespace_io_concurrency(Oid spcid)
216
0
{
217
0
  TableSpaceCacheEntry *spc = get_tablespace(spcid);
218
219
0
  if (!spc->opts || spc->opts->effective_io_concurrency < 0)
220
0
    return effective_io_concurrency;
221
0
  else
222
0
    return spc->opts->effective_io_concurrency;
223
0
}
224
225
/*
226
 * get_tablespace_maintenance_io_concurrency
227
 */
228
int
229
get_tablespace_maintenance_io_concurrency(Oid spcid)
230
0
{
231
0
  TableSpaceCacheEntry *spc = get_tablespace(spcid);
232
233
0
  if (!spc->opts || spc->opts->maintenance_io_concurrency < 0)
234
0
    return maintenance_io_concurrency;
235
0
  else
236
0
    return spc->opts->maintenance_io_concurrency;
237
0
}