Skip to content

Commit 7c24bb7

Browse files
committed
Observability for chunk data
This change collects information about compressed chunks and stores them in an LRU fashion. It captures events at compression, query and DML oparations. The query/DML operations maintain totals and information about the last operation. The chunk information is maintained in shared memory per database. One can access the data via calling the `_timescaledb_functions.observ_chunks(..)` function or by selecting from the `timescaledb_information.observ_chunks` view the former allows the user to push down filters, so it is faster and less lock contention. The amount of shared memory is controlled by the GUC: `timescaledb.observ_max_chunks` If the GUC is set to zero, the feature is disabled. The default value is 1024 and the maximum is 32768. The amount must be a power of two number. The `observ_max_chunks` value controls how many chunk we store information about. The memory consumption is 300 bytes per entry.
1 parent 2c424c2 commit 7c24bb7

43 files changed

Lines changed: 5913 additions & 13 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.unreleased/pr_9730

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Implements: #9730 In memory observability for compressed chunks

cmake/ScriptFiles.cmake

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ set(SOURCE_FILES
4242
bgw_scheduler.sql
4343
metadata.sql
4444
uuidv7.sql
45+
ts_stats.sql
4546
views.sql
4647
views_experimental.sql
4748
gapfill.sql

sql/ts_stats.sql

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
-- This file and its contents are licensed under the Apache License 2.0.
2+
-- Please see the included NOTICE for copyright information and
3+
-- LICENSE-APACHE for a copy of the license.
4+
5+
-- Chunk statistics. This function takes three optional parameters:
6+
-- - compressed_relid: if provided, returns stats for the specified compressed chunk only; otherwise, returns stats for all chunks.
7+
-- - uncompressed_relid: if provided, returns stats for the specified user chunk only; if compressed_relid is also provided, both must agree.
8+
-- - since: if provided, returns stats for operations that occurred since the specified timestamp; otherwise, returns all stats.
9+
--
10+
CREATE OR REPLACE FUNCTION _timescaledb_functions.chunk_statistics(
11+
compressed_relid regclass DEFAULT NULL,
12+
uncompressed_relid regclass DEFAULT NULL,
13+
since timestamptz DEFAULT NULL
14+
)
15+
RETURNS TABLE (
16+
compressed_relid oid,
17+
uncompressed_relid oid,
18+
-- Compression
19+
compressed_batch_count bigint,
20+
compressed_block_count bigint,
21+
compressed_batch_rows_min bigint,
22+
compressed_batch_rows_max bigint,
23+
compressed_batch_rows_sum bigint,
24+
compressed_batch_rows_sqsum double precision,
25+
compressed_batch_bytes_min bigint,
26+
compressed_batch_bytes_max bigint,
27+
compressed_batch_bytes_sum bigint,
28+
compressed_batch_bytes_sqsum double precision,
29+
compressed_block_bytes_min bigint,
30+
compressed_block_bytes_max bigint,
31+
compressed_block_bytes_sum bigint,
32+
compressed_block_bytes_sqsum double precision,
33+
-- DML totals (cumulative, from cmd_totals)
34+
total_batches_deleted bigint,
35+
total_batches_decompressed bigint,
36+
total_tuples_decompressed bigint,
37+
total_batches_scanned bigint,
38+
total_batches_checked_by_bloom bigint,
39+
total_batches_pruned_by_bloom bigint,
40+
total_batches_without_bloom bigint,
41+
total_batches_bloom_false_positives bigint,
42+
total_batches_filtered_compressed bigint,
43+
total_batches_filtered_decompressed bigint,
44+
-- DML last operation snapshot (from cmd_last_op)
45+
last_op_batches_deleted bigint,
46+
last_op_batches_decompressed bigint,
47+
last_op_tuples_decompressed bigint,
48+
last_op_batches_scanned bigint,
49+
last_op_batches_checked_by_bloom bigint,
50+
last_op_batches_pruned_by_bloom bigint,
51+
last_op_batches_without_bloom bigint,
52+
last_op_batches_bloom_false_positives bigint,
53+
last_op_batches_filtered_compressed bigint,
54+
last_op_batches_filtered_decompressed bigint,
55+
-- Operation counts
56+
n_selects bigint,
57+
n_inserts bigint,
58+
n_updates bigint,
59+
n_deletes bigint,
60+
-- Timestamps
61+
first_update timestamptz,
62+
last_update timestamptz
63+
)
64+
AS '@MODULE_PATHNAME@', 'ts_stats_chunks'
65+
LANGUAGE C STABLE;
66+
67+
-- Logical reset: clears all cached chunks under the segment lock.
68+
CREATE OR REPLACE FUNCTION _timescaledb_functions.chunk_statistics_reset()
69+
RETURNS VOID
70+
AS '@MODULE_PATHNAME@', 'ts_stats_reset'
71+
LANGUAGE C VOLATILE;

sql/updates/reverse-dev.sql

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,8 @@ DROP FUNCTION IF EXISTS _timescaledb_functions.bloom1_hash(anyelement);
99
-- Drop BIGINT-returning version so the downgrade script can recreate the
1010
-- INTEGER-returning version of compressed_data_column_size.
1111
DROP FUNCTION IF EXISTS _timescaledb_functions.compressed_data_column_size(_timescaledb_internal.compressed_data, ANYELEMENT);
12+
13+
DROP VIEW IF EXISTS timescaledb_information.stat_chunk_activity;
14+
DROP FUNCTION IF EXISTS _timescaledb_functions.chunk_statistics(regclass, regclass, timestamptz);
15+
DROP FUNCTION IF EXISTS _timescaledb_functions.chunk_statistics_reset();
16+

sql/views.sql

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,5 +431,74 @@ SELECT * FROM timescaledb_information.chunk_compression_settings;
431431
CREATE OR REPLACE VIEW _timescaledb_config.bgw_job AS
432432
SELECT * from _timescaledb_catalog.bgw_job;
433433

434+
-- chunk statistics view
435+
CREATE OR REPLACE VIEW timescaledb_information.stat_chunk_activity AS
436+
SELECT
437+
o.uncompressed_relid::regclass AS chunk,
438+
o.compressed_relid::regclass AS compressed_chunk,
439+
c.id AS chunk_id,
440+
c.hypertable_id,
441+
h.table_name AS hypertable,
442+
-- Compression
443+
o.compressed_batch_count,
444+
o.compressed_block_count,
445+
o.compressed_batch_rows_min,
446+
o.compressed_batch_rows_max,
447+
o.compressed_batch_rows_sum / NULLIF(o.compressed_batch_count, 0)
448+
AS compressed_batch_rows_avg,
449+
sqrt(o.compressed_batch_rows_sqsum / NULLIF(o.compressed_batch_count, 0)
450+
- power(o.compressed_batch_rows_sum::double precision
451+
/ NULLIF(o.compressed_batch_count, 0), 2))
452+
AS compressed_batch_rows_stddev,
453+
o.compressed_batch_bytes_min,
454+
o.compressed_batch_bytes_max,
455+
o.compressed_batch_bytes_sum / NULLIF(o.compressed_batch_count, 0)
456+
AS compressed_batch_bytes_avg,
457+
sqrt(o.compressed_batch_bytes_sqsum / NULLIF(o.compressed_batch_count, 0)
458+
- power(o.compressed_batch_bytes_sum::double precision
459+
/ NULLIF(o.compressed_batch_count, 0), 2))
460+
AS compressed_batch_bytes_stddev,
461+
o.compressed_block_bytes_min,
462+
o.compressed_block_bytes_max,
463+
o.compressed_block_bytes_sum / NULLIF(o.compressed_block_count, 0)
464+
AS compressed_block_bytes_avg,
465+
sqrt(o.compressed_block_bytes_sqsum / NULLIF(o.compressed_block_count, 0)
466+
- power(o.compressed_block_bytes_sum::double precision
467+
/ NULLIF(o.compressed_block_count, 0), 2))
468+
AS compressed_block_bytes_stddev,
469+
-- CMD totals
470+
o.total_batches_deleted,
471+
o.total_batches_decompressed,
472+
o.total_tuples_decompressed,
473+
o.total_batches_scanned,
474+
o.total_batches_checked_by_bloom,
475+
o.total_batches_pruned_by_bloom,
476+
o.total_batches_without_bloom,
477+
o.total_batches_bloom_false_positives,
478+
o.total_batches_filtered_compressed,
479+
o.total_batches_filtered_decompressed,
480+
-- last CMD operation
481+
o.last_op_batches_deleted,
482+
o.last_op_batches_decompressed,
483+
o.last_op_tuples_decompressed,
484+
o.last_op_batches_scanned,
485+
o.last_op_batches_checked_by_bloom,
486+
o.last_op_batches_pruned_by_bloom,
487+
o.last_op_batches_without_bloom,
488+
o.last_op_batches_bloom_false_positives,
489+
o.last_op_batches_filtered_compressed,
490+
o.last_op_batches_filtered_decompressed,
491+
o.n_selects,
492+
o.n_inserts,
493+
o.n_updates,
494+
o.n_deletes,
495+
o.first_update,
496+
o.last_update
497+
FROM _timescaledb_functions.chunk_statistics() o
498+
LEFT JOIN _timescaledb_catalog.chunk c
499+
ON format('%I.%I', c.schema_name, c.table_name)::regclass = o.uncompressed_relid
500+
LEFT JOIN _timescaledb_catalog.hypertable h ON h.id = c.hypertable_id;
501+
502+
434503
GRANT SELECT ON ALL TABLES IN SCHEMA _timescaledb_config TO PUBLIC;
435504
GRANT SELECT ON ALL TABLES IN SCHEMA timescaledb_information TO PUBLIC;

src/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ add_subdirectory(ts_catalog)
126126
add_subdirectory(nodes)
127127
add_subdirectory(planner)
128128
add_subdirectory(with_clause)
129+
add_subdirectory(ts_stats)
129130

130131
# Don't run clang-tidy on the files we copied from Postgres. We don't want to
131132
# introduce changes there unless absolutely necessary. CMake can only access the

src/guc.c

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,8 @@ TSDLLEXPORT bool ts_guc_enable_delete_after_compression = false;
153153
TSDLLEXPORT bool ts_guc_enable_merge_on_cagg_refresh = false;
154154

155155
bool ts_guc_enable_partitioned_hypertables = false;
156+
TSDLLEXPORT int ts_guc_observ_max_chunks = TS_OBSERV_MAX_CHUNKS_DEFAULT;
157+
156158
#if PG16_GE
157159
TSDLLEXPORT bool ts_guc_enable_cagg_rewrites = false;
158160
TSDLLEXPORT bool ts_guc_cagg_rewrites_debug_info = false;
@@ -493,6 +495,46 @@ assign_default_chunk_time_interval(const char *newval, void *extra)
493495
default_chunk_time_interval = extra;
494496
}
495497

498+
/*
499+
* check_hook: accept 0 (feature disabled) or a power of two in [MIN, MAX].
500+
* Non-power-of-two values are rejected with a hint listing valid sizes.
501+
*/
502+
static bool
503+
observ_max_chunks_check_hook(int *newval, void **extra, GucSource source)
504+
{
505+
int v = *newval;
506+
507+
if (v == 0)
508+
{
509+
return true; /* 0 = disabled, explicitly allowed */
510+
}
511+
512+
if (v < TS_OBSERV_MAX_CHUNKS_MIN)
513+
{
514+
GUC_check_errdetail("Minimum cache capacity is %d chunks. "
515+
"Set to 0 to disable the observability feature.",
516+
TS_OBSERV_MAX_CHUNKS_MIN);
517+
return false;
518+
}
519+
520+
if (v > TS_OBSERV_MAX_CHUNKS_MAX)
521+
{
522+
GUC_check_errdetail("Maximum cache capacity is %d chunks "
523+
"(largest power of 2 within the slot index space).",
524+
TS_OBSERV_MAX_CHUNKS_MAX);
525+
return false;
526+
}
527+
528+
if ((v & (v - 1)) != 0)
529+
{
530+
GUC_check_errdetail("timescaledb.observ_max_chunks must be 0 (disabled) or a power of 2.");
531+
GUC_check_errhint("Valid values: 0, 64, 128, 256, 512, 1024, 2048, "
532+
"4096, 8192, 16384, 32768.");
533+
return false;
534+
}
535+
return true;
536+
}
537+
496538
void
497539
_guc_init(void)
498540
{
@@ -1423,6 +1465,22 @@ _guc_init(void)
14231465
NULL);
14241466
#endif
14251467

1468+
DefineCustomIntVariable(MAKE_EXTOPTION("observ_max_chunks"),
1469+
"Per-database observability cache capacity, "
1470+
"in chunks. 0 disables the feature.",
1471+
"Must be 0 or a power of 2. "
1472+
"Takes effect only for databases whose observability segment "
1473+
"has not yet been created.",
1474+
&ts_guc_observ_max_chunks,
1475+
TS_OBSERV_MAX_CHUNKS_DEFAULT, /* default */
1476+
0, /* min: 0 = disabled */
1477+
TS_OBSERV_MAX_CHUNKS_MAX, /* max: 2^15 = 32768 chunks */
1478+
PGC_SIGHUP,
1479+
0,
1480+
observ_max_chunks_check_hook,
1481+
NULL,
1482+
NULL);
1483+
14261484
#ifdef USE_TELEMETRY
14271485
DefineCustomEnumVariable(MAKE_EXTOPTION("telemetry_level"),
14281486
"Telemetry settings level",

src/guc.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,13 @@ extern TSDLLEXPORT bool ts_guc_enable_rowlevel_compression_locking;
160160
extern TSDLLEXPORT DebugRequireOption ts_guc_debug_require_batch_sorted_merge;
161161

162162
extern bool ts_guc_enable_partitioned_hypertables;
163+
extern TSDLLEXPORT int ts_guc_observ_max_chunks;
164+
165+
#define TS_OBSERV_MAX_CHUNKS_DEFAULT 1024
166+
#define TS_OBSERV_MAX_CHUNKS_MIN 64
167+
#define TS_OBSERV_MAX_CHUNKS_MAX 32768 /* largest power of two <= INVALID_IDX */
168+
169+
#define IS_OBSERV_CHUNKS_ENABLED() (ts_guc_observ_max_chunks > 0)
163170

164171
void _guc_init(void);
165172

src/loader/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ set(SOURCES
55
bgw_launcher.c
66
bgw_interface.c
77
function_telemetry.c
8+
ts_stats_handles.c
89
lwlocks.c)
910

1011
set(TEST_SOURCES ${PROJECT_SOURCE_DIR}/test/src/symbol_conflict.c)

src/loader/loader.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
#include "loader/function_telemetry.h"
4242
#include "loader/loader.h"
4343
#include "loader/lwlocks.h"
44+
#include "loader/ts_stats_handles.h"
4445

4546
/*
4647
* Loading process:
@@ -623,6 +624,9 @@ timescaledb_shmem_startup_hook(void)
623624
ts_bgw_message_queue_shmem_startup();
624625
ts_lwlocks_shmem_startup();
625626
ts_function_telemetry_shmem_startup();
627+
#if PG17_LT
628+
ts_stats_shmem_startup();
629+
#endif
626630
}
627631

628632
/*
@@ -642,6 +646,9 @@ timescaledb_shmem_request_hook(void)
642646
ts_bgw_message_queue_alloc();
643647
ts_lwlocks_shmem_alloc();
644648
ts_function_telemetry_shmem_alloc();
649+
#if PG17_LT
650+
ts_stats_shmem_request();
651+
#endif
645652
}
646653

647654
static void

0 commit comments

Comments
 (0)