Skip to content

Commit 275f1cf

Browse files
stats: make table statistics cache size configurable
Replace the hardcoded table statistics cache size (previously passed through server config plumbing as `defaultSQLTableStatCacheSize = 256`) with a cluster setting `sql.stats.table_statistics_cache.capacity`. The `ShouldEvict` callback now reads the setting dynamically, so the cache resizes on the next insertion after the setting is changed. This removes the `cacheSize int` parameter from `NewTableStatisticsCache` along with the `SQLConfig.TableStatCacheSize` field and associated plumbing through `server_controller_new_server.go`. Informs: #54030 Release note (sql change): The new cluster setting `sql.stats.table_statistics_cache.capacity` controls the maximum number of tables whose statistics are retained in the in-memory LRU cache (default: 256). Co-Authored-By: roachdev-claude <roachdev-claude-bot@cockroachlabs.com>
1 parent 8ba05e7 commit 275f1cf

10 files changed

Lines changed: 118 additions & 29 deletions

File tree

docs/generated/settings/settings-for-tenants.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,7 @@ sql.stats.response.max integer 20000 the maximum number of statements and transa
378378
sql.stats.response.show_internal.enabled boolean false controls if statistics for internal executions should be returned by the CombinedStatements and if internal sessions should be returned by the ListSessions endpoints. These endpoints are used to display statistics on the SQL Activity pages application
379379
sql.stats.system_tables.enabled boolean true when true, enables use of statistics on system tables by the query optimizer application
380380
sql.stats.system_tables_autostats.enabled boolean true when true, enables automatic collection of statistics on system tables application
381+
sql.stats.table_statistics_cache.capacity integer 256 the maximum number of table statistics entries stored in the LRU cache application
381382
sql.stats.virtual_computed_columns.enabled boolean true set to true to collect table statistics on virtual computed columns application
382383
sql.telemetry.query_sampling.enabled boolean false when set to true, executed queries will emit an event on the telemetry logging channel application
383384
sql.telemetry.query_sampling.internal.enabled boolean false when set to true, internal queries will be sampled in telemetry logging application

docs/generated/settings/settings.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,7 @@
333333
<tr><td><div id="setting-sql-stats-response-show-internal-enabled" class="anchored"><code>sql.stats.response.show_internal.enabled</code></div></td><td>boolean</td><td><code>false</code></td><td>controls if statistics for internal executions should be returned by the CombinedStatements and if internal sessions should be returned by the ListSessions endpoints. These endpoints are used to display statistics on the SQL Activity pages</td><td>Serverless/Dedicated/Self-Hosted</td></tr>
334334
<tr><td><div id="setting-sql-stats-system-tables-enabled" class="anchored"><code>sql.stats.system_tables.enabled</code></div></td><td>boolean</td><td><code>true</code></td><td>when true, enables use of statistics on system tables by the query optimizer</td><td>Serverless/Dedicated/Self-Hosted</td></tr>
335335
<tr><td><div id="setting-sql-stats-system-tables-autostats-enabled" class="anchored"><code>sql.stats.system_tables_autostats.enabled</code></div></td><td>boolean</td><td><code>true</code></td><td>when true, enables automatic collection of statistics on system tables</td><td>Serverless/Dedicated/Self-Hosted</td></tr>
336+
<tr><td><div id="setting-sql-stats-table-statistics-cache-capacity" class="anchored"><code>sql.stats.table_statistics_cache.capacity</code></div></td><td>integer</td><td><code>256</code></td><td>the maximum number of table statistics entries stored in the LRU cache</td><td>Serverless/Dedicated/Self-Hosted</td></tr>
336337
<tr><td><div id="setting-sql-stats-virtual-computed-columns-enabled" class="anchored"><code>sql.stats.virtual_computed_columns.enabled</code></div></td><td>boolean</td><td><code>true</code></td><td>set to true to collect table statistics on virtual computed columns</td><td>Serverless/Dedicated/Self-Hosted</td></tr>
337338
<tr><td><div id="setting-sql-telemetry-query-sampling-enabled" class="anchored"><code>sql.telemetry.query_sampling.enabled</code></div></td><td>boolean</td><td><code>false</code></td><td>when set to true, executed queries will emit an event on the telemetry logging channel</td><td>Serverless/Dedicated/Self-Hosted</td></tr>
338339
<tr><td><div id="setting-sql-telemetry-query-sampling-internal-enabled" class="anchored"><code>sql.telemetry.query_sampling.internal.enabled</code></div></td><td>boolean</td><td><code>false</code></td><td>when set to true, internal queries will be sampled in telemetry logging</td><td>Serverless/Dedicated/Self-Hosted</td></tr>

pkg/server/config.go

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,6 @@ const (
8787
minimumNetworkFileDescriptors = 256
8888
recommendedNetworkFileDescriptors = 5000
8989

90-
defaultSQLTableStatCacheSize = 256
91-
9290
// This comes out to 1024 cache entries.
9391
defaultSQLQueryCacheSize = 8 * 1024 * 1024
9492
)
@@ -500,10 +498,6 @@ type SQLConfig struct {
500498
// used by SQL clients to store row data in server RAM.
501499
MemoryPoolSize int64
502500

503-
// TableStatCacheSize is the size (number of tables) of the table
504-
// statistics cache.
505-
TableStatCacheSize int
506-
507501
// QueryCacheSize is the memory size (in bytes) of the query plan cache.
508502
QueryCacheSize int64
509503

@@ -570,7 +564,6 @@ func (sqlCfg *SQLConfig) SetDefaults(tempStorageCfg base.TempStorageConfig) {
570564
tenName := sqlCfg.TenantName
571565
*sqlCfg = SQLConfig{TenantID: tenID, TenantName: tenName}
572566
sqlCfg.MemoryPoolSize = defaultSQLMemoryPoolSize
573-
sqlCfg.TableStatCacheSize = defaultSQLTableStatCacheSize
574567
sqlCfg.QueryCacheSize = defaultSQLQueryCacheSize
575568
sqlCfg.TempStorageConfig = tempStorageCfg
576569
sqlCfg.LicenseEnforcer = license.NewEnforcer(nil)

pkg/server/server_controller_new_server.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,6 @@ func makeSharedProcessTenantServerConfig(
354354
// of them will be mostly idle.
355355
// We might want to reconsider this if we use more than 1 in-memory tenant at a time.
356356
sqlCfg.MemoryPoolSize = kvServerCfg.SQLConfig.MemoryPoolSize
357-
sqlCfg.TableStatCacheSize = kvServerCfg.SQLConfig.TableStatCacheSize
358357
sqlCfg.QueryCacheSize = kvServerCfg.SQLConfig.QueryCacheSize
359358

360359
// LocalKVServerInfo tells the rpc.Context of the tenant's server

pkg/server/server_sql.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1039,7 +1039,6 @@ func newSQLServer(ctx context.Context, cfg sqlServerArgs) (*SQLServer, error) {
10391039
),
10401040

10411041
TableStatsCache: stats.NewTableStatisticsCache(
1042-
cfg.TableStatCacheSize,
10431042
cfg.Settings,
10441043
cfg.internalDB,
10451044
cfg.stopper,

pkg/sql/logictest/testdata/logic_test/stats

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,3 +122,32 @@ query II
122122
SELECT row_count, distinct_count FROM [SHOW STATISTICS FOR TABLE t141448] WHERE column_names = ARRAY['b'];
123123
----
124124
3 1
125+
126+
subtest table_stats_cache_settings
127+
128+
# Verify the table statistics cache capacity setting.
129+
query I
130+
SHOW CLUSTER SETTING sql.stats.table_statistics_cache.capacity
131+
----
132+
256
133+
134+
statement ok
135+
SET CLUSTER SETTING sql.stats.table_statistics_cache.capacity = 128
136+
137+
query I
138+
SHOW CLUSTER SETTING sql.stats.table_statistics_cache.capacity
139+
----
140+
128
141+
142+
statement ok
143+
RESET CLUSTER SETTING sql.stats.table_statistics_cache.capacity
144+
145+
query I
146+
SHOW CLUSTER SETTING sql.stats.table_statistics_cache.capacity
147+
----
148+
256
149+
150+
statement error cannot be set to a negative value
151+
SET CLUSTER SETTING sql.stats.table_statistics_cache.capacity = -1
152+
153+
subtest end

pkg/sql/stats/automatic_stats_test.go

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,6 @@ func TestMaybeRefreshStats(t *testing.T) {
6767
internalDB := s.InternalDB().(descs.DB)
6868
descA := desctestutils.TestingGetPublicTableDescriptor(s.DB(), codec, "t", "a")
6969
cache := NewTableStatisticsCache(
70-
10, /* cacheSize */
7170
s.ClusterSettings(),
7271
s.InternalDB().(descs.DB),
7372
s.AppStopper(),
@@ -229,7 +228,6 @@ func TestEnsureAllTablesQueries(t *testing.T) {
229228

230229
internalDB := s.InternalDB().(descs.DB)
231230
cache := NewTableStatisticsCache(
232-
10, /* cacheSize */
233231
s.ClusterSettings(),
234232
s.InternalDB().(descs.DB),
235233
s.AppStopper(),
@@ -332,7 +330,6 @@ func BenchmarkEnsureAllTables(b *testing.B) {
332330

333331
internalDB := s.InternalDB().(descs.DB)
334332
cache := NewTableStatisticsCache(
335-
10, /* cacheSize */
336333
s.ClusterSettings(),
337334
s.InternalDB().(descs.DB),
338335
s.AppStopper(),
@@ -406,7 +403,6 @@ func TestAverageRefreshTime(t *testing.T) {
406403
internalDB := s.InternalDB().(descs.DB)
407404
table := desctestutils.TestingGetPublicTableDescriptor(s.DB(), codec, "t", "a")
408405
cache := NewTableStatisticsCache(
409-
10, /* cacheSize */
410406
s.ClusterSettings(),
411407
s.InternalDB().(descs.DB),
412408
s.AppStopper(),
@@ -656,7 +652,6 @@ func TestAutoStatsReadOnlyTables(t *testing.T) {
656652

657653
internalDB := s.InternalDB().(descs.DB)
658654
cache := NewTableStatisticsCache(
659-
10, /* cacheSize */
660655
s.ClusterSettings(),
661656
s.InternalDB().(descs.DB),
662657
s.AppStopper(),
@@ -712,7 +707,6 @@ func TestAutoStatsOnStartupClusterSettingOff(t *testing.T) {
712707

713708
internalDB := s.InternalDB().(descs.DB)
714709
cache := NewTableStatisticsCache(
715-
10, /* cacheSize */
716710
s.ClusterSettings(),
717711
s.InternalDB().(descs.DB),
718712
s.AppStopper(),
@@ -760,7 +754,6 @@ func TestNoRetryOnFailure(t *testing.T) {
760754

761755
internalDB := s.InternalDB().(descs.DB)
762756
cache := NewTableStatisticsCache(
763-
10, /* cacheSize */
764757
s.ClusterSettings(),
765758
s.InternalDB().(descs.DB),
766759
s.AppStopper(),
@@ -878,7 +871,6 @@ func TestAnalyzeSystemTables(t *testing.T) {
878871
defer evalCtx.Stop(ctx)
879872
executor := s.InternalExecutor().(isql.Executor)
880873
cache := NewTableStatisticsCache(
881-
10, /* cacheSize */
882874
s.ClusterSettings(),
883875
s.InternalDB().(descs.DB),
884876
s.AppStopper(),
@@ -999,7 +991,6 @@ func TestAutoStatsDisabledReadOnlyTenant(t *testing.T) {
999991
internalDB := s.InternalDB().(descs.DB)
1000992
descA := desctestutils.TestingGetPublicTableDescriptor(s.DB(), codec, "t", "a")
1001993
cache := NewTableStatisticsCache(
1002-
10, /* cacheSize */
1003994
s.ClusterSettings(),
1004995
s.InternalDB().(descs.DB),
1005996
s.AppStopper(),

pkg/sql/stats/delete_stats_test.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ func TestDeleteOldStatsForColumns(t *testing.T) {
4040
s := srv.ApplicationLayer()
4141
db := s.InternalDB().(descs.DB)
4242
cache := NewTableStatisticsCache(
43-
10, /* cacheSize */
4443
s.ClusterSettings(),
4544
db,
4645
s.AppStopper(),
@@ -341,7 +340,6 @@ func TestDeleteOldStatsForOtherColumns(t *testing.T) {
341340
s := srv.ApplicationLayer()
342341
db := s.InternalDB().(isql.DB)
343342
cache := NewTableStatisticsCache(
344-
10, /* cacheSize */
345343
s.ClusterSettings(),
346344
s.InternalDB().(descs.DB),
347345
s.AppStopper(),

pkg/sql/stats/stats_cache.go

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"github.com/cockroachdb/cockroach/pkg/kv/kvclient/rangefeed"
1919
"github.com/cockroachdb/cockroach/pkg/kv/kvpb"
2020
"github.com/cockroachdb/cockroach/pkg/roachpb"
21+
"github.com/cockroachdb/cockroach/pkg/settings"
2122
"github.com/cockroachdb/cockroach/pkg/settings/cluster"
2223
"github.com/cockroachdb/cockroach/pkg/sql/catalog"
2324
"github.com/cockroachdb/cockroach/pkg/sql/catalog/catenumpb"
@@ -39,6 +40,17 @@ import (
3940
"github.com/cockroachdb/errors"
4041
)
4142

43+
// TableStatsCacheSize controls the maximum number of table statistics entries
44+
// in the cache.
45+
var TableStatsCacheSize = settings.RegisterIntSetting(
46+
settings.ApplicationLevel,
47+
"sql.stats.table_statistics_cache.capacity",
48+
"the maximum number of table statistics entries stored in the LRU cache",
49+
256,
50+
settings.NonNegativeInt,
51+
settings.WithPublic,
52+
)
53+
4254
// A TableStatistic object holds a statistic for a particular column or group
4355
// of columns.
4456
type TableStatistic struct {
@@ -113,19 +125,22 @@ type cacheEntry struct {
113125
err error
114126
}
115127

116-
// NewTableStatisticsCache creates a new TableStatisticsCache that can hold
117-
// statistics for <cacheSize> tables.
128+
// NewTableStatisticsCache creates a new TableStatisticsCache. The maximum
129+
// number of entries is controlled by the
130+
// sql.stats.table_statistics_cache.capacity cluster setting.
118131
func NewTableStatisticsCache(
119-
cacheSize int, settings *cluster.Settings, db descs.DB, stopper *stop.Stopper,
132+
settings *cluster.Settings, db descs.DB, stopper *stop.Stopper,
120133
) *TableStatisticsCache {
121134
tableStatsCache := &TableStatisticsCache{
122135
db: db,
123136
settings: settings,
124137
stopper: stopper,
125138
}
126139
tableStatsCache.mu.cache = cache.NewUnorderedCache(cache.Config{
127-
Policy: cache.CacheLRU,
128-
ShouldEvict: func(s int, key, value interface{}) bool { return s > cacheSize },
140+
Policy: cache.CacheLRU,
141+
ShouldEvict: func(s int, key, value interface{}) bool {
142+
return s > int(TableStatsCacheSize.Get(&settings.SV))
143+
},
129144
})
130145
return tableStatsCache
131146
}

pkg/sql/stats/stats_cache_test.go

Lines changed: 67 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717

1818
"github.com/cockroachdb/cockroach/pkg/base"
1919
"github.com/cockroachdb/cockroach/pkg/kv/kvclient/rangefeed"
20+
"github.com/cockroachdb/cockroach/pkg/settings/cluster"
2021
"github.com/cockroachdb/cockroach/pkg/sql/catalog/descpb"
2122
"github.com/cockroachdb/cockroach/pkg/sql/catalog/descs"
2223
"github.com/cockroachdb/cockroach/pkg/sql/catalog/desctestutils"
@@ -210,6 +211,65 @@ func initTestData(
210211
return expectedStats, nil
211212
}
212213

214+
// addTestEntry adds a cache entry with the given stats directly. The caller
215+
// must hold sc.mu.
216+
func addTestEntry(
217+
ctx context.Context, sc *TableStatisticsCache, tableID descpb.ID, stats []*TableStatistic,
218+
) *cacheEntry {
219+
e := &cacheEntry{
220+
stats: stats,
221+
waitCond: sync.Cond{L: &sc.mu},
222+
}
223+
sc.mu.cache.Add(tableID, e)
224+
return e
225+
}
226+
227+
func TestCacheCapacitySetting(t *testing.T) {
228+
defer leaktest.AfterTest(t)()
229+
defer log.Scope(t).Close(t)
230+
231+
ctx := context.Background()
232+
st := cluster.MakeTestingClusterSettings()
233+
234+
sc := NewTableStatisticsCache(st, nil /* db */, nil /* stopper */)
235+
236+
sc.mu.Lock()
237+
defer sc.mu.Unlock()
238+
239+
emptyStats := []*TableStatistic{{
240+
TableStatisticProto: TableStatisticProto{
241+
TableID: 1,
242+
StatisticID: 1,
243+
ColumnIDs: []descpb.ColumnID{1},
244+
},
245+
}}
246+
247+
// Default capacity is 256; add 5 entries.
248+
for i := 1; i <= 5; i++ {
249+
addTestEntry(ctx, sc, descpb.ID(i), emptyStats)
250+
}
251+
require.Equal(t, 5, sc.mu.cache.Len())
252+
253+
// Lower the capacity to 3. Existing entries remain until new entries
254+
// push the cache past the limit.
255+
TableStatsCacheSize.Override(ctx, &st.SV, 3)
256+
257+
// Adding a sixth entry should trigger eviction down to the new limit.
258+
addTestEntry(ctx, sc, 6, emptyStats)
259+
require.Equal(t, 3, sc.mu.cache.Len())
260+
261+
// The most recently accessed entries should remain.
262+
_, ok := sc.mu.cache.Get(descpb.ID(6))
263+
require.True(t, ok, "newest entry should be in cache")
264+
265+
// Raise the capacity — should allow more entries without eviction.
266+
TableStatsCacheSize.Override(ctx, &st.SV, 10)
267+
for i := 7; i <= 12; i++ {
268+
addTestEntry(ctx, sc, descpb.ID(i), emptyStats)
269+
}
270+
require.Equal(t, 9, sc.mu.cache.Len())
271+
}
272+
213273
func TestCacheBasic(t *testing.T) {
214274
defer leaktest.AfterTest(t)()
215275
defer log.Scope(t).Close(t)
@@ -235,7 +295,8 @@ func TestCacheBasic(t *testing.T) {
235295
// Create a cache and iteratively query the cache for each tableID. This
236296
// will result in the cache getting populated. When the stats cache size is
237297
// exceeded, entries should be evicted according to the LRU policy.
238-
sc := NewTableStatisticsCache(2 /* cacheSize */, s.ClusterSettings(), db, s.AppStopper())
298+
TableStatsCacheSize.Override(ctx, &s.ClusterSettings().SV, 2)
299+
sc := NewTableStatisticsCache(s.ClusterSettings(), db, s.AppStopper())
239300
require.NoError(t, sc.Start(ctx, s.Codec(), s.RangeFeedFactory().(*rangefeed.Factory)))
240301
for _, tableID := range tableIDs {
241302
checkStatsForTable(ctx, t, sc, expectedStats[tableID], tableID)
@@ -337,7 +398,8 @@ func TestCacheUserDefinedTypes(t *testing.T) {
337398
insqlDB := s.InternalDB().(descs.DB)
338399

339400
// Make a stats cache.
340-
sc := NewTableStatisticsCache(1, s.ClusterSettings(), insqlDB, s.AppStopper())
401+
TableStatsCacheSize.Override(ctx, &s.ClusterSettings().SV, 1)
402+
sc := NewTableStatisticsCache(s.ClusterSettings(), insqlDB, s.AppStopper())
341403
require.NoError(t, sc.Start(ctx, s.Codec(), s.RangeFeedFactory().(*rangefeed.Factory)))
342404
tbl := desctestutils.TestingGetPublicTableDescriptor(kvDB, s.Codec(), "t", "tt")
343405
// Get stats for our table. We are ensuring here that the access to the stats
@@ -390,7 +452,8 @@ func TestCacheWait(t *testing.T) {
390452
tableIDs = append(tableIDs, tableID)
391453
}
392454
sort.Sort(tableIDs)
393-
sc := NewTableStatisticsCache(len(tableIDs) /* cacheSize */, s.ClusterSettings(), db, s.AppStopper())
455+
TableStatsCacheSize.Override(ctx, &s.ClusterSettings().SV, int64(len(tableIDs)))
456+
sc := NewTableStatisticsCache(s.ClusterSettings(), db, s.AppStopper())
394457
require.NoError(t, sc.Start(ctx, s.Codec(), s.RangeFeedFactory().(*rangefeed.Factory)))
395458
for _, tableID := range tableIDs {
396459
checkStatsForTable(ctx, t, sc, expectedStats[tableID], tableID)
@@ -438,8 +501,8 @@ func TestCacheAutoRefresh(t *testing.T) {
438501
tc := serverutils.StartCluster(t, 3 /* numNodes */, base.TestClusterArgs{})
439502
defer tc.Stopper().Stop(ctx)
440503
s := tc.ApplicationLayer(0)
504+
TableStatsCacheSize.Override(ctx, &s.ClusterSettings().SV, 10)
441505
sc := NewTableStatisticsCache(
442-
10, /* cacheSize */
443506
s.ClusterSettings(),
444507
s.InternalDB().(descs.DB),
445508
s.AppStopper(),

0 commit comments

Comments
 (0)