1 /*
2 * This file and its contents are licensed under the Timescale License.
3 * Please see the included NOTICE for copyright information and
4 * LICENSE-TIMESCALE for a copy of the license.
5 */
6
7 #include <postgres.h>
8 #include <catalog/pg_type.h>
9 #include <catalog/pg_proc.h>
10 #include <catalog/pg_inherits.h>
11 #include <funcapi.h>
12 #include <utils/builtins.h>
13 #include <utils/lsyscache.h>
14 #include <utils/syscache.h>
15 #include <utils/array.h>
16 #include <miscadmin.h>
17 #include <access/xact.h>
18 #include <cache.h>
19 #include <nodes/pg_list.h>
20
21 #include "dimension.h"
22 #include "errors.h"
23 #include "hypertable.h"
24 #include "utils.h"
25 #include "hypertable_cache.h"
26 #include "chunk.h"
27 #include "chunk_data_node.h"
28
29 #include <foreign/foreign.h>
30 #include <libpq-fe.h>
31
32 #include "fdw/fdw.h"
33 #include "data_node.h"
34 #include "deparse.h"
35 #include "remote/dist_commands.h"
36 #include "compat/compat.h"
37 #include "hypertable_data_node.h"
38 #include "extension.h"
39
40 static List *
data_node_append(List * data_nodes,int32 hypertable_id,const char * node_name,int32 node_hypertable_id,bool block_chunks)41 data_node_append(List *data_nodes, int32 hypertable_id, const char *node_name,
42 int32 node_hypertable_id, bool block_chunks)
43 {
44 ForeignServer *server = data_node_get_foreign_server(node_name, ACL_NO_CHECK, true, false);
45 HypertableDataNode *hdn = palloc0(sizeof(HypertableDataNode));
46
47 hdn->fd.hypertable_id = hypertable_id;
48 namestrcpy(&hdn->fd.node_name, node_name);
49 hdn->fd.node_hypertable_id = node_hypertable_id;
50 hdn->foreign_server_oid = server->serverid;
51 hdn->fd.block_chunks = block_chunks;
52
53 return lappend(data_nodes, hdn);
54 }
55
56 /* Returns the remote hypertable ids for the data_nodes (in the same order)
57 */
58 static List *
hypertable_create_data_node_tables(int32 hypertable_id,List * data_nodes)59 hypertable_create_data_node_tables(int32 hypertable_id, List *data_nodes)
60 {
61 Hypertable *ht = ts_hypertable_get_by_id(hypertable_id);
62 ListCell *cell;
63 List *remote_ids = NIL;
64 DistCmdResult *dist_res;
65 DeparsedHypertableCommands *commands = deparse_get_distributed_hypertable_create_command(ht);
66
67 foreach (cell, deparse_get_tabledef_commands(ht->main_table_relid))
68 ts_dist_cmd_run_on_data_nodes(lfirst(cell), data_nodes, true);
69
70 dist_res = ts_dist_cmd_invoke_on_data_nodes(commands->table_create_command, data_nodes, true);
71 foreach (cell, data_nodes)
72 {
73 PGresult *res = ts_dist_cmd_get_result_by_node_name(dist_res, lfirst(cell));
74
75 Assert(PQntuples(res) == 1);
76 Assert(PQnfields(res) == AttrNumberGetAttrOffset(_Anum_create_hypertable_max));
77 remote_ids =
78 lappend(remote_ids,
79 (void *) Int32GetDatum(atoi(
80 PQgetvalue(res, 0, AttrNumberGetAttrOffset(Anum_create_hypertable_id)))));
81 }
82 ts_dist_cmd_close_response(dist_res);
83
84 foreach (cell, commands->dimension_add_commands)
85 ts_dist_cmd_run_on_data_nodes(lfirst(cell), data_nodes, true);
86
87 foreach (cell, commands->grant_commands)
88 ts_dist_cmd_run_on_data_nodes(lfirst(cell), data_nodes, true);
89
90 return remote_ids;
91 }
92
93 /*
94 * Assign data nodes to a hypertable.
95 *
96 * Given a list of data node names, add mappings to ensure the
97 * hypertable is distributed across those nodes.
98 *
99 * Returns a list of HypertableDataNode objects that correspond to the given
100 * data node names.
101 */
102 List *
hypertable_assign_data_nodes(int32 hypertable_id,List * nodes)103 hypertable_assign_data_nodes(int32 hypertable_id, List *nodes)
104 {
105 ListCell *lc;
106 List *assigned_nodes = NIL;
107 List *remote_ids = hypertable_create_data_node_tables(hypertable_id, nodes);
108 ListCell *id_cell;
109
110 Assert(nodes->length == remote_ids->length);
111 forboth (lc, nodes, id_cell, remote_ids)
112 {
113 assigned_nodes =
114 data_node_append(assigned_nodes, hypertable_id, lfirst(lc), lfirst_int(id_cell), false);
115 }
116
117 ts_hypertable_data_node_insert_multi(assigned_nodes);
118
119 return assigned_nodes;
120 }
121
122 /*
123 * Validate data nodes when creating a new hypertable.
124 *
125 * The function is passed the explicit array of data nodes given by the user,
126 * if any.
127 *
128 * If the data node array is NULL (no data nodes specified), we return all
129 * data nodes that the user is allowed to use.
130 *
131 */
132 List *
hypertable_get_and_validate_data_nodes(ArrayType * nodearr)133 hypertable_get_and_validate_data_nodes(ArrayType *nodearr)
134 {
135 bool fail_on_aclcheck = nodearr != NULL;
136 List *data_nodes = NIL;
137 int num_data_nodes;
138 List *all_data_nodes = NIL;
139
140 /* If the user explicitly specified a set of data nodes (data_node_arr is
141 * non-NULL), we validate the given array and fail if the user doesn't
142 * have USAGE on all of them. Otherwise, we get a list of all
143 * database-configured data nodes that the user has USAGE on. */
144 data_nodes = data_node_get_filtered_node_name_list(nodearr, ACL_USAGE, fail_on_aclcheck);
145 num_data_nodes = list_length(data_nodes);
146
147 if (NULL == nodearr)
148 {
149 /* No explicit set of data nodes given. Check if there are any data
150 * nodes that the user cannot use due to lack of permissions and
151 * raise a NOTICE if some of them cannot be used. */
152 all_data_nodes = data_node_get_node_name_list();
153 int num_nodes_not_used = list_length(all_data_nodes) - list_length(data_nodes);
154
155 if (num_nodes_not_used > 0)
156 ereport(NOTICE,
157 (errmsg("%d of %d data nodes not used by this hypertable due to lack of "
158 "permissions",
159 num_nodes_not_used,
160 list_length(all_data_nodes)),
161 errhint("Grant USAGE on data nodes to attach them to a hypertable.")));
162 }
163
164 /*
165 * In this case, if we couldn't find any valid data nodes to assign, it
166 * means that they do not have the right permissions on the data nodes or
167 * that there were no data nodes to assign. Depending on the case, we
168 * print different error details and hints to aid the user.
169 */
170 if (num_data_nodes == 0)
171 ereport(ERROR,
172 (errcode(ERRCODE_TS_INSUFFICIENT_NUM_DATA_NODES),
173 errmsg("no data nodes can be assigned to the hypertable"),
174 errdetail(list_length(all_data_nodes) == 0 ?
175 "No data nodes where available to assign to the hypertable." :
176 "Data nodes exist, but none have USAGE privilege."),
177 errhint(list_length(all_data_nodes) == 0 ?
178 "Add data nodes to the database." :
179 "Grant USAGE on data nodes to attach them to the hypertable.")));
180
181 if (num_data_nodes == 1)
182 ereport(WARNING,
183 (errmsg("only one data node was assigned to the hypertable"),
184 errdetail("A distributed hypertable should have at least two data nodes for best "
185 "performance."),
186 errhint(
187 list_length(all_data_nodes) == 1 ?
188 "Add more data nodes to the database and attach them to the hypertable." :
189 "Grant USAGE on data nodes and attach them to the hypertable.")));
190
191 if (num_data_nodes > MAX_NUM_HYPERTABLE_DATA_NODES)
192 ereport(ERROR,
193 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
194 errmsg("max number of data nodes exceeded"),
195 errhint("The number of data nodes cannot exceed %d.",
196 MAX_NUM_HYPERTABLE_DATA_NODES)));
197
198 return data_nodes;
199 }
200
201 void
hypertable_make_distributed(Hypertable * ht,List * data_node_names)202 hypertable_make_distributed(Hypertable *ht, List *data_node_names)
203 {
204 hypertable_assign_data_nodes(ht->fd.id, data_node_names);
205 }
206
207 static bool
hypertable_is_underreplicated(Hypertable * const ht,const int16 replication_factor)208 hypertable_is_underreplicated(Hypertable *const ht, const int16 replication_factor)
209 {
210 ListCell *lc;
211 List *chunks = find_inheritance_children(ht->main_table_relid, NoLock);
212
213 Assert(hypertable_is_distributed(ht));
214
215 foreach (lc, chunks)
216 {
217 Oid chunk_oid = lfirst_oid(lc);
218 Chunk *chunk = ts_chunk_get_by_relid(chunk_oid, true);
219 List *replicas = ts_chunk_data_node_scan_by_chunk_id(chunk->fd.id, CurrentMemoryContext);
220
221 Assert(get_rel_relkind(chunk_oid) == RELKIND_FOREIGN_TABLE);
222
223 if (list_length(replicas) < replication_factor)
224 return true;
225 }
226 return false;
227 }
228
229 static void
update_replication_factor(Hypertable * const ht,const int32 replication_factor_in)230 update_replication_factor(Hypertable *const ht, const int32 replication_factor_in)
231 {
232 const int16 replication_factor =
233 ts_validate_replication_factor(replication_factor_in, false, true);
234
235 ht->fd.replication_factor = replication_factor;
236 ts_hypertable_update(ht);
237 if (list_length(ht->data_nodes) < replication_factor)
238 ereport(ERROR,
239 (errcode(ERRCODE_TS_INSUFFICIENT_NUM_DATA_NODES),
240 errmsg("replication factor too large for hypertable \"%s\"",
241 NameStr(ht->fd.table_name)),
242 errdetail("The hypertable has %d data nodes attached, while "
243 "the replication factor is %d.",
244 list_length(ht->data_nodes),
245 replication_factor),
246 errhint("Decrease the replication factor or attach more data "
247 "nodes to the hypertable.")));
248 if (hypertable_is_underreplicated(ht, replication_factor))
249 ereport(WARNING,
250 (errcode(ERRCODE_WARNING),
251 errmsg("hypertable \"%s\" is under-replicated", NameStr(ht->fd.table_name)),
252 errdetail("Some chunks have less than %d replicas.", replication_factor)));
253 }
254
255 Datum
hypertable_set_replication_factor(PG_FUNCTION_ARGS)256 hypertable_set_replication_factor(PG_FUNCTION_ARGS)
257 {
258 const Oid table_relid = PG_ARGISNULL(0) ? InvalidOid : PG_GETARG_OID(0);
259 const int32 replication_factor_in = PG_ARGISNULL(1) ? 0 : PG_GETARG_INT32(1);
260 Cache *hcache;
261 Hypertable *ht;
262
263 TS_PREVENT_FUNC_IF_READ_ONLY();
264
265 if (!OidIsValid(table_relid))
266 ereport(ERROR,
267 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
268 errmsg("invalid hypertable: cannot be NULL")));
269
270 hcache = ts_hypertable_cache_pin();
271 ht = ts_hypertable_cache_get_entry(hcache, table_relid, CACHE_FLAG_NONE);
272
273 if (!hypertable_is_distributed(ht))
274 ereport(ERROR,
275 (errcode(ERRCODE_TS_HYPERTABLE_NOT_DISTRIBUTED),
276 errmsg("hypertable \"%s\" is not distributed", get_rel_name(table_relid))));
277
278 update_replication_factor(ht, replication_factor_in);
279
280 ts_cache_release(hcache);
281
282 PG_RETURN_VOID();
283 }
284