Repository navigation
[Ideas] pluggable cumulative statistics in Cloudberry #2108
Unanswered
Alena0704
asked this question in
Ideas / Feature Requests
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Description
Proposal: pluggable cumulative statistics in Cloudberry
The idea and intended result
Pluggable cumulative statistics let extensions define their own statistics and store them through the same infrastructure as built-in statistics. An extension registers a statistics kind describing its data, and the common system provides shared-memory storage, reads, resets, and persistence across clean restarts.
We want new statistics and their SQL interfaces to evolve in extensions. Adding an extension-owned metric should not require adding a built-in catalog function or system-view column and bumping the core catalog version for that change. Extensions should also be able to reuse the common statistics store instead of implementing storage, synchronization, resets, and persistence independently.
Pluggable statistics provide the storage mechanism. The extension author decides what to count, where to update the counters, and how to expose them through SQL. New measurement points may still require core hooks.
How extensions and users would use it
An extension developer defines counters, registers their kind at server startup, and connects measurement code or reporting hooks to updates of those counters. The extension supplies SQL functions and views for reading the values, plus any collection controls and reset functions it needs.
An operator loads the module through
shared_preload_libraries, restarts the server, and installs the extension's SQL objects in the relevant databases. Monitoring then queries the extension's views and compares observations over time, accounting for resets. In Cloudberry, the extension also defines how local observations are exposed and aggregated across the coordinator and segments.This gives each consumer control over its metrics and SQL interface while keeping storage and lifecycle operations in the common cumulative-statistics system. The VACUUM example below follows this path from measurement to SQL.
What Cloudberry has today and why it is insufficient
Before the proposed port, Cloudberry already has a cumulative-statistics infrastructure for built-in kinds, including Cloudberry-specific resource-queue statistics. What is missing is the supported registration API that lets extensions define their own kinds and participate in that infrastructure.
There are two existing approaches to adding statistics, each with a limitation:
For example,
pg_stat_statementsmaintains its own shared hash table with a limit set bypg_stat_statements.maxand removes entries when it fills up. This illustrates the separate storage machinery an extension maintains today. pg_stat_statements implementationThe common cumulative-statistics system also uses a hash table, backed by dynamic shared memory. It allocates memory as entries are added, without a configured entry-count limit like
pg_stat_statements.max. Making this store available to custom kinds would let extensions reuse it. Statistics memory allocationProposed implementation and how it works
This mechanism has already been added to PostgreSQL and is documented in PostgreSQL 19. PR #2102 proposes bringing it to Cloudberry by cherry-picking the upstream commits and adapting them, including preservation of Cloudberry-specific resource-queue statistics.
The implementation extends the existing cumulative-statistics infrastructure with registration and access APIs. Its operation and division of responsibilities are described first, followed by the commits that introduce them.
Registration and storage
An extension describes a statistics kind with
PgStat_KindInfoand registers it throughpgstat_register_kind()while loading throughshared_preload_libraries.Two object models are supported:
The kind descriptor defines the data layout, storage properties, and applicable callbacks. Extensions can use pending local updates with callbacks to flush them into shared memory. The
write_to_fileproperty controls whether statistics participate in persistence.The PR also adds an optional
reset_data_cbfor variable-numbered kinds. It lets a reset clear counters while preserving identifying metadata in the entry payload. Without the callback, the existing zero-fill behavior remains. PR #2102Limitations
Dynamic allocation does not guarantee that a statistics update will succeed when memory is exhausted. Variable-numbered kinds share the cumulative-statistics memory infrastructure. Their memory use grows with the number of tracked objects and the size of each entry, with additional overhead for the shared hash table, backend-local references, pending updates, and snapshots. There is no per-kind entry-count limit or automatic eviction policy comparable to
pg_stat_statements.max.When a new entry is needed, the current implementation inserts it into the shared hash table and allocates its payload with
dsa_allocate0(). This allocation does not useDSA_ALLOC_NO_OOM: if memory cannot be allocated, the allocator raisesERROR. The infrastructure therefore does not guarantee that it will simply skip a statistics sample under memory pressure. The error can propagate to the operation collecting the statistics, including a VACUUM reporting through an extension hook. See the local implementation in pgstat_shmem.c, dsa.h, and dsa.c.This makes memory budgeting and cleanup part of extension design. Extensions should keep payloads compact, avoid unnecessarily high-cardinality keys, and connect object deletion to entry cleanup. A per-kind quota, eviction policy, or a safe way to drop a sample on allocation failure would require additional design; the proposed API does not provide these guarantees. Memory-pressure tests should establish both the error seen by the caller and whether subsequent collection remains usable.
Other limitations also affect consumers:
Commits cherry-picked from PostgreSQL
PR #2102 includes 15 PostgreSQL commits. In application order, they prepare the existing statistics code for extensions, add the registration API, and include follow-up fixes. The links below point to the original PostgreSQL commits recorded in the cherry-pick messages. The purpose column explains each change's role in the port, based on those messages. The series includes preparatory refactoring, the registration API itself, and subsequent usability, correctness, and performance changes; not every commit is independently required to register a custom kind.
named_on_diskflag; existing callbacks already control name serialization.PgStat_KindInfointo the custom-kind interface. Name serialization is already determined by callbacks, so this keeps a single source of truth; it does not add a new capability.Frecord. This is supporting refactoring: it makes the subsequent format change explicit, while leaving the format and behavior unchanged on its own.init_shmem_cbso a kind can initialize its shared-memory area.Ffile entry containing the kind ID, allowing fixed-numbered kinds to be identified when loading saved statistics.PgStat_Kindfrom an enum touint32to support extension-defined IDs.PgStat_Kindbecomesuint32. This is a correctness cleanup for the preceding changes, not an additional extension API.pgStatLocalsnapshot layout. Registration alone would leave that access dependent on internals.shared_preload_librariescan leave saved records whose kind is no longer registered. The checks prevent null-pointer crashes for fixed-numbered records and named variable-numbered records; they do not guarantee that the remaining statistics are preserved.write_to_file, allowing each kind to choose whether its values are saved at shutdown.pgstat_kind.h.pgstat.h. This is interface organization, not a prerequisite for registration itself.pgstat_report_stat(). Smaller bounds reduce that cost even for simple queries, at the price of fewer available custom-kind IDs.The backports keep Cloudberry's
PGSTAT_KIND_RESQUEUEand omit changes to PostgreSQL'sinjection_pointstest module, which is absent here. Two additional commits adapt the mechanism to Cloudberry; these are not PostgreSQL cherry-picks:reset_data_cbRisks, limitations, and compatibility
The recommendations below describe how an extension can work within the proposed API. They are design and operational guidance, not additional guarantees implemented by PR #2102. Some risks can be reduced in extension code; transparent payload migration, safe module removal without losing other statistics, and graceful handling of allocation failure remain infrastructure questions.
Collection and startup requirements
An extension supplies its measurement hooks and SQL interface. Custom kinds are registered at server startup, so initially enabling a module requires a restart. The current port accepts custom kind IDs 24–32 inclusive: nine slots shared across loaded extensions, with 24 designated as the experimental ID. IDs outside this range are rejected by
pgstat_register_kind().This is a compile-time bound, not a fundamental limit of pluggable statistics. It can be increased in core by raising
PGSTAT_KIND_MAXinpgstat_kind.h(the custom maximum and array sizes derive from it), rebuilding the server and affected extensions, and deploying compatible builds across the cluster. It cannot be raised through a GUC or by an extension alone. The upstream series originally allowed IDs 128–256; the later reduction lowered array/iteration overhead, including the statistics-reporting path for simple queries. A larger range therefore needs performance and compatibility validation. The limit counts kinds, not counters within a payload or objects stored under a kind.Recommended extension design: keep kind IDs stable across releases and coordinate their allocation with other supported extensions. Use one kind for a family of objects with a compatible layout, rather than allocating a kind per metric or per object. Document the required preload configuration and deploy compatible extension binaries and kind assignments on the coordinator and all relevant segments.
Provide a runtime collection switch that hooks check before doing extension-specific collection or updates. This lets operators pause collection without unloading the module or unregistering its kinds. Disabling the consumer does not necessarily remove the cost of instrumentation in core, so measure unloaded, loaded-but-disabled, and enabled configurations separately. The initial restart requirement remains; the switch only controls an already-loaded extension.
Persistence and module compatibility
With
write_to_file = true, a kind's statistics survive a clean restart when the saved format and payload layout remain compatible. After a crash, cumulative counters are reset: this is existing PostgreSQL behavior, not a new limitation introduced by pluggable statistics. Usewrite_to_file = falsefor kinds whose values should cover only the current server lifetime. PostgreSQL cumulative-statistics documentationPR #2102 changes the statistics-file format, so the first startup with an old-format file resets cumulative statistics, including built-in counters. Subsequent clean restarts with a compatible format preserve persisted values. This is an upgrade event that must be documented; it does not remove table data or the planner statistics stored by
ANALYZE. PR #2102Proposed solution for extension upgrades: export and import
An extension can provide its own export and import functions to preserve its counters across an incompatible upgrade. The extension knows the meaning of its metrics and can convert an old exported representation into its new entry layout. Import restores live counters through the shared statistics APIs, so collection can continue from the restored values.
The proposed upgrade procedure is:
The importer should replace the target values, rather than blindly add to them, so retrying an interrupted import cannot double-count statistics. Restrict import to authorized users, reject unsupported versions, and explicitly report missing or recreated objects. Preserve reset metadata only where counter continuity has actually been maintained; changed metric semantics require a new baseline.
This is a proposed extension feature: the current
ext_vacuum_statisticsinterface has read and reset functions, but no export/import implementation. Its upgrade procedure must also define how the new server handles the old binary statistics file before import. Logical export/import restores the extension's values after startup; it does not itself make that file compatible.Stopping collection or removing the module
To stop collection temporarily, keep the module preloaded and disable collection through its setting. Its kinds remain registered, so the server can still recognize their saved entries.
For permanent removal, export the extension's values if historical observations are needed. They can remain in a regular table or monitoring store; live custom counters require a loaded module that registers and understands their kind.
In the current implementation, removing a module whose kinds remain in the saved statistics file can reset all cumulative statistics on that instance. Extension-owned import restores only that extension's counters; it cannot restore built-in or other extensions' statistics automatically. Preserving those requires migration support for the affected kinds or a loader that can safely skip the removed kind. Resetting counters alone does not remove the kind's saved entries. Implementation documentation
Entry lifecycle and reset safety
An extension must connect object creation and deletion to the transactional statistics APIs and preserve entry identity during resets. Mistakes can leave stale counters after DROP or OID reuse.
Recommended extension design: use the transactional creation/deletion APIs rather than immediately deleting statistics from a DROP hook. Verify commit and rollback paths, including creation followed by rollback and reuse of an object OID. If the payload contains a discriminator or other identity metadata, use
reset_data_cbto clear counters while retaining that metadata. Keep SQL reset functions permission-controlled and document whether each reset affects one object, one database, or multiple instances.Expose reset timing or another clear reset boundary in the SQL interface so monitoring can reject deltas spanning a reset. Test these behaviors explicitly; the common storage layer cannot infer which object events or reset semantics an extension intends.
Memory and update overhead
Dynamic allocation does not make memory unlimited. More tracked objects require more memory, and frequent updates or concurrent reads add overhead. An allocation failure can propagate to the collecting operation, as described under Limitations.
Recommended extension design: use compact payloads and bounded object identities, remove obsolete entries through the lifecycle APIs, and estimate memory from the expected object count before enabling collection at scale. Where detailed per-object data is optional, consider database-level aggregates or configurable collection scope. Reducing report frequency can reduce update cost, but does not reduce the memory of entries that remain allocated. Similarly, disabling collection and resetting counters do not by themselves reclaim entries.
Keep shared-entry lock hold times short. For frequent events, consider backend-local accumulation and the API's pending-update callbacks to batch shared writes, while documenting delayed visibility and budgeting the additional local memory. For infrequent operation-completion reports, direct shared updates may be simpler; choose based on measurements.
Set a capacity budget with headroom for DSA growth, hash-table resizing, and concurrent readers. Monitor host/container memory and the capacity of the configured shared-memory backing. Pausing collection before exhaustion can prevent further extension updates, but it is not a recovery mechanism once allocations are failing. A strict per-kind quota, automatic eviction, or a guaranteed “drop the sample and continue” path is not supplied by this proposal. Adding one requires a design that keeps entries, references, and locks consistent across allocation failures; simply catching an allocation error in the hook is not a demonstrated solution.
Cluster reporting and validation
Cluster reporting needs explicit aggregation rules so distributed and replicated relations retain meaningful statistics. A cluster-wide reset can also leave instances with different reset boundaries if only part of the operation succeeds.
Recommended extension interface: expose per-instance rows with instance identity and reset boundaries, then build summary views with documented rules for distributed relations, replicated relations, and database aggregates. Keep raw observations available so users can investigate segment differences. Report partial reset failures rather than treating them as a successful cluster-wide reset, and avoid comparing counter intervals across inconsistent reset boundaries.
Example: VACUUM statistics
ext_vacuum_statisticsin PR #2106 demonstrates how an extension uses pluggable statistics from registration to SQL access.pgstat_register_kind()with itsPgStat_KindInfodescriptions. It registersext_vacuum_statistics_relationfor table and index entries, andext_vacuum_statistics_dbfor database entries. These descriptions specify the data layout, persistence settings, and reset callback.pgstat_get_entry_ref_locked(), updates its counters, and releases the lock. Relation entries are identified by statistics kind, database OID, and relation OID. Their memory is managed by the common statistics system.pgstat_fetch_entry()to read the stored values and return them through views such asext_vacuum_statistics.pg_stats_vacuum_tables.For example, after loading and installing the extension, this query reads its local table statistics:
For example,
ext_vacuum_statisticsprovidesvacuum_statistics.enabled = offto stop updating its counters without unloading the module. Example implementationThe important point is where these statistics are defined and stored: their kinds and SQL interface are supplied by the extension, while storage, reads, resets, and persistence use the existing cumulative statistics mechanism. Other extensions can use the same approach for their own statistics.
The individual VACUUM metrics, their diagnostic uses, and storage memory requirements are covered in the separate VACUUM discussion.
There is no default “statistics fail after X GiB of RAM is occupied” threshold. The statistics DSA starts with 256 KiB in main shared memory, then grows;
pgstat_shmem.cremoves its temporary startup size limit withdsa_set_size_limit(dsa, -1). Neitherwork_memnorshared_buffersis a cap on this statistics store.ext_vacuum_statisticsis an example implementation of an extension using pluggable cumulative statistics. Its tests show how to check the mechanism in a real extension: collection control, resets, persistence, object creation and deletion, and cluster reporting. The extension is described in PR #2106. It builds on PR #2102 for pluggable statistics, PR #2103 for VACUUM resource instrumentation, PR #2104 for native timing and visibility-map statistics, and PR #2105 for native VACUUM work counters. PR #2102 also adds a coordinator/segment test for preserving existing resource-queue statistics across a clean restart.Use case/motivation
No response
Related issues
No response
Are you willing to submit a PR?
All reactions