From fa2d99a1c24be380b1e1f13e32736709ed529929 Mon Sep 17 00:00:00 2001 From: Nikhil Sontakke Date: Fri, 22 Dec 2023 16:35:58 +0530 Subject: [PATCH] Support approximate hypertable size If a lot of chunks are involved then the current pl/pgsql function to compute the size of each chunk via a nested loop is pretty slow. Additionally, the current functionality makes a system call to get the file size on disk for each chunk everytime this function is called. That again slows things down. We now have an approximate function which is implemented in C to avoid the issues in the pl/pgsql function. Additionally, this function also uses per backend caching using the smgr layer to compute the approximate size cheaply. The PG cache invalidation clears off the cached size for a chunk when DML happens into it. That size cache is thus able to get the latest size in a matter of minutes. Also, due to the backend caching, any long running session will only fetch latest data for new or modified chunks and can use the cached data (which is calculated afresh the first time around) effectively for older chunks. --- .unreleased/pr_6463 | 1 + sql/size_utils.sql | 27 ++ sql/updates/latest-dev.sql | 18 ++ sql/updates/reverse-dev.sql | 3 + src/utils.c | 268 ++++++++++++++++++ test/expected/size_utils.out | 176 ++++++++++++ test/sql/size_utils.sql | 49 ++++ tsl/test/expected/chunk_utils_compression.out | 14 + tsl/test/expected/chunk_utils_internal.out | 20 ++ tsl/test/shared/expected/extension.out | 3 + tsl/test/sql/chunk_utils_compression.sql | 4 + tsl/test/sql/chunk_utils_internal.sql | 6 + 12 files changed, 589 insertions(+) create mode 100644 .unreleased/pr_6463 diff --git a/.unreleased/pr_6463 b/.unreleased/pr_6463 new file mode 100644 index 00000000000..4b9d4c70a00 --- /dev/null +++ b/.unreleased/pr_6463 @@ -0,0 +1 @@ +Implements: #6463 Support approximate hypertable size diff --git a/sql/size_utils.sql b/sql/size_utils.sql index d968b030533..27bd7da9438 100644 --- a/sql/size_utils.sql +++ b/sql/size_utils.sql @@ -9,6 +9,10 @@ CREATE OR REPLACE FUNCTION _timescaledb_functions.relation_size(relation REGCLAS RETURNS TABLE (total_size BIGINT, heap_size BIGINT, index_size BIGINT, toast_size BIGINT) AS '@MODULE_PATHNAME@', 'ts_relation_size' LANGUAGE C VOLATILE; +CREATE OR REPLACE FUNCTION _timescaledb_functions.relation_approximate_size(relation REGCLASS) +RETURNS TABLE (total_size BIGINT, heap_size BIGINT, index_size BIGINT, toast_size BIGINT) +AS '@MODULE_PATHNAME@', 'ts_relation_approximate_size' LANGUAGE C STRICT VOLATILE; + CREATE OR REPLACE VIEW _timescaledb_internal.hypertable_chunk_local_size AS SELECT h.schema_name AS hypertable_schema, @@ -169,6 +173,29 @@ $BODY$ FROM @extschema@.hypertable_detailed_size(hypertable); $BODY$ SET search_path TO pg_catalog, pg_temp; +-- Get approximate relation size of hypertable +-- +-- hypertable - hypertable to get approximate size of +-- +-- Returns: +-- table_bytes - Approximate disk space used by hypertable +-- index_bytes - Approximate disk space used by indexes +-- toast_bytes - Approximate disk space of toast tables +-- total_bytes - Total approximate disk space used by the specified table, including all indexes and TOAST data +CREATE OR REPLACE FUNCTION @extschema@.hypertable_approximate_detailed_size(relation REGCLASS) +RETURNS TABLE (table_bytes BIGINT, index_bytes BIGINT, toast_bytes BIGINT, total_bytes BIGINT) +AS '@MODULE_PATHNAME@', 'ts_hypertable_approximate_size' LANGUAGE C VOLATILE; + +--- returns approximate total-bytes for a hypertable (includes table + index) +CREATE OR REPLACE FUNCTION @extschema@.hypertable_approximate_size( + hypertable REGCLASS) +RETURNS BIGINT +LANGUAGE SQL VOLATILE STRICT AS +$BODY$ + SELECT sum(total_bytes)::bigint + FROM @extschema@.hypertable_approximate_detailed_size(hypertable); +$BODY$ SET search_path TO pg_catalog, pg_temp; + CREATE OR REPLACE FUNCTION _timescaledb_functions.chunks_local_size( schema_name_in name, table_name_in name) diff --git a/sql/updates/latest-dev.sql b/sql/updates/latest-dev.sql index a29f5458bf8..a727f285a7b 100644 --- a/sql/updates/latest-dev.sql +++ b/sql/updates/latest-dev.sql @@ -427,3 +427,21 @@ ALTER EXTENSION timescaledb ADD TABLE _timescaledb_internal.job_errors; ALTER EXTENSION timescaledb DROP TABLE _timescaledb_catalog.hypertable; ALTER EXTENSION timescaledb ADD TABLE _timescaledb_catalog.hypertable; SELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.hypertable', 'WHERE id >= 1'); + +CREATE FUNCTION _timescaledb_functions.relation_approximate_size(relation REGCLASS) +RETURNS TABLE (total_size BIGINT, heap_size BIGINT, index_size BIGINT, toast_size BIGINT) +AS '@MODULE_PATHNAME@', 'ts_relation_approximate_size' LANGUAGE C STRICT VOLATILE; + +CREATE FUNCTION @extschema@.hypertable_approximate_detailed_size(relation REGCLASS) +RETURNS TABLE (table_bytes BIGINT, index_bytes BIGINT, toast_bytes BIGINT, total_bytes BIGINT) +AS '@MODULE_PATHNAME@', 'ts_hypertable_approximate_size' LANGUAGE C VOLATILE; + +--- returns approximate total-bytes for a hypertable (includes table + index) +CREATE FUNCTION @extschema@.hypertable_approximate_size( + hypertable REGCLASS) +RETURNS BIGINT +LANGUAGE SQL VOLATILE STRICT AS +$BODY$ + SELECT sum(total_bytes)::bigint + FROM @extschema@.hypertable_approximate_detailed_size(hypertable); +$BODY$ SET search_path TO pg_catalog, pg_temp; diff --git a/sql/updates/reverse-dev.sql b/sql/updates/reverse-dev.sql index 556427441b0..43d0f493123 100644 --- a/sql/updates/reverse-dev.sql +++ b/sql/updates/reverse-dev.sql @@ -788,3 +788,6 @@ ALTER EXTENSION timescaledb DROP TABLE _timescaledb_catalog.hypertable; ALTER EXTENSION timescaledb ADD TABLE _timescaledb_catalog.hypertable; -- include this now in the config SELECT pg_catalog.pg_extension_config_dump('_timescaledb_catalog.hypertable', ''); +DROP FUNCTION IF EXISTS _timescaledb_functions.relation_approximate_size(relation REGCLASS); +DROP FUNCTION IF EXISTS @extschema@.hypertable_approximate_detailed_size(relation REGCLASS); +DROP FUNCTION IF EXISTS @extschema@.hypertable_approximate_size(hypertable REGCLASS); diff --git a/src/utils.c b/src/utils.c index 98288350bd1..78a5d7e98a3 100644 --- a/src/utils.c +++ b/src/utils.c @@ -1025,6 +1025,274 @@ ts_relation_size_impl(Oid relid) return relsize; } +/* + * Try to get cached size for a provided relation across all forks. The + * size is returned in terms of number of blocks. + * + * The function calls the underlying smgrnblocks if there is no cached + * data. That call populates the cache for subsequent invocations. This + * cached data gets removed asynchronously by PG relcache invalidations + * and then the refresh/cache cycle repeats till the next invalidation. + */ +static int64 +ts_try_relation_cached_size(Relation rel, bool verbose) +{ + BlockNumber result = 0, nblocks = 0; + ForkNumber forkNum; + bool cached = true; + + /* Get heap size, including FSM and VM */ + for (forkNum = 0; forkNum <= MAX_FORKNUM; forkNum++) + { +#if PG14_LT + /* PG13 does not have smgr_cached_nblocks */ + result = InvalidBlockNumber; +#else + result = RelationGetSmgr(rel)->smgr_cached_nblocks[forkNum]; +#endif + + if (result != InvalidBlockNumber) + { + nblocks += result; + } + else + { + if (smgrexists(RelationGetSmgr(rel), forkNum)) + { + cached = false; + nblocks += smgrnblocks(RelationGetSmgr(rel), forkNum); + } + } + } + + if (verbose) + ereport(DEBUG2, + (errmsg("%s for %s", + cached ? "Cached size used" : "Fetching actual size", + RelationGetRelationName(rel)))); + + /* convert the size into bytes and return */ + return nblocks * BLCKSZ; +} + +static RelationSize +ts_relation_approximate_size_impl(Oid relid) +{ + RelationSize relsize = { 0 }; + Relation rel; + + DEBUG_WAITPOINT("relation_approximate_size_before_lock"); + /* Open relation earlier to keep a lock during all function calls */ + rel = try_relation_open(relid, AccessShareLock); + + if (!rel) + return relsize; + + /* Get the main heap size */ + relsize.heap_size = ts_try_relation_cached_size(rel, false); + + /* Get the size of the relation's indexes */ + if (rel->rd_rel->relhasindex) + { + List *index_oids = RelationGetIndexList(rel); + ListCell *cell; + + foreach (cell, index_oids) + { + Oid idxOid = lfirst_oid(cell); + Relation idxRel; + + idxRel = relation_open(idxOid, AccessShareLock); + relsize.index_size += ts_try_relation_cached_size(idxRel, false); + relation_close(idxRel, AccessShareLock); + } + } + + /* If there's an associated TOAST table, calculate the total size (including its indexes) */ + if (OidIsValid(rel->rd_rel->reltoastrelid)) + { + Relation toastRel; + List *index_oids; + ListCell *cell; + + toastRel = relation_open(rel->rd_rel->reltoastrelid, AccessShareLock); + relsize.toast_size = ts_try_relation_cached_size(toastRel, false); + + /* Get the indexes size of the TOAST relation */ + index_oids = RelationGetIndexList(toastRel); + foreach (cell, index_oids) + { + Oid idxOid = lfirst_oid(cell); + Relation idxRel; + + idxRel = relation_open(idxOid, AccessShareLock); + relsize.toast_size += ts_try_relation_cached_size(idxRel, false); + relation_close(idxRel, AccessShareLock); + } + + relation_close(toastRel, AccessShareLock); + } + + relation_close(rel, AccessShareLock); + + /* Add up the total size based on the heap size, indexes and toast */ + relsize.total_size = relsize.heap_size + relsize.index_size + relsize.toast_size; + + return relsize; +} + +TS_FUNCTION_INFO_V1(ts_relation_approximate_size); +Datum +ts_relation_approximate_size(PG_FUNCTION_ARGS) +{ + Oid relid = PG_GETARG_OID(0); + RelationSize relsize = { 0 }; + TupleDesc tupdesc; + HeapTuple tuple; + Datum values[4] = { 0 }; + bool nulls[4] = { false }; + + /* Build a tuple descriptor for our result type */ + if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("function returning record called in context " + "that cannot accept type record"))); + + /* check if object exists, return NULL otherwise */ + if (get_rel_name(relid) == NULL) + PG_RETURN_NULL(); + + relsize = ts_relation_approximate_size_impl(relid); + + tupdesc = BlessTupleDesc(tupdesc); + + values[0] = Int64GetDatum(relsize.total_size); + values[1] = Int64GetDatum(relsize.heap_size); + values[2] = Int64GetDatum(relsize.index_size); + values[3] = Int64GetDatum(relsize.toast_size); + + tuple = heap_form_tuple(tupdesc, values, nulls); + + return HeapTupleGetDatum(tuple); +} + +static void +init_scan_by_hypertable_id(ScanIterator *iterator, int32 hypertable_id) +{ + iterator->ctx.index = catalog_get_index(ts_catalog_get(), CHUNK, CHUNK_HYPERTABLE_ID_INDEX); + ts_scan_iterator_scan_key_init(iterator, + Anum_chunk_hypertable_id_idx_hypertable_id, + BTEqualStrategyNumber, + F_INT4EQ, + Int32GetDatum(hypertable_id)); +} + +#define ADD_RELATIONSIZE(total, rel) \ + do \ + { \ + (total).heap_size += (rel).heap_size; \ + (total).toast_size += (rel).toast_size; \ + (total).index_size += (rel).index_size; \ + (total).total_size += (rel).total_size; \ + } while (0) + +TS_FUNCTION_INFO_V1(ts_hypertable_approximate_size); +Datum +ts_hypertable_approximate_size(PG_FUNCTION_ARGS) +{ + Oid relid = PG_ARGISNULL(0) ? InvalidOid : PG_GETARG_OID(0); + RelationSize total_relsize = { 0 }; + TupleDesc tupdesc; + HeapTuple tuple; + Datum values[4] = { 0 }; + bool nulls[4] = { false }; + Cache *hcache; + Hypertable *ht; + ScanIterator iterator = ts_scan_iterator_create(CHUNK, RowExclusiveLock, CurrentMemoryContext); + + /* Build a tuple descriptor for our result type */ + if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("function returning record called in context " + "that cannot accept type record"))); + + if (!OidIsValid(relid)) + PG_RETURN_NULL(); + + /* go ahead only if this is a hypertable or a CAgg */ + hcache = ts_hypertable_cache_pin(); + ht = ts_resolve_hypertable_from_table_or_cagg(hcache, relid, true); + if (ht == NULL) + { + ts_cache_release(hcache); + PG_RETURN_NULL(); + } + + /* get the main hypertable size */ + total_relsize = ts_relation_approximate_size_impl(relid); + + iterator = ts_scan_iterator_create(CHUNK, RowExclusiveLock, CurrentMemoryContext); + init_scan_by_hypertable_id(&iterator, ht->fd.id); + ts_scanner_foreach(&iterator) + { + bool isnull, dropped, is_osm_chunk; + TupleInfo *ti = ts_scan_iterator_tuple_info(&iterator); + Datum id = slot_getattr(ti->slot, Anum_chunk_id, &isnull); + Datum comp_id = DatumGetInt32(slot_getattr(ti->slot, Anum_chunk_id, &isnull)); + int32 chunk_id, compressed_chunk_id; + Oid chunk_relid, compressed_chunk_relid; + RelationSize chunk_relsize, compressed_chunk_relsize; + + if (isnull) + continue; + + /* only consider chunks that are not dropped */ + dropped = DatumGetBool(slot_getattr(ti->slot, Anum_chunk_dropped, &isnull)); + Assert(!isnull); + if (dropped) + continue; + + chunk_id = DatumGetInt32(id); + + /* avoid if it's an OSM chunk */ + is_osm_chunk = slot_getattr(ti->slot, Anum_chunk_osm_chunk, &isnull); + Assert(!isnull); + if (is_osm_chunk) + continue; + + chunk_relid = ts_chunk_get_relid(chunk_id, false); + chunk_relsize = ts_relation_approximate_size_impl(chunk_relid); + /* add this chunk's size to the total size */ + ADD_RELATIONSIZE(total_relsize, chunk_relsize); + + /* check if the chunk has a compressed counterpart and add if yes */ + comp_id = slot_getattr(ti->slot, Anum_chunk_compressed_chunk_id, &isnull); + if (isnull) + continue; + + compressed_chunk_id = DatumGetInt32(comp_id); + compressed_chunk_relid = ts_chunk_get_relid(compressed_chunk_id, false); + compressed_chunk_relsize = ts_relation_approximate_size_impl(compressed_chunk_relid); + /* add this compressed chunk's size to the total size */ + ADD_RELATIONSIZE(total_relsize, compressed_chunk_relsize); + } + ts_scan_iterator_close(&iterator); + + tupdesc = BlessTupleDesc(tupdesc); + + values[0] = Int64GetDatum(total_relsize.heap_size); + values[1] = Int64GetDatum(total_relsize.index_size); + values[2] = Int64GetDatum(total_relsize.toast_size); + values[3] = Int64GetDatum(total_relsize.total_size); + + tuple = heap_form_tuple(tupdesc, values, nulls); + ts_cache_release(hcache); + + return HeapTupleGetDatum(tuple); +} + #define STR_VALUE(str) #str #define NODE_CASE(name) \ case T_##name: \ diff --git a/test/expected/size_utils.out b/test/expected/size_utils.out index a4ccc02e499..0349a2ef99f 100644 --- a/test/expected/size_utils.out +++ b/test/expected/size_utils.out @@ -583,6 +583,86 @@ SELECT * FROM _timescaledb_functions.relation_size(NULL); | | | (1 row) +-- Test approximate size functions with invalid input +SELECT * FROM hypertable_approximate_size(0); + hypertable_approximate_size +----------------------------- + +(1 row) + +SELECT * FROM hypertable_approximate_detailed_size(0); + table_bytes | index_bytes | toast_bytes | total_bytes +-------------+-------------+-------------+------------- + | | | +(1 row) + +SELECT * FROM _timescaledb_functions.relation_approximate_size(0); + total_size | heap_size | index_size | toast_size +------------+-----------+------------+------------ + | | | +(1 row) + +SELECT * FROM hypertable_approximate_size(NULL); + hypertable_approximate_size +----------------------------- + +(1 row) + +SELECT * FROM hypertable_approximate_detailed_size(NULL); + table_bytes | index_bytes | toast_bytes | total_bytes +-------------+-------------+-------------+------------- + | | | +(1 row) + +SELECT * FROM _timescaledb_functions.relation_approximate_size(NULL); + total_size | heap_size | index_size | toast_size +------------+-----------+------------+------------ +(0 rows) + +-- Test size on view, sequence and composite type +CREATE VIEW view1 as SELECT 1; +SELECT * FROM _timescaledb_functions.relation_approximate_size('view1'); + total_size | heap_size | index_size | toast_size +------------+-----------+------------+------------ + 0 | 0 | 0 | 0 +(1 row) + +SELECT * FROM _timescaledb_functions.relation_size('view1'); + total_size | heap_size | index_size | toast_size +------------+-----------+------------+------------ + 0 | 0 | 0 | 0 +(1 row) + +CREATE SEQUENCE test_id_seq + INCREMENT 1 + START 1 MINVALUE 1 + MAXVALUE 9223372036854775807 + CACHE 1; +SELECT * FROM _timescaledb_functions.relation_approximate_size('test_id_seq'); + total_size | heap_size | index_size | toast_size +------------+-----------+------------+------------ + 8192 | 8192 | 0 | 0 +(1 row) + +SELECT * FROM _timescaledb_functions.relation_size('test_id_seq'); + total_size | heap_size | index_size | toast_size +------------+-----------+------------+------------ + 8192 | 8192 | 0 | 0 +(1 row) + +CREATE TYPE test_type AS (time timestamp, temp float); +SELECT * FROM _timescaledb_functions.relation_approximate_size('test_type'); + total_size | heap_size | index_size | toast_size +------------+-----------+------------+------------ + 0 | 0 | 0 | 0 +(1 row) + +SELECT * FROM _timescaledb_functions.relation_size('test_type'); + total_size | heap_size | index_size | toast_size +------------+-----------+------------+------------ + 0 | 0 | 0 | 0 +(1 row) + -- Test size functions on regular table CREATE TABLE hypersize(time timestamptz, device int); CREATE INDEX hypersize_time_idx ON hypersize (time); @@ -633,6 +713,18 @@ SELECT * FROM hypertable_index_size('hypersize_time_idx'); (1 row) +SELECT * FROM _timescaledb_functions.relation_approximate_size('hypersize'); + total_size | heap_size | index_size | toast_size +------------+-----------+------------+------------ + 8192 | 0 | 8192 | 0 +(1 row) + +SELECT * FROM hypertable_approximate_size('hypersize'); +ERROR: "hypersize" is not a hypertable or a continuous aggregate +HINT: The operation is only possible on a hypertable or continuous aggregate. +SELECT * FROM hypertable_approximate_detailed_size('hypersize'); +ERROR: "hypersize" is not a hypertable or a continuous aggregate +HINT: The operation is only possible on a hypertable or continuous aggregate. \set VERBOSITY terse \set ON_ERROR_STOP 1 -- Test size functions on empty hypertable @@ -688,6 +780,24 @@ SELECT * FROM hypertable_index_size('hypersize_time_idx'); 8192 (1 row) +SELECT * FROM _timescaledb_functions.relation_approximate_size('hypersize'); + total_size | heap_size | index_size | toast_size +------------+-----------+------------+------------ + 8192 | 0 | 8192 | 0 +(1 row) + +SELECT * FROM hypertable_approximate_size('hypersize'); + hypertable_approximate_size +----------------------------- + 8192 +(1 row) + +SELECT * FROM hypertable_approximate_detailed_size('hypersize'); + table_bytes | index_bytes | toast_bytes | total_bytes +-------------+-------------+-------------+------------- + 0 | 8192 | 0 | 8192 +(1 row) + -- Test size functions on non-empty hypertable INSERT INTO hypersize VALUES('2021-02-25', 1); SELECT pg_relation_size('hypersize'), pg_table_size('hypersize'), pg_indexes_size('hypersize'), pg_total_relation_size('hypersize'), pg_relation_size('hypersize_time_idx'); @@ -744,6 +854,72 @@ SELECT * FROM hypertable_index_size('hypersize_time_idx'); 24576 (1 row) +SELECT * FROM _timescaledb_functions.relation_approximate_size('hypersize'); + total_size | heap_size | index_size | toast_size +------------+-----------+------------+------------ + 8192 | 0 | 8192 | 0 +(1 row) + +SELECT * FROM hypertable_approximate_size('hypersize'); + hypertable_approximate_size +----------------------------- + 32768 +(1 row) + +SELECT * FROM hypertable_approximate_detailed_size('hypersize'); + table_bytes | index_bytes | toast_bytes | total_bytes +-------------+-------------+-------------+------------- + 8192 | 24576 | 0 | 32768 +(1 row) + +-- Test approx size functions with toast entries +SELECT * FROM _timescaledb_functions.relation_approximate_size('toast_test'); + total_size | heap_size | index_size | toast_size +------------+-----------+------------+------------ + 16384 | 0 | 8192 | 8192 +(1 row) + +SELECT * FROM hypertable_approximate_size('toast_test'); + hypertable_approximate_size +----------------------------- + 65536 +(1 row) + +SELECT * FROM hypertable_approximate_detailed_size('toast_test'); + table_bytes | index_bytes | toast_bytes | total_bytes +-------------+-------------+-------------+------------- + 8192 | 24576 | 32768 | 65536 +(1 row) + +-- Test approx size function against a regular table +\set ON_ERROR_STOP 0 +CREATE TABLE regular(time TIMESTAMP, value TEXT); +SELECT * FROM hypertable_approximate_size('regular'); +ERROR: "regular" is not a hypertable or a continuous aggregate +\set ON_ERROR_STOP 1 +-- Test approx size functions with dropped chunks +CREATE TABLE drop_chunks_table(time BIGINT NOT NULL, data INTEGER); +SELECT hypertable_id AS drop_chunks_table_id + FROM create_hypertable('drop_chunks_table', 'time', chunk_time_interval => 10) \gset +INSERT INTO drop_chunks_table SELECT i, i FROM generate_series(0, 29) AS i; +SELECT * FROM hypertable_approximate_size('drop_chunks_table'); + hypertable_approximate_size +----------------------------- + 81920 +(1 row) + +SELECT drop_chunks('drop_chunks_table', older_than => 19); + drop_chunks +----------------------------------------- + _timescaledb_internal._hyper_7_12_chunk +(1 row) + +SELECT * FROM hypertable_approximate_size('drop_chunks_table'); + hypertable_approximate_size +----------------------------- + 57344 +(1 row) + -- github issue #4857 -- below procedure should not crash SET client_min_messages = ERROR; diff --git a/test/sql/size_utils.sql b/test/sql/size_utils.sql index dc54b99b2df..c0a8ebf6597 100644 --- a/test/sql/size_utils.sql +++ b/test/sql/size_utils.sql @@ -232,6 +232,29 @@ SELECT * FROM chunk_compression_stats(NULL) ORDER BY node_name; SELECT * FROM hypertable_index_size(NULL); SELECT * FROM _timescaledb_functions.relation_size(NULL); +-- Test approximate size functions with invalid input +SELECT * FROM hypertable_approximate_size(0); +SELECT * FROM hypertable_approximate_detailed_size(0); +SELECT * FROM _timescaledb_functions.relation_approximate_size(0); +SELECT * FROM hypertable_approximate_size(NULL); +SELECT * FROM hypertable_approximate_detailed_size(NULL); +SELECT * FROM _timescaledb_functions.relation_approximate_size(NULL); + +-- Test size on view, sequence and composite type +CREATE VIEW view1 as SELECT 1; +SELECT * FROM _timescaledb_functions.relation_approximate_size('view1'); +SELECT * FROM _timescaledb_functions.relation_size('view1'); +CREATE SEQUENCE test_id_seq + INCREMENT 1 + START 1 MINVALUE 1 + MAXVALUE 9223372036854775807 + CACHE 1; +SELECT * FROM _timescaledb_functions.relation_approximate_size('test_id_seq'); +SELECT * FROM _timescaledb_functions.relation_size('test_id_seq'); +CREATE TYPE test_type AS (time timestamp, temp float); +SELECT * FROM _timescaledb_functions.relation_approximate_size('test_type'); +SELECT * FROM _timescaledb_functions.relation_size('test_type'); + -- Test size functions on regular table CREATE TABLE hypersize(time timestamptz, device int); CREATE INDEX hypersize_time_idx ON hypersize (time); @@ -246,6 +269,9 @@ SELECT * FROM chunks_detailed_size('hypersize') ORDER BY node_name; SELECT * FROM hypertable_compression_stats('hypersize') ORDER BY node_name; SELECT * FROM chunk_compression_stats('hypersize') ORDER BY node_name; SELECT * FROM hypertable_index_size('hypersize_time_idx'); +SELECT * FROM _timescaledb_functions.relation_approximate_size('hypersize'); +SELECT * FROM hypertable_approximate_size('hypersize'); +SELECT * FROM hypertable_approximate_detailed_size('hypersize'); \set VERBOSITY terse \set ON_ERROR_STOP 1 @@ -259,6 +285,9 @@ SELECT * FROM chunks_detailed_size('hypersize') ORDER BY node_name; SELECT * FROM hypertable_compression_stats('hypersize') ORDER BY node_name; SELECT * FROM chunk_compression_stats('hypersize') ORDER BY node_name; SELECT * FROM hypertable_index_size('hypersize_time_idx'); +SELECT * FROM _timescaledb_functions.relation_approximate_size('hypersize'); +SELECT * FROM hypertable_approximate_size('hypersize'); +SELECT * FROM hypertable_approximate_detailed_size('hypersize'); -- Test size functions on non-empty hypertable INSERT INTO hypersize VALUES('2021-02-25', 1); @@ -273,6 +302,26 @@ SELECT * FROM chunks_detailed_size('hypersize') ORDER BY node_name; SELECT * FROM hypertable_compression_stats('hypersize') ORDER BY node_name; SELECT * FROM chunk_compression_stats('hypersize') ORDER BY node_name; SELECT * FROM hypertable_index_size('hypersize_time_idx'); +SELECT * FROM _timescaledb_functions.relation_approximate_size('hypersize'); +SELECT * FROM hypertable_approximate_size('hypersize'); +SELECT * FROM hypertable_approximate_detailed_size('hypersize'); +-- Test approx size functions with toast entries +SELECT * FROM _timescaledb_functions.relation_approximate_size('toast_test'); +SELECT * FROM hypertable_approximate_size('toast_test'); +SELECT * FROM hypertable_approximate_detailed_size('toast_test'); +-- Test approx size function against a regular table +\set ON_ERROR_STOP 0 +CREATE TABLE regular(time TIMESTAMP, value TEXT); +SELECT * FROM hypertable_approximate_size('regular'); +\set ON_ERROR_STOP 1 +-- Test approx size functions with dropped chunks +CREATE TABLE drop_chunks_table(time BIGINT NOT NULL, data INTEGER); +SELECT hypertable_id AS drop_chunks_table_id + FROM create_hypertable('drop_chunks_table', 'time', chunk_time_interval => 10) \gset +INSERT INTO drop_chunks_table SELECT i, i FROM generate_series(0, 29) AS i; +SELECT * FROM hypertable_approximate_size('drop_chunks_table'); +SELECT drop_chunks('drop_chunks_table', older_than => 19); +SELECT * FROM hypertable_approximate_size('drop_chunks_table'); -- github issue #4857 -- below procedure should not crash diff --git a/tsl/test/expected/chunk_utils_compression.out b/tsl/test/expected/chunk_utils_compression.out index 4f79e960704..c561a01f979 100644 --- a/tsl/test/expected/chunk_utils_compression.out +++ b/tsl/test/expected/chunk_utils_compression.out @@ -73,6 +73,20 @@ SELECT compress_chunk(show_chunks('public.table_to_compress')); _timescaledb_internal._hyper_2_9_chunk (3 rows) +-- check that approx size function works. We call VACUUM to ensure all forks exist +VACUUM public.table_to_compress; +SELECT * FROM hypertable_approximate_size('public.table_to_compress'); + hypertable_approximate_size +----------------------------- + 262144 +(1 row) + +SELECT * FROM hypertable_size('public.table_to_compress'); + hypertable_size +----------------- + 262144 +(1 row) + -- and run the queries again to make sure results are the same SELECT show_chunks('public.uncompressed_table'); show_chunks diff --git a/tsl/test/expected/chunk_utils_internal.out b/tsl/test/expected/chunk_utils_internal.out index 3e1d1b1aebe..6536bd44ec7 100644 --- a/tsl/test/expected/chunk_utils_internal.out +++ b/tsl/test/expected/chunk_utils_internal.out @@ -515,6 +515,19 @@ SELECT * FROM child_fdw_table; Wed Jan 01 01:00:00 2020 PST | 100 | 1000 (1 row) +-- test size functions on foreign table +SELECT * FROM _timescaledb_functions.relation_approximate_size('child_fdw_table'); + total_size | heap_size | index_size | toast_size +------------+-----------+------------+------------ + 0 | 0 | 0 | 0 +(1 row) + +SELECT * FROM _timescaledb_functions.relation_size('child_fdw_table'); + total_size | heap_size | index_size | toast_size +------------+-----------+------------+------------ + 0 | 0 | 0 | 0 +(1 row) + -- error should be thrown as the hypertable does not yet have an associated tiered chunk \set ON_ERROR_STOP 0 SELECT _timescaledb_functions.hypertable_osm_range_update('ht_try','2020-01-01 01:00'::timestamptz, '2020-01-01 03:00'); @@ -707,6 +720,13 @@ ERROR: dimension slice range_end cannot be less than range_start SELECT _timescaledb_functions.hypertable_osm_range_update('ht_try', '2022-05-05 01:00'::timestamptz, '2022-05-06'); ERROR: attempting to set overlapping range for tiered chunk of public.ht_try \set ON_ERROR_STOP 1 +-- test that approximate size function works when a osm chunk is present +SELECT * FROM hypertable_approximate_size('ht_try'); + hypertable_approximate_size +----------------------------- + 32768 +(1 row) + --TEST GUC variable to enable/disable OSM chunk SET timescaledb.enable_tiered_reads=false; EXPLAIN (COSTS OFF) SELECT * from ht_try; diff --git a/tsl/test/shared/expected/extension.out b/tsl/test/shared/expected/extension.out index fa6b8e1fa03..b8fca9a6e2b 100644 --- a/tsl/test/shared/expected/extension.out +++ b/tsl/test/shared/expected/extension.out @@ -116,6 +116,7 @@ ORDER BY pronamespace::regnamespace::text COLLATE "C", p.oid::regprocedure::text _timescaledb_functions.process_ddl_event() _timescaledb_functions.range_value_to_pretty(bigint,regtype) _timescaledb_functions.recompress_chunk_segmentwise(regclass,boolean) + _timescaledb_functions.relation_approximate_size(regclass) _timescaledb_functions.relation_size(regclass) _timescaledb_functions.repair_relation_acls() _timescaledb_functions.restart_background_workers() @@ -246,6 +247,8 @@ ORDER BY pronamespace::regnamespace::text COLLATE "C", p.oid::regprocedure::text drop_chunks(regclass,"any","any",boolean,"any","any") first(anyelement,"any") histogram(double precision,double precision,double precision,integer) + hypertable_approximate_detailed_size(regclass) + hypertable_approximate_size(regclass) hypertable_compression_stats(regclass) hypertable_detailed_size(regclass) hypertable_index_size(regclass) diff --git a/tsl/test/sql/chunk_utils_compression.sql b/tsl/test/sql/chunk_utils_compression.sql index d258039debf..113ca95dd80 100644 --- a/tsl/test/sql/chunk_utils_compression.sql +++ b/tsl/test/sql/chunk_utils_compression.sql @@ -32,6 +32,10 @@ SELECT show_chunks('public.table_to_compress', older_than=>'1 day'::interval); SELECT show_chunks('public.table_to_compress', newer_than=>'1 day'::interval); -- compress all chunks of the table: SELECT compress_chunk(show_chunks('public.table_to_compress')); +-- check that approx size function works. We call VACUUM to ensure all forks exist +VACUUM public.table_to_compress; +SELECT * FROM hypertable_approximate_size('public.table_to_compress'); +SELECT * FROM hypertable_size('public.table_to_compress'); -- and run the queries again to make sure results are the same SELECT show_chunks('public.uncompressed_table'); SELECT show_chunks('public.table_to_compress'); diff --git a/tsl/test/sql/chunk_utils_internal.sql b/tsl/test/sql/chunk_utils_internal.sql index 47918882af8..9c2dde243d2 100644 --- a/tsl/test/sql/chunk_utils_internal.sql +++ b/tsl/test/sql/chunk_utils_internal.sql @@ -325,6 +325,9 @@ SELECT create_hypertable('ht_try', 'timec', chunk_time_interval => interval '1 d INSERT INTO ht_try VALUES ('2022-05-05 01:00', 222, 222); SELECT * FROM child_fdw_table; +-- test size functions on foreign table +SELECT * FROM _timescaledb_functions.relation_approximate_size('child_fdw_table'); +SELECT * FROM _timescaledb_functions.relation_size('child_fdw_table'); -- error should be thrown as the hypertable does not yet have an associated tiered chunk \set ON_ERROR_STOP 0 @@ -396,6 +399,9 @@ SELECT _timescaledb_functions.hypertable_osm_range_update('ht_try', '2020-01-02 SELECT _timescaledb_functions.hypertable_osm_range_update('ht_try', '2022-05-05 01:00'::timestamptz, '2022-05-06'); \set ON_ERROR_STOP 1 +-- test that approximate size function works when a osm chunk is present +SELECT * FROM hypertable_approximate_size('ht_try'); + --TEST GUC variable to enable/disable OSM chunk SET timescaledb.enable_tiered_reads=false; EXPLAIN (COSTS OFF) SELECT * from ht_try;