-
Notifications
You must be signed in to change notification settings - Fork 260
Expand file tree
/
Copy pathcached_store.go
More file actions
180 lines (144 loc) · 4.47 KB
/
cached_store.go
File metadata and controls
180 lines (144 loc) · 4.47 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
package store
import (
"context"
lru "github.com/hashicorp/golang-lru/v2"
"github.com/evstack/ev-node/types"
)
const (
// DefaultHeaderCacheSize is the default number of headers to cache in memory.
DefaultHeaderCacheSize = 200_000
// DefaultBlockDataCacheSize is the default number of block data entries to cache.
DefaultBlockDataCacheSize = 200_000
)
// CachedStore wraps a Store with LRU caching for frequently accessed data.
// The underlying LRU cache is thread-safe, so no additional synchronization is needed.
type CachedStore struct {
Store
headerCache *lru.Cache[uint64, *types.SignedHeader]
blockDataCache *lru.Cache[uint64, *blockDataEntry]
}
type blockDataEntry struct {
header *types.SignedHeader
data *types.Data
}
// CachedStoreOption configures a CachedStore.
type CachedStoreOption func(*CachedStore) error
// WithHeaderCacheSize sets the header cache size.
func WithHeaderCacheSize(size int) CachedStoreOption {
return func(cs *CachedStore) error {
cache, err := lru.New[uint64, *types.SignedHeader](size)
if err != nil {
return err
}
cs.headerCache = cache
return nil
}
}
// WithBlockDataCacheSize sets the block data cache size.
func WithBlockDataCacheSize(size int) CachedStoreOption {
return func(cs *CachedStore) error {
cache, err := lru.New[uint64, *blockDataEntry](size)
if err != nil {
return err
}
cs.blockDataCache = cache
return nil
}
}
// NewCachedStore creates a new CachedStore wrapping the given store.
func NewCachedStore(store Store, opts ...CachedStoreOption) (*CachedStore, error) {
headerCache, err := lru.New[uint64, *types.SignedHeader](DefaultHeaderCacheSize)
if err != nil {
return nil, err
}
blockDataCache, err := lru.New[uint64, *blockDataEntry](DefaultBlockDataCacheSize)
if err != nil {
return nil, err
}
cs := &CachedStore{
Store: store,
headerCache: headerCache,
blockDataCache: blockDataCache,
}
for _, opt := range opts {
if err := opt(cs); err != nil {
return nil, err
}
}
return cs, nil
}
// GetHeader returns the header at the given height, using the cache if available.
func (cs *CachedStore) GetHeader(ctx context.Context, height uint64) (*types.SignedHeader, error) {
// Try cache first
if header, ok := cs.headerCache.Get(height); ok {
return header, nil
}
// Cache miss, fetch from underlying store
header, err := cs.Store.GetHeader(ctx, height)
if err != nil {
return nil, err
}
header.MemoizeHash()
// Add to cache
cs.headerCache.Add(height, header)
return header, nil
}
// GetBlockData returns block header and data at given height, using cache if available.
func (cs *CachedStore) GetBlockData(ctx context.Context, height uint64) (*types.SignedHeader, *types.Data, error) {
// Try cache first
if entry, ok := cs.blockDataCache.Get(height); ok {
return entry.header, entry.data, nil
}
// Cache miss, fetch from underlying store
header, data, err := cs.Store.GetBlockData(ctx, height)
if err != nil {
return nil, nil, err
}
header.MemoizeHash()
// Add to cache
cs.blockDataCache.Add(height, &blockDataEntry{header: header, data: data})
// Also add header to header cache
cs.headerCache.Add(height, header)
return header, data, nil
}
// InvalidateRange removes headers in the given range from the cache.
func (cs *CachedStore) InvalidateRange(fromHeight, toHeight uint64) {
for h := fromHeight; h <= toHeight; h++ {
cs.headerCache.Remove(h)
cs.blockDataCache.Remove(h)
}
}
// ClearCache clears all cached entries.
func (cs *CachedStore) ClearCache() {
cs.headerCache.Purge()
cs.blockDataCache.Purge()
}
// Rollback wraps the underlying store's Rollback and invalidates affected cache entries.
func (cs *CachedStore) Rollback(ctx context.Context, height uint64, aggregator bool) error {
currentHeight, err := cs.Height(ctx)
if err != nil {
return err
}
// First do the rollback
if err := cs.Store.Rollback(ctx, height, aggregator); err != nil {
return err
}
// Then invalidate cache entries for rolled back heights
cs.InvalidateRange(height+1, currentHeight)
return nil
}
// PruneBlocks wraps the underlying store's PruneBlocks and invalidates caches
// up to the heigh that we purne
func (cs *CachedStore) PruneBlocks(ctx context.Context, height uint64) error {
if err := cs.Store.PruneBlocks(ctx, height); err != nil {
return err
}
// Invalidate cache for pruned heights
cs.InvalidateRange(1, height)
return nil
}
// Close closes the underlying store.
func (cs *CachedStore) Close() error {
cs.ClearCache()
return cs.Store.Close()
}