Skip to content

Commit

Permalink
Fix and refactor tablespace support
Browse files Browse the repository at this point in the history
Tablespaces can now be associated with a hypertable
using the new user-facing API attach_tablespace().
Alternatively, if the main table, which is to be
converted into a hypertable, was created with an
associated tablespace, that tablespace will
automatically be associated also with the hypertable.

Whenever a chunk is created, a tablespace will be
chosen from the ones associated with the chunk's
hypertable (if any). This is done in a way that ensures
consistency in one dimension. I.e., if a hypertable
has a closed (space) dimension with a fixed number
of slices (ranges), it will ensure that chunks that
fall in the same slice will alsp be stored in the same
tablespace.

If a hypertable has more than one closed dimension,
the first one will be used to assign tablespaces
to chunks. If the table has no closed dimensions, but
one or more open (time) dimensions, then the first
time dimension will be used. However, since open
dimensions do not have a fixed number of slices,
tablespaces will be assigned in a round-robbin
fashion as new slices are created. Still, chunks
in the same time slice will be stored in the same
tablespace.
  • Loading branch information
erimatnor committed Jun 22, 2017
1 parent 15657b5 commit 71c5e78
Show file tree
Hide file tree
Showing 14 changed files with 356 additions and 156 deletions.
48 changes: 24 additions & 24 deletions sql/chunk.sql
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ CREATE OR REPLACE FUNCTION _timescaledb_internal.chunk_get_dimension_constraint_
$BODY$
SELECT format($$
SELECT cc.chunk_id
FROM _timescaledb_catalog.dimension_slice ds
INNER JOIN _timescaledb_catalog.chunk_constraint cc ON (ds.id = cc.dimension_slice_id)
WHERE ds.dimension_id = %1$L and ds.range_start <= %2$L and ds.range_end > %2$L
$$,
FROM _timescaledb_catalog.dimension_slice ds
INNER JOIN _timescaledb_catalog.chunk_constraint cc ON (ds.id = cc.dimension_slice_id)
WHERE ds.dimension_id = %1$L and ds.range_start <= %2$L and ds.range_end > %2$L
$$,
dimension_id, dimension_value);
$BODY$;

Expand All @@ -21,7 +21,7 @@ CREATE OR REPLACE FUNCTION _timescaledb_internal.chunk_get_dimensions_constraint
RETURNS TEXT LANGUAGE SQL STABLE AS
$BODY$

SELECT string_agg(_timescaledb_internal.chunk_get_dimension_constraint_sql(dimension_id,
SELECT string_agg(_timescaledb_internal.chunk_get_dimension_constraint_sql(dimension_id,
dimension_value),
' INTERSECT ')
FROM (SELECT unnest(dimension_ids) AS dimension_id,
Expand All @@ -37,7 +37,7 @@ CREATE OR REPLACE FUNCTION _timescaledb_internal.chunk_id_get_by_dimensions(
$BODY$
BEGIN
IF array_length(dimension_ids, 1) > 0 THEN
RETURN QUERY EXECUTE _timescaledb_internal.chunk_get_dimensions_constraint_sql(dimension_ids,
RETURN QUERY EXECUTE _timescaledb_internal.chunk_get_dimensions_constraint_sql(dimension_ids,
dimension_values);
END IF;
END
Expand Down Expand Up @@ -124,13 +124,13 @@ BEGIN
SELECT free_slice.range_start, free_slice.range_end
INTO new_range_start, new_range_end
FROM _timescaledb_catalog.chunk c
INNER JOIN _timescaledb_catalog.chunk_constraint cc ON (cc.chunk_id = c.id)
INNER JOIN _timescaledb_catalog.chunk_constraint cc ON (cc.chunk_id = c.id)
INNER JOIN _timescaledb_catalog.dimension_slice free_slice ON (free_slice.id = cc.dimension_slice_id AND free_slice.dimension_id = free_dimension_id)
WHERE
WHERE
free_slice.range_end > free_dimension_value and free_slice.range_start <= free_dimension_value
LIMIT 1;

SELECT new_range_start IS NOT NULL INTO alignment_found;
SELECT new_range_start IS NOT NULL INTO alignment_found;
END IF;

IF NOT alignment_found THEN
Expand All @@ -141,18 +141,18 @@ BEGIN
END IF;

-- Check whether the new chunk interval overlaps with existing chunks.
-- new_range_start overlaps
SELECT free_slice.range_end
-- new_range_start overlaps
SELECT free_slice.range_end
INTO overlap_value
FROM _timescaledb_catalog.chunk c
INNER JOIN _timescaledb_catalog.chunk_constraint cc ON (cc.chunk_id = c.id)
INNER JOIN _timescaledb_catalog.chunk_constraint cc ON (cc.chunk_id = c.id)
INNER JOIN _timescaledb_catalog.dimension_slice free_slice ON (free_slice.id = cc.dimension_slice_id AND free_slice.dimension_id = free_dimension_id)
WHERE
WHERE
c.id = (
SELECT _timescaledb_internal.chunk_id_get_by_dimensions(free_dimension_id || fixed_dimension_ids,
new_range_start || fixed_dimension_values)
)
ORDER BY free_slice.range_end DESC
ORDER BY free_slice.range_end DESC
LIMIT 1;

IF FOUND THEN
Expand All @@ -169,9 +169,9 @@ BEGIN
SELECT free_slice.range_start
INTO overlap_value
FROM _timescaledb_catalog.chunk c
INNER JOIN _timescaledb_catalog.chunk_constraint cc ON (cc.chunk_id = c.id)
INNER JOIN _timescaledb_catalog.chunk_constraint cc ON (cc.chunk_id = c.id)
INNER JOIN _timescaledb_catalog.dimension_slice free_slice ON (free_slice.id = cc.dimension_slice_id AND free_slice.dimension_id = free_dimension_id)
WHERE
WHERE
c.id = (
SELECT _timescaledb_internal.chunk_id_get_by_dimensions(free_dimension_id || fixed_dimension_ids,
new_range_end || fixed_dimension_values)
Expand Down Expand Up @@ -234,11 +234,11 @@ BEGIN

--do not use RETURNING here (ON CONFLICT DO NOTHING)
INSERT INTO _timescaledb_catalog.dimension_slice
(dimension_id, range_start, range_end)
VALUES(dimension_ids[free_index], free_range_start, free_range_end)
(dimension_id, range_start, range_end)
VALUES(dimension_ids[free_index], free_range_start, free_range_end)
ON CONFLICT DO NOTHING;

SELECT id INTO STRICT slice_id
SELECT id INTO STRICT slice_id
FROM _timescaledb_catalog.dimension_slice ds
WHERE ds.dimension_id = dimension_ids[free_index] AND
ds.range_start = free_range_start AND ds.range_end = free_range_end;
Expand All @@ -250,9 +250,9 @@ BEGIN
INSERT INTO _timescaledb_catalog.chunk (id, hypertable_id, schema_name, table_name)
SELECT seq_id, h.id, h.associated_schema_name,
format('%s_%s_chunk', h.associated_table_prefix, seq_id)
FROM
FROM
nextval(pg_get_serial_sequence('_timescaledb_catalog.chunk','id')) seq_id,
_timescaledb_catalog.hypertable h
_timescaledb_catalog.hypertable h
WHERE h.id = dimension_row.hypertable_id
RETURNING *
)
Expand Down Expand Up @@ -302,12 +302,12 @@ $BODY$
DECLARE
chunk_row _timescaledb_catalog.chunk;
dimension_ids INTEGER[];
dimension_values bigint[];
dimension_values bigint[];
BEGIN
CASE WHEN space_dimension_id IS NOT NULL AND space_dimension_id <> 0 THEN
CASE WHEN space_dimension_id IS NOT NULL AND space_dimension_id <> 0 THEN
dimension_ids := ARRAY[time_dimension_id, space_dimension_id];
dimension_values := ARRAY[time_value, space_value];
ELSE
ELSE
dimension_ids := ARRAY[time_dimension_id];
dimension_values := ARRAY[time_value];
END CASE;
Expand Down
41 changes: 37 additions & 4 deletions sql/ddl_api.sql
Original file line number Diff line number Diff line change
Expand Up @@ -163,13 +163,13 @@ BEGIN
INNER JOIN pg_namespace n ON (n.OID = c.relnamespace)
WHERE c.OID = main_table;

UPDATE _timescaledb_catalog.dimension d
UPDATE _timescaledb_catalog.dimension d
SET interval_length = set_chunk_time_interval.chunk_time_interval
FROM _timescaledb_internal.dimension_get_time(
(
SELECT id
FROM _timescaledb_catalog.hypertable h
WHERE h.schema_name = main_schema_name AND
SELECT id
FROM _timescaledb_catalog.hypertable h
WHERE h.schema_name = main_schema_name AND
h.table_name = main_table_name
)) time_dim
WHERE time_dim.id = d.id;
Expand Down Expand Up @@ -215,3 +215,36 @@ BEGIN
PERFORM drop_chunks(older_than_ts, table_name, schema_name);
END
$BODY$;

CREATE OR REPLACE FUNCTION attach_tablespace(
hypertable REGCLASS,
tablespace NAME
)
RETURNS VOID LANGUAGE PLPGSQL VOLATILE AS
$BODY$
DECLARE
main_schema_name NAME;
main_table_name NAME;
hypertable_id INTEGER;
tablespace_oid OID;
BEGIN
SELECT nspname, relname
FROM pg_class c INNER JOIN pg_namespace n
ON (c.relnamespace = n.oid)
WHERE c.oid = hypertable
INTO STRICT main_schema_name, main_table_name;

SELECT id
FROM _timescaledb_catalog.hypertable h
WHERE (h.schema_name = main_schema_name)
AND (h.table_name = main_table_name)
INTO hypertable_id;

IF hypertable_id IS NULL THEN
RAISE EXCEPTION 'No hypertable "%" exists', main_table_name
USING ERRCODE = 'IO101';
END IF;

PERFORM _timescaledb_internal.attach_tablespace(hypertable_id, tablespace);
END
$BODY$;
35 changes: 20 additions & 15 deletions sql/ddl_internal.sql
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
-- Creates a hypertable row.
CREATE OR REPLACE FUNCTION _timescaledb_internal.create_hypertable_row(
schema_name NAME,
table_name NAME,
time_column_name NAME,
time_column_type REGTYPE,
partitioning_column NAME,
schema_name NAME,
table_name NAME,
time_column_name NAME,
time_column_type REGTYPE,
partitioning_column NAME,
partitioning_column_type REGTYPE,
number_partitions INTEGER,
associated_schema_name NAME,
associated_table_prefix NAME,
chunk_time_interval BIGINT,
tablespace NAME
number_partitions INTEGER,
associated_schema_name NAME,
associated_table_prefix NAME,
chunk_time_interval BIGINT,
tablespace NAME
)
RETURNS _timescaledb_catalog.hypertable LANGUAGE PLPGSQL VOLATILE AS
$BODY$
Expand Down Expand Up @@ -63,20 +63,25 @@ BEGIN
)
RETURNING * INTO hypertable_row;

--add default tablespace, if any
IF tablespace IS NOT NULL THEN
PERFORM _timescaledb_internal.attach_tablespace(hypertable_row.id, tablespace);
END IF;

--create time dimension
INSERT INTO _timescaledb_catalog.dimension(hypertable_id, column_name, column_type,
num_slices, partitioning_func_schema, partitioning_func,
num_slices, partitioning_func_schema, partitioning_func,
interval_length
) VALUES (
hypertable_row.id, time_column_name, time_column_type,
NULL, NULL, NULL,
chunk_time_interval
);

IF partitioning_column IS NOT NULL THEN
IF partitioning_column IS NOT NULL THEN
--create space dimension
INSERT INTO _timescaledb_catalog.dimension(hypertable_id, column_name, column_type,
num_slices, partitioning_func_schema, partitioning_func,
num_slices, partitioning_func_schema, partitioning_func,
interval_length
) VALUES (
hypertable_row.id, partitioning_column, partitioning_column_type,
Expand Down Expand Up @@ -130,8 +135,8 @@ CREATE OR REPLACE FUNCTION _timescaledb_internal.dimension_get_time(
)
RETURNS _timescaledb_catalog.dimension LANGUAGE SQL STABLE AS
$BODY$
SELECT *
FROM _timescaledb_catalog.dimension d
SELECT *
FROM _timescaledb_catalog.dimension d
WHERE d.hypertable_id = dimension_get_time.hypertable_id AND
d.interval_length IS NOT NULL
$BODY$;
Expand Down
1 change: 0 additions & 1 deletion sql/dimensions.sql

This file was deleted.

1 change: 1 addition & 0 deletions sql/load_order.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ sql/chunk_triggers.sql
sql/setup_main.sql
sql/ddl_api.sql
sql/ddl_triggers.sql
sql/tablespace.sql
sql/bookend.sql
sql/time_bucket.sql
sql/permissions.sql
Expand Down
8 changes: 8 additions & 0 deletions sql/tables.sql
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,14 @@ CREATE TABLE IF NOT EXISTS _timescaledb_catalog.hypertable (
SELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.hypertable', '');
SELECT pg_catalog.pg_extension_config_dump(pg_get_serial_sequence('_timescaledb_catalog.hypertable','id'), '');

CREATE TABLE IF NOT EXISTS _timescaledb_catalog.tablespace (
id SERIAL PRIMARY KEY,
hypertable_id INT NOT NULL REFERENCES _timescaledb_catalog.hypertable(id) ON DELETE CASCADE,
tablespace_name NAME NOT NULL,
UNIQUE (hypertable_id, tablespace_name)
);
SELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.tablespace', '');

CREATE TABLE _timescaledb_catalog.dimension (
id SERIAL NOT NULL PRIMARY KEY,
hypertable_id INTEGER NOT NULL REFERENCES _timescaledb_catalog.hypertable(id) ON DELETE CASCADE,
Expand Down
101 changes: 101 additions & 0 deletions sql/tablespace.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
CREATE OR REPLACE FUNCTION _timescaledb_internal.select_tablespace(
hypertable_id INTEGER,
chunk_id INTEGER
)
RETURNS NAME LANGUAGE PLPGSQL VOLATILE AS
$BODY$
<<main_block>>
DECLARE
chunk_slice_id INTEGER;
chunk_slice_index INTEGER;
dimension_id INTEGER;
dimension_slice_id INTEGER;
tablespaces NAME[] = ARRAY(
SELECT t.tablespace_name FROM _timescaledb_catalog.tablespace t
WHERE (t.hypertable_id = select_tablespace.hypertable_id)
ORDER BY id DESC
);
dimension_slices INT[];
partitioning_func TEXT;
BEGIN

IF array_length(tablespaces, 1) = 0 THEN
RETURN NULL;
END IF;

-- Try to pick first closed dimension, otherwise first open.
-- The partition_func variable will be valid or NULL depending
-- on the type of dimension found. This can be used to pick
-- different tablespace assignment strategies depending
-- on type of dimension.
SELECT d.id, d.partitioning_func FROM _timescaledb_catalog.dimension d
WHERE (d.hypertable_id = select_tablespace.hypertable_id)
ORDER BY partitioning_func NULLS LAST, id DESC
LIMIT 1
INTO dimension_id, partitioning_func;

-- Find all dimension slices for the chosen dimension
dimension_slices := ARRAY(
SELECT s.id FROM _timescaledb_catalog.dimension_slice s
WHERE (s.dimension_id = main_block.dimension_id)
);

-- Find the chunk's dimension slice for the chosen dimension
SELECT s.id FROM _timescaledb_catalog.dimension_slice s
INNER JOIN _timescaledb_catalog.chunk_constraint cc ON (cc.dimension_slice_id = s.id)
INNER JOIN _timescaledb_catalog.chunk c ON (cc.chunk_id = c.id)
WHERE (s.dimension_id = main_block.dimension_id)
AND (c.id = select_tablespace.chunk_id)
INTO STRICT chunk_slice_id;

-- Find the array index of the chunk's dimension slice
SELECT i
FROM generate_subscripts(dimension_slices, 1) AS i
WHERE dimension_slices[i] = chunk_slice_id
INTO STRICT chunk_slice_index;

-- Use the chunk's dimension slice index to pick a tablespace in the tablespaces array
RETURN tablespaces[chunk_slice_index % array_length(tablespaces, 1) + 1];
END
$BODY$;

CREATE OR REPLACE FUNCTION _timescaledb_internal.select_tablespace(
chunk_id INTEGER
)
RETURNS NAME LANGUAGE SQL AS
$BODY$
SELECT _timescaledb_internal.select_tablespace(
(SELECT hypertable_id FROM _timescaledb_catalog.chunk WHERE id = chunk_id),
chunk_id);
$BODY$;

CREATE OR REPLACE FUNCTION _timescaledb_internal.attach_tablespace(
hypertable_id INTEGER,
tablespace_name NAME
)
RETURNS VOID LANGUAGE PLPGSQL VOLATILE AS
$BODY$
DECLARE
tablespace_oid OID;
BEGIN
SELECT oid
FROM pg_catalog.pg_tablespace
WHERE spcname = tablespace_name
INTO tablespace_oid;

IF tablespace_oid IS NULL THEN
RAISE EXCEPTION 'No tablespace "%" exists. A tablespace needs to be created before assigning it to a hypertable dimension', tablespace_name
USING ERRCODE = 'IO101';
END IF;

BEGIN
INSERT INTO _timescaledb_catalog.tablespace (hypertable_id, tablespace_name)
VALUES (hypertable_id, tablespace_name);
EXCEPTION
WHEN unique_violation THEN
RAISE EXCEPTION 'Tablespace "%" already assigned to hypertable "%"',
tablespace_name, (SELECT table_name FROM _timescaledb_catalog.hypertable
WHERE id = hypertable_id);
END;
END
$BODY$;

0 comments on commit 71c5e78

Please sign in to comment.