You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@skywalking.apache.org by wu...@apache.org on 2025/08/18 23:06:22 UTC
(skywalking-banyandb) branch main updated: A patch to the block cache (#727)
This is an automated email from the ASF dual-hosted git repository.
wusheng pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/skywalking-banyandb.git
The following commit(s) were added to refs/heads/main by this push:
new df00ff2c A patch to the block cache (#727)
df00ff2c is described below
commit df00ff2c565d6db7f11875cf754da3db4a3fa25f
Author: Gao Hongtao <ha...@gmail.com>
AuthorDate: Tue Aug 19 06:06:17 2025 +0700
A patch to the block cache (#727)
* Refactor cache implementation to use Sizable interface for memory management
- Updated cache methods to accept and return Sizable objects, enhancing memory size tracking.
- Enhanced blockMetadataArray with Size method to report its memory size accurately.
* Add bypass cache implementation for zero cache size scenarios
- Introduced a new bypassCache type that implements the Cache interface as a no-op cache.
- Updated PreRun methods in dataSVC and standalone to use NewBypassCache when MaxCacheSize is set to zero, allowing for flexible cache management.
---
banyand/internal/storage/cache.go | 90 ++++++++++++++++++++++++++++------
banyand/internal/storage/cache_test.go | 27 +++++++---
banyand/internal/storage/segment.go | 4 +-
banyand/internal/storage/shard.go | 4 +-
banyand/internal/storage/tsdb.go | 4 +-
banyand/measure/block_metadata.go | 26 ++++++++++
banyand/measure/svc_data.go | 6 ++-
banyand/measure/svc_standalone.go | 6 ++-
8 files changed, 138 insertions(+), 29 deletions(-)
diff --git a/banyand/internal/storage/cache.go b/banyand/internal/storage/cache.go
index 5814b798..7a890d82 100644
--- a/banyand/internal/storage/cache.go
+++ b/banyand/internal/storage/cache.go
@@ -28,10 +28,15 @@ import (
"github.com/apache/skywalking-banyandb/pkg/run"
)
+// Sizable represents an object that can report its memory size.
+type Sizable interface {
+ Size() uint64
+}
+
// Cache encapsulates the cache operations.
type Cache interface {
- Get(key EntryKey) any
- Put(key EntryKey, value any)
+ Get(key EntryKey) Sizable
+ Put(key EntryKey, value Sizable)
Close()
Requests() uint64
Misses() uint64
@@ -56,8 +61,9 @@ func DefaultCacheConfig() CacheConfig {
}
type entry struct {
- value any
+ value Sizable
lastAccess uint64
+ size uint64
}
// EntryKey is the key of an entry in the cache.
@@ -97,14 +103,14 @@ func (h entryIndexHeap) Swap(i, j int) {
h[j].index = j
}
-func (h *entryIndexHeap) Push(x interface{}) {
+func (h *entryIndexHeap) Push(x any) {
n := len(*h)
ei := x.(*entryIndex)
ei.index = n
*h = append(*h, ei)
}
-func (h *entryIndexHeap) Pop() interface{} {
+func (h *entryIndexHeap) Pop() any {
old := *h
n := len(old)
x := old[n-1]
@@ -121,6 +127,7 @@ type serviceCache struct {
stopCh chan struct{}
requests uint64
misses uint64
+ currentSize uint64
mu sync.RWMutex
wg sync.WaitGroup
maxCacheSize uint64
@@ -146,6 +153,7 @@ func NewServiceCacheWithConfig(config CacheConfig) Cache {
maxCacheSize: uint64(config.MaxCacheSize),
cleanupInterval: config.CleanupInterval,
idleTimeout: config.IdleTimeout,
+ currentSize: 0,
}
sc.wg.Add(1)
sc.startCleaner()
@@ -159,6 +167,17 @@ func (sc *serviceCache) startCleaner() {
}()
}
+func (sc *serviceCache) removeEntry(key EntryKey) {
+ if entry, exists := sc.entry[key]; exists {
+ atomic.AddUint64(&sc.currentSize, ^(entry.size - 1))
+ delete(sc.entry, key)
+ if ei, exists := sc.entryIndex[key]; exists && ei.index >= 0 && ei.index < sc.entryIndexHeap.Len() {
+ heap.Remove(sc.entryIndexHeap, ei.index)
+ delete(sc.entryIndex, key)
+ }
+ }
+}
+
func (sc *serviceCache) clean() {
ticker := time.NewTicker(sc.cleanupInterval)
defer ticker.Stop()
@@ -169,9 +188,7 @@ func (sc *serviceCache) clean() {
sc.mu.Lock()
for key, entry := range sc.entry {
if now-atomic.LoadUint64(&entry.lastAccess) > uint64(sc.idleTimeout.Nanoseconds()) {
- delete(sc.entry, key)
- heap.Remove(sc.entryIndexHeap, sc.entryIndex[key].index)
- delete(sc.entryIndex, key)
+ sc.removeEntry(key)
}
}
sc.mu.Unlock()
@@ -189,7 +206,7 @@ func (sc *serviceCache) Close() {
sc.entryIndexHeap = nil
}
-func (sc *serviceCache) Get(key EntryKey) any {
+func (sc *serviceCache) Get(key EntryKey) Sizable {
atomic.AddUint64(&sc.requests, 1)
sc.mu.RLock()
@@ -213,20 +230,29 @@ func (sc *serviceCache) Get(key EntryKey) any {
return nil
}
-func (sc *serviceCache) Put(key EntryKey, value any) {
+func (sc *serviceCache) Put(key EntryKey, value Sizable) {
sc.mu.Lock()
defer sc.mu.Unlock()
- for sc.size() > sc.maxCacheSize && sc.len() > 0 {
+ valueSize := value.Size()
+ entryOverhead := uint64(unsafe.Sizeof(entry{}) + unsafe.Sizeof(entryIndex{}) + unsafe.Sizeof(key))
+ totalSize := valueSize + entryOverhead
+
+ if existing, exists := sc.entry[key]; exists {
+ atomic.AddUint64(&sc.currentSize, ^(existing.size - 1))
+ sc.removeEntry(key)
+ }
+
+ for atomic.LoadUint64(&sc.currentSize)+totalSize > sc.maxCacheSize && sc.len() > 0 {
ei := heap.Pop(sc.entryIndexHeap).(*entryIndex)
- delete(sc.entry, ei.key)
- delete(sc.entryIndex, ei.key)
+ sc.removeEntry(ei.key)
}
now := uint64(time.Now().UnixNano())
e := &entry{
value: value,
lastAccess: now,
+ size: totalSize,
}
ei := &entryIndex{
key: key,
@@ -235,6 +261,7 @@ func (sc *serviceCache) Put(key EntryKey, value any) {
sc.entry[key] = e
sc.entryIndex[key] = ei
heap.Push(sc.entryIndexHeap, ei)
+ atomic.AddUint64(&sc.currentSize, totalSize)
}
func (sc *serviceCache) Requests() uint64 {
@@ -262,5 +289,40 @@ func (sc *serviceCache) Size() uint64 {
}
func (sc *serviceCache) size() uint64 {
- return uint64(unsafe.Sizeof(*sc))
+ return atomic.LoadUint64(&sc.currentSize)
+}
+
+var _ Cache = (*bypassCache)(nil)
+
+type bypassCache struct{}
+
+// NewBypassCache creates a no-op cache implementation.
+func NewBypassCache() Cache {
+ return &bypassCache{}
+}
+
+func (bc *bypassCache) Get(_ EntryKey) Sizable {
+ return nil
+}
+
+func (bc *bypassCache) Put(_ EntryKey, _ Sizable) {
+}
+
+func (bc *bypassCache) Close() {
+}
+
+func (bc *bypassCache) Requests() uint64 {
+ return 0
+}
+
+func (bc *bypassCache) Misses() uint64 {
+ return 0
+}
+
+func (bc *bypassCache) Entries() uint64 {
+ return 0
+}
+
+func (bc *bypassCache) Size() uint64 {
+ return 0
}
diff --git a/banyand/internal/storage/cache_test.go b/banyand/internal/storage/cache_test.go
index 0bf4eb3d..ad0149da 100644
--- a/banyand/internal/storage/cache_test.go
+++ b/banyand/internal/storage/cache_test.go
@@ -29,6 +29,19 @@ import (
"github.com/apache/skywalking-banyandb/pkg/run"
)
+// testSizableString is a test implementation of Sizable for string values.
+type testSizableString struct {
+ value string
+}
+
+func (s testSizableString) Size() uint64 {
+ return uint64(len(s.value)) + 16 // base struct overhead
+}
+
+func (s testSizableString) String() string {
+ return s.value
+}
+
func TestCachePutAndGet(t *testing.T) {
serviceCache := NewServiceCache()
@@ -39,7 +52,7 @@ func TestCachePutAndGet(t *testing.T) {
segmentID: segmentID(0),
shardID: common.ShardID(0),
}
- value := "test-value"
+ value := testSizableString{value: "test-value"}
serviceCache.Put(key, value)
assert.Equal(t, uint64(1), serviceCache.Entries())
@@ -67,7 +80,7 @@ func TestCacheEvict(t *testing.T) {
serviceCache.maxCacheSize = 50
var expectedKey EntryKey
- var expectedValue string
+ var expectedValue testSizableString
for i := 0; i < 10; i++ {
key := EntryKey{
group: "test-group",
@@ -76,7 +89,7 @@ func TestCacheEvict(t *testing.T) {
segmentID: segmentID(i),
shardID: common.ShardID(i),
}
- value := "test-value" + strconv.Itoa(i)
+ value := testSizableString{value: "test-value" + strconv.Itoa(i)}
serviceCache.Put(key, value)
if i == 9 {
expectedKey, expectedValue = key, value
@@ -104,7 +117,7 @@ func TestCacheClean(t *testing.T) {
segmentID: segmentID(0),
shardID: common.ShardID(0),
}
- value := "test-value"
+ value := testSizableString{value: "test-value"}
serviceCache.Put(key, value)
assert.Equal(t, uint64(1), serviceCache.Entries())
@@ -123,7 +136,7 @@ func TestCacheClose(t *testing.T) {
segmentID: segmentID(0),
shardID: common.ShardID(0),
}
- value := "test-value"
+ value := testSizableString{value: "test-value"}
serviceCache.Put(key, value)
assert.Equal(t, uint64(1), serviceCache.Entries())
@@ -140,7 +153,7 @@ func TestCacheConcurrency(t *testing.T) {
const numOperations = 100
var wg sync.WaitGroup
var expectedKey EntryKey
- var expectedValue string
+ var expectedValue testSizableString
wg.Add(numGoroutines * 2)
for i := 0; i < numGoroutines; i++ {
go func(id int) {
@@ -154,7 +167,7 @@ func TestCacheConcurrency(t *testing.T) {
segmentID: segmentID(num),
shardID: common.ShardID(num),
}
- value := "test-value" + strconv.Itoa(num)
+ value := testSizableString{value: "test-value" + strconv.Itoa(num)}
if id == 0 && j == 0 {
expectedKey, expectedValue = key, value
}
diff --git a/banyand/internal/storage/segment.go b/banyand/internal/storage/segment.go
index 1c4c1ce0..914eb7ed 100644
--- a/banyand/internal/storage/segment.go
+++ b/banyand/internal/storage/segment.go
@@ -53,12 +53,12 @@ type segmentCache struct {
segmentID segmentID
}
-func (sc *segmentCache) get(key EntryKey) any {
+func (sc *segmentCache) get(key EntryKey) Sizable {
key.segmentID = sc.segmentID
return sc.groupCache.get(key)
}
-func (sc *segmentCache) put(key EntryKey, value any) {
+func (sc *segmentCache) put(key EntryKey, value Sizable) {
key.segmentID = sc.segmentID
sc.groupCache.put(key, value)
}
diff --git a/banyand/internal/storage/shard.go b/banyand/internal/storage/shard.go
index 2f892280..6b97f302 100644
--- a/banyand/internal/storage/shard.go
+++ b/banyand/internal/storage/shard.go
@@ -52,12 +52,12 @@ func NewShardCache(group string, segmentID segmentID, shardID common.ShardID) Ca
}
}
-func (sc *shardCache) Get(key EntryKey) any {
+func (sc *shardCache) Get(key EntryKey) Sizable {
key.shardID = sc.shardID
return sc.segmentCache.get(key)
}
-func (sc *shardCache) Put(key EntryKey, value any) {
+func (sc *shardCache) Put(key EntryKey, value Sizable) {
key.shardID = sc.shardID
sc.segmentCache.put(key, value)
}
diff --git a/banyand/internal/storage/tsdb.go b/banyand/internal/storage/tsdb.go
index 4332be83..474bb255 100644
--- a/banyand/internal/storage/tsdb.go
+++ b/banyand/internal/storage/tsdb.go
@@ -81,12 +81,12 @@ type groupCache struct {
group string
}
-func (gc *groupCache) get(key EntryKey) any {
+func (gc *groupCache) get(key EntryKey) Sizable {
key.group = gc.group
return gc.Get(key)
}
-func (gc *groupCache) put(key EntryKey, value any) {
+func (gc *groupCache) put(key EntryKey, value Sizable) {
key.group = gc.group
gc.Put(key, value)
}
diff --git a/banyand/measure/block_metadata.go b/banyand/measure/block_metadata.go
index 0e3efe3c..b4796a63 100644
--- a/banyand/measure/block_metadata.go
+++ b/banyand/measure/block_metadata.go
@@ -21,6 +21,7 @@ import (
"errors"
"fmt"
"sort"
+ "unsafe"
"github.com/apache/skywalking-banyandb/api/common"
"github.com/apache/skywalking-banyandb/pkg/convert"
@@ -192,6 +193,31 @@ type blockMetadataArray struct {
arr []blockMetadata
}
+func (bma *blockMetadataArray) Size() uint64 {
+ size := uint64(unsafe.Sizeof(*bma))
+ size += uint64(len(bma.arr)) * uint64(unsafe.Sizeof(blockMetadata{}))
+ for i := range bma.arr {
+ bm := &bma.arr[i]
+ if bm.tagFamilies != nil {
+ size += uint64(len(bm.tagFamilies)) * uint64(unsafe.Sizeof(dataBlock{}))
+ for name := range bm.tagFamilies {
+ size += uint64(unsafe.Sizeof("")) + uint64(len(name))
+ }
+ }
+ if bm.tagProjection != nil {
+ size += uint64(len(bm.tagProjection)) * uint64(unsafe.Sizeof(model.TagProjection{}))
+ for j := range bm.tagProjection {
+ tp := &bm.tagProjection[j]
+ size += uint64(unsafe.Sizeof("")) + uint64(len(tp.Family)) // Family string header + data
+ for _, name := range tp.Names {
+ size += uint64(unsafe.Sizeof("")) + uint64(len(name)) // Each name string header + data
+ }
+ }
+ }
+ }
+ return size
+}
+
type timestampsMetadata struct {
dataBlock
min int64
diff --git a/banyand/measure/svc_data.go b/banyand/measure/svc_data.go
index fd2ec7aa..f91bae5d 100644
--- a/banyand/measure/svc_data.go
+++ b/banyand/measure/svc_data.go
@@ -156,7 +156,11 @@ func (s *dataSVC) PreRun(ctx context.Context) error {
if val == nil {
return errors.New("node id is empty")
}
- s.c = storage.NewServiceCacheWithConfig(s.cc)
+ if s.cc.MaxCacheSize == 0 {
+ s.c = storage.NewBypassCache()
+ } else {
+ s.c = storage.NewServiceCacheWithConfig(s.cc)
+ }
node := val.(common.Node)
s.schemaRepo = newDataSchemaRepo(s.dataPath, s, node.Labels)
diff --git a/banyand/measure/svc_standalone.go b/banyand/measure/svc_standalone.go
index b6504730..33834bc2 100644
--- a/banyand/measure/svc_standalone.go
+++ b/banyand/measure/svc_standalone.go
@@ -161,7 +161,11 @@ func (s *standalone) PreRun(ctx context.Context) error {
if val == nil {
return errors.New("node id is empty")
}
- s.c = storage.NewServiceCacheWithConfig(s.cc)
+ if s.cc.MaxCacheSize == 0 {
+ s.c = storage.NewBypassCache()
+ } else {
+ s.c = storage.NewServiceCacheWithConfig(s.cc)
+ }
node := val.(common.Node)
s.schemaRepo = newSchemaRepo(s.dataPath, s, node.Labels, node.NodeID)