diff --git a/CHANGELOG.md b/CHANGELOG.md index f6b37552..42bf6eab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,13 @@ ### Added +- **TurboStats databases count their tables.** `DatabaseResources` sends + `table_count`, the tables in the database's own schemas with partitions + included, and `partition_count`, how many of them are partitions. A + table partitioned by day adds a partition a day until retention drops + one, so a partition count that keeps climbing is retention that + stopped. The widest database bundle grows 54 bytes, to 84,091. + - **TurboStats tables carry their schema and their writes.** A `DatabaseTable` sends `schema_hash` every report, a hash of its columns' names, types and nullability, and `schema_changes` in the report where diff --git a/internal/turbostats/dimensional_test.go b/internal/turbostats/dimensional_test.go index cfa66fdd..914386a7 100644 --- a/internal/turbostats/dimensional_test.go +++ b/internal/turbostats/dimensional_test.go @@ -620,6 +620,8 @@ func widestDatabase(tables, replicas, errors, queries, changes int) *wire.Databa SizeBytes: &big, OldestTransactionSeconds: &big, Memory: &wire.DatabaseMemory{SharedBuffersBytes: big}, + TableCount: &n, + PartitionCount: &n, }, Replication: &wire.DatabaseReplication{Role: "primary", LagSeconds: &secs, LastReplayedAt: &at, Upstream: host + ":65535"}, Load: &wire.DatabaseLoad{ diff --git a/turbostats/wire/database.go b/turbostats/wire/database.go index 1383198c..42ec35b3 100644 --- a/turbostats/wire/database.go +++ b/turbostats/wire/database.go @@ -137,6 +137,13 @@ type DatabaseResources struct { // one idle in transaction holds vacuum back. OldestTransactionSeconds *int64 `json:"oldest_transaction_seconds,omitempty"` Memory *DatabaseMemory `json:"memory,omitempty"` + // TableCount is the tables in the database's own schemas, partitions + // included; the system catalogs are not counted. PartitionCount is how + // many of them are partitions. A table partitioned by day adds one a + // day until retention drops one, so a PartitionCount that keeps + // climbing is retention that stopped. + TableCount *int `json:"table_count,omitempty"` + PartitionCount *int `json:"partition_count,omitempty"` } // DatabaseConnections is connections in use against the configured maximum. diff --git a/turbostats/wire/database_test.go b/turbostats/wire/database_test.go index a425505a..f495d37c 100644 --- a/turbostats/wire/database_test.go +++ b/turbostats/wire/database_test.go @@ -163,3 +163,23 @@ func TestDatabaseTable_SchemaAndWritesRoundTrip(t *testing.T) { assert.That(t, !strings.Contains(string(out), "rows_inserted")) assert.Equal(t, 60, MaxDatabaseSchemaChanges) } + +// A database counts its tables and how many of them are partitions. A +// table partitioned by day adds a partition a day until retention drops +// one, so a partition count that keeps climbing is retention that stopped. +func TestDatabaseResources_TableAndPartitionCountsRoundTrip(t *testing.T) { + coverage.Covers(t, "observability.turbostats") + raw := `{"size_bytes":247463936,"table_count":172,"partition_count":148}` + var r DatabaseResources + assert.NoError(t, json.Unmarshal([]byte(raw), &r)) + assert.Equal(t, 172, *r.TableCount) + assert.Equal(t, 148, *r.PartitionCount) + out, err := json.Marshal(r) + assert.NoError(t, err) + assert.Equal(t, raw, string(out)) + + // A kind that cannot count its tables leaves both out. + out, err = json.Marshal(DatabaseResources{}) + assert.NoError(t, err) + assert.Equal(t, `{}`, string(out)) +} diff --git a/turbostats/wire/schema/bundle.schema.json b/turbostats/wire/schema/bundle.schema.json index 34bf4a47..4460f699 100644 --- a/turbostats/wire/schema/bundle.schema.json +++ b/turbostats/wire/schema/bundle.schema.json @@ -618,6 +618,13 @@ "required": [ "shared_buffers_bytes" ] + }, + "table_count": { + "type": "integer", + "description": "TableCount is the tables in the database's own schemas, partitions\nincluded; the system catalogs are not counted. PartitionCount is how\nmany of them are partitions. A table partitioned by day adds one a\nday until retention drops one, so a PartitionCount that keeps\nclimbing is retention that stopped." + }, + "partition_count": { + "type": "integer" } }, "type": "object"