Skip to content

Commit 0b0653a

Browse files
committed
sstable: shard categorized iterator stats
Shard the categorized iterator stats to avoid mutex contention in high-read workloads that are frequently closing sstable iterators.
1 parent 13f37fa commit 0b0653a

File tree

1 file changed

+45
-14
lines changed

1 file changed

+45
-14
lines changed

sstable/category_stats.go

Lines changed: 45 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"slices"
1010
"sync"
1111
"time"
12+
"unsafe"
1213

1314
"github.com/cockroachdb/errors"
1415
"github.com/cockroachdb/redact"
@@ -96,45 +97,75 @@ type CategoryStatsAggregate struct {
9697
CategoryStats CategoryStats
9798
}
9899

100+
const numCategoryStatsShards = 8
101+
99102
type categoryStatsWithMu struct {
100103
mu sync.Mutex
101104
// Protected by mu.
102-
stats CategoryStatsAggregate
105+
stats CategoryStats
103106
}
104107

105108
// CategoryStatsCollector collects and aggregates the stats per category.
106109
type CategoryStatsCollector struct {
107110
// mu protects additions to statsMap.
108111
mu sync.Mutex
109-
// Category => categoryStatsWithMu.
112+
// Category => *shardedCategoryStats.
110113
statsMap sync.Map
111114
}
112115

116+
// shardedCategoryStats accumulates stats for a category, splitting its stats
117+
// across multiple shards to prevent mutex contention. In high-read workloads,
118+
// contention on the category stats mutex has been observed.
119+
type shardedCategoryStats struct {
120+
Category Category
121+
QoSLevel QoSLevel
122+
shards [numCategoryStatsShards]struct {
123+
categoryStatsWithMu
124+
// Pad each shard to 64 bytes so they don't share a cache line.
125+
_ [64 - unsafe.Sizeof(categoryStatsWithMu{})]byte
126+
}
127+
}
128+
129+
// getStats retrieves the aggregated stats for the category, summing across all
130+
// shards.
131+
func (s *shardedCategoryStats) getStats() CategoryStatsAggregate {
132+
agg := CategoryStatsAggregate{
133+
Category: s.Category,
134+
QoSLevel: s.QoSLevel,
135+
}
136+
for i := range s.shards {
137+
s.shards[i].mu.Lock()
138+
agg.CategoryStats.aggregate(s.shards[i].stats)
139+
s.shards[i].mu.Unlock()
140+
}
141+
return agg
142+
}
143+
113144
func (c *CategoryStatsCollector) reportStats(
114-
category Category, qosLevel QoSLevel, stats CategoryStats,
145+
p uintptr, category Category, qosLevel QoSLevel, stats CategoryStats,
115146
) {
116147
v, ok := c.statsMap.Load(category)
117148
if !ok {
118149
c.mu.Lock()
119-
v, _ = c.statsMap.LoadOrStore(category, &categoryStatsWithMu{
120-
stats: CategoryStatsAggregate{Category: category, QoSLevel: qosLevel},
150+
v, _ = c.statsMap.LoadOrStore(category, &shardedCategoryStats{
151+
Category: category,
152+
QoSLevel: qosLevel,
121153
})
122154
c.mu.Unlock()
123155
}
124-
aggStats := v.(*categoryStatsWithMu)
125-
aggStats.mu.Lock()
126-
aggStats.stats.CategoryStats.aggregate(stats)
127-
aggStats.mu.Unlock()
156+
157+
shardedStats := v.(*shardedCategoryStats)
158+
s := p & (numCategoryStatsShards - 1)
159+
shardedStats.shards[s].mu.Lock()
160+
shardedStats.shards[s].stats.aggregate(stats)
161+
shardedStats.shards[s].mu.Unlock()
128162
}
129163

130164
// GetStats returns the aggregated stats.
131165
func (c *CategoryStatsCollector) GetStats() []CategoryStatsAggregate {
132166
var stats []CategoryStatsAggregate
133167
c.statsMap.Range(func(_, v any) bool {
134-
aggStats := v.(*categoryStatsWithMu)
135-
aggStats.mu.Lock()
136-
s := aggStats.stats
137-
aggStats.mu.Unlock()
168+
s := v.(*shardedCategoryStats).getStats()
138169
if len(s.Category) == 0 {
139170
s.Category = "_unknown"
140171
}
@@ -175,6 +206,6 @@ func (accum *iterStatsAccumulator) reportStats(
175206

176207
func (accum *iterStatsAccumulator) close() {
177208
if accum.collector != nil {
178-
accum.collector.reportStats(accum.Category, accum.QoSLevel, accum.stats)
209+
accum.collector.reportStats(uintptr(unsafe.Pointer(accum)), accum.Category, accum.QoSLevel, accum.stats)
179210
}
180211
}

0 commit comments

Comments
 (0)