Skip to content

Commit 763e8c6

Browse files
authored
refactor: remove unnecessary usage of lru (#3204)
* fix(syncer): refetch latest da height instead of da height +1 * wip * fixes * fix changelog and underflow * fix nil * fix unit tests * arrange cl * updates * fixes * fix * remove lru from generic cache as slow cleanup already happens * simplify * Update CHANGELOG.md
1 parent 1c2309a commit 763e8c6

File tree

7 files changed

+231
-334
lines changed

7 files changed

+231
-334
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1717

1818
### Fixed
1919

20+
- Avoid evicting yet to be processed heights [#3204](https://github.com/evstack/ev-node/pull/3204)
2021
- Refetch latest da height instead of da height +1 when P2P is offline [#3201](https://github.com/evstack/ev-node/pull/3201)
2122
- Fix race on startup sync. [#3162](https://github.com/evstack/ev-node/pull/3162)
2223
- Strict raft state. [#3167](https://github.com/evstack/ev-node/pull/3167)

block/internal/cache/generic_cache.go

Lines changed: 79 additions & 141 deletions
Original file line numberDiff line numberDiff line change
@@ -8,23 +8,11 @@ import (
88
"sync"
99
"sync/atomic"
1010

11-
lru "github.com/hashicorp/golang-lru/v2"
1211
ds "github.com/ipfs/go-datastore"
1312

1413
"github.com/evstack/ev-node/pkg/store"
1514
)
1615

17-
const (
18-
// DefaultItemsCacheSize is the default size for items cache.
19-
DefaultItemsCacheSize = 200_000
20-
21-
// DefaultHashesCacheSize is the default size for hash tracking.
22-
DefaultHashesCacheSize = 200_000
23-
24-
// DefaultDAIncludedCacheSize is the default size for DA inclusion tracking.
25-
DefaultDAIncludedCacheSize = 200_000
26-
)
27-
2816
// snapshotEntry is one record in the persisted snapshot.
2917
// Encoded as 16 bytes: [blockHeight uint64 LE][daHeight uint64 LE].
3018
type snapshotEntry struct {
@@ -34,142 +22,98 @@ type snapshotEntry struct {
3422

3523
const snapshotEntrySize = 16 // bytes per snapshotEntry
3624

37-
// Cache tracks seen blocks and DA inclusion status using bounded LRU caches.
38-
type Cache[T any] struct {
39-
// itemsByHeight stores items keyed by uint64 height.
40-
// Mutex needed for atomic get-and-remove in getNextItem.
41-
itemsByHeight *lru.Cache[uint64, *T]
42-
itemsByHeightMu sync.Mutex
25+
// Cache tracks seen blocks and DA inclusion status.
26+
type Cache struct {
27+
mu sync.Mutex
4328

44-
// hashes tracks whether a given hash has been seen
45-
hashes *lru.Cache[string, bool]
29+
hashes map[string]bool
30+
daIncluded map[string]uint64
31+
hashByHeight map[uint64]string
32+
maxDAHeight *atomic.Uint64
4633

47-
// daIncluded maps hash → daHeight. Hash may be a real content hash or a
48-
// height placeholder (see HeightPlaceholderKey) immediately after restore.
49-
daIncluded *lru.Cache[string, uint64]
50-
51-
// hashByHeight maps blockHeight → hash, used for pruning and height-based
52-
// lookups. Protected by hashByHeightMu only in deleteAllForHeight where a
53-
// read-then-remove must be atomic.
54-
hashByHeight *lru.Cache[uint64, string]
55-
hashByHeightMu sync.Mutex
56-
57-
// maxDAHeight tracks the maximum DA height seen
58-
maxDAHeight *atomic.Uint64
59-
60-
store store.Store // nil = ephemeral, no persistence
61-
// storeKeyPrefix is the prefix used for store keys
34+
store store.Store
6235
storeKeyPrefix string
6336
}
6437

65-
func (c *Cache[T]) snapshotKey() string {
38+
func (c *Cache) snapshotKey() string {
6639
return c.storeKeyPrefix + "__snap"
6740
}
6841

6942
// NewCache creates a Cache. When store and keyPrefix are set, mutations
7043
// persist a snapshot so RestoreFromStore can recover in-flight state.
71-
func NewCache[T any](s store.Store, keyPrefix string) *Cache[T] {
72-
// LRU cache creation only fails if size <= 0, which won't happen with our defaults
73-
itemsCache, _ := lru.New[uint64, *T](DefaultItemsCacheSize)
74-
hashesCache, _ := lru.New[string, bool](DefaultHashesCacheSize)
75-
daIncludedCache, _ := lru.New[string, uint64](DefaultDAIncludedCacheSize)
76-
hashByHeightCache, _ := lru.New[uint64, string](DefaultHashesCacheSize)
77-
78-
return &Cache[T]{
79-
itemsByHeight: itemsCache,
80-
hashes: hashesCache,
81-
daIncluded: daIncludedCache,
82-
hashByHeight: hashByHeightCache,
44+
func NewCache(s store.Store, keyPrefix string) *Cache {
45+
return &Cache{
46+
hashes: make(map[string]bool),
47+
daIncluded: make(map[string]uint64),
48+
hashByHeight: make(map[uint64]string),
8349
maxDAHeight: &atomic.Uint64{},
8450
store: s,
8551
storeKeyPrefix: keyPrefix,
8652
}
8753
}
8854

89-
// getItem returns an item from the cache by height.
90-
func (c *Cache[T]) getItem(height uint64) *T {
91-
item, ok := c.itemsByHeight.Get(height)
92-
if !ok {
93-
return nil
94-
}
95-
return item
96-
}
97-
98-
// setItem sets an item in the cache by height.
99-
func (c *Cache[T]) setItem(height uint64, item *T) {
100-
c.itemsByHeight.Add(height, item)
101-
}
102-
103-
// getNextItem returns and removes the item at height, or nil if absent.
104-
func (c *Cache[T]) getNextItem(height uint64) *T {
105-
c.itemsByHeightMu.Lock()
106-
defer c.itemsByHeightMu.Unlock()
107-
108-
item, ok := c.itemsByHeight.Get(height)
109-
if !ok {
110-
return nil
111-
}
112-
c.itemsByHeight.Remove(height)
113-
return item
114-
}
115-
116-
// itemCount returns the number of items currently stored by height.
117-
func (c *Cache[T]) itemCount() int {
118-
return c.itemsByHeight.Len()
55+
func (c *Cache) isSeen(hash string) bool {
56+
c.mu.Lock()
57+
defer c.mu.Unlock()
58+
return c.hashes[hash]
11959
}
12060

121-
// isSeen returns true if the hash has been seen.
122-
func (c *Cache[T]) isSeen(hash string) bool {
123-
seen, ok := c.hashes.Get(hash)
124-
return ok && seen
61+
func (c *Cache) setSeen(hash string, height uint64) {
62+
c.mu.Lock()
63+
defer c.mu.Unlock()
64+
c.hashes[hash] = true
65+
c.hashByHeight[height] = hash
12566
}
12667

127-
// setSeen sets the hash as seen and tracks its height for pruning.
128-
func (c *Cache[T]) setSeen(hash string, height uint64) {
129-
c.hashes.Add(hash, true)
130-
c.hashByHeight.Add(height, hash)
68+
func (c *Cache) removeSeen(hash string) {
69+
c.mu.Lock()
70+
defer c.mu.Unlock()
71+
delete(c.hashes, hash)
13172
}
13273

133-
// getDAIncluded returns the DA height if the hash has been DA-included.
134-
func (c *Cache[T]) getDAIncluded(daCommitmentHash string) (uint64, bool) {
135-
return c.daIncluded.Get(daCommitmentHash)
74+
func (c *Cache) getDAIncluded(hash string) (uint64, bool) {
75+
c.mu.Lock()
76+
defer c.mu.Unlock()
77+
v, ok := c.daIncluded[hash]
78+
return v, ok
13679
}
13780

138-
// getDAIncludedByHeight resolves DA height via the height→hash index.
139-
// Works for both real hashes (steady state) and snapshot placeholders
140-
// (post-restart, before the DA retriever re-fires the real hash).
141-
func (c *Cache[T]) getDAIncludedByHeight(blockHeight uint64) (uint64, bool) {
142-
hash, ok := c.hashByHeight.Get(blockHeight)
81+
func (c *Cache) getDAIncludedByHeight(blockHeight uint64) (uint64, bool) {
82+
c.mu.Lock()
83+
defer c.mu.Unlock()
84+
hash, ok := c.hashByHeight[blockHeight]
14385
if !ok {
14486
return 0, false
14587
}
146-
return c.getDAIncluded(hash)
88+
v, exists := c.daIncluded[hash]
89+
return v, exists
14790
}
14891

14992
// setDAIncluded records DA inclusion in memory.
15093
// If a previous entry already exists at blockHeight (e.g. a placeholder from
15194
// RestoreFromStore), it is evicted from daIncluded to avoid orphan leaks.
152-
func (c *Cache[T]) setDAIncluded(hash string, daHeight uint64, blockHeight uint64) {
153-
if prev, ok := c.hashByHeight.Get(blockHeight); ok && prev != hash {
154-
c.daIncluded.Remove(prev)
95+
func (c *Cache) setDAIncluded(hash string, daHeight uint64, blockHeight uint64) {
96+
c.mu.Lock()
97+
defer c.mu.Unlock()
98+
if prev, ok := c.hashByHeight[blockHeight]; ok && prev != hash {
99+
delete(c.daIncluded, prev)
155100
}
156-
c.daIncluded.Add(hash, daHeight)
157-
c.hashByHeight.Add(blockHeight, hash)
101+
c.daIncluded[hash] = daHeight
102+
c.hashByHeight[blockHeight] = hash
158103
c.setMaxDAHeight(daHeight)
159104
}
160105

161-
// removeDAIncluded removes the DA-included status of the hash from the cache.
162-
func (c *Cache[T]) removeDAIncluded(hash string) {
163-
c.daIncluded.Remove(hash)
106+
func (c *Cache) removeDAIncluded(hash string) {
107+
c.mu.Lock()
108+
defer c.mu.Unlock()
109+
delete(c.daIncluded, hash)
164110
}
165111

166-
// daHeight returns the maximum DA height from all DA-included items.
167-
func (c *Cache[T]) daHeight() uint64 {
112+
func (c *Cache) daHeight() uint64 {
168113
return c.maxDAHeight.Load()
169114
}
170115

171-
// setMaxDAHeight sets the maximum DA height if the provided value is greater.
172-
func (c *Cache[T]) setMaxDAHeight(daHeight uint64) {
116+
func (c *Cache) setMaxDAHeight(daHeight uint64) {
173117
for range 1_000 {
174118
current := c.maxDAHeight.Load()
175119
if daHeight <= current {
@@ -181,49 +125,41 @@ func (c *Cache[T]) setMaxDAHeight(daHeight uint64) {
181125
}
182126
}
183127

184-
// removeSeen removes a hash from the seen cache.
185-
func (c *Cache[T]) removeSeen(hash string) {
186-
c.hashes.Remove(hash)
187-
}
188-
189-
// deleteAllForHeight removes all items and their associated data from the
190-
// cache at the given height.
191-
func (c *Cache[T]) deleteAllForHeight(height uint64) {
192-
c.itemsByHeight.Remove(height)
193-
194-
c.hashByHeightMu.Lock()
195-
hash, ok := c.hashByHeight.Get(height)
196-
if ok {
197-
c.hashByHeight.Remove(height)
128+
func (c *Cache) deleteAllForHeight(height uint64) {
129+
c.mu.Lock()
130+
defer c.mu.Unlock()
131+
hash, ok := c.hashByHeight[height]
132+
if !ok {
133+
return
198134
}
199-
c.hashByHeightMu.Unlock()
135+
delete(c.hashByHeight, height)
136+
delete(c.hashes, hash)
137+
delete(c.daIncluded, hash)
138+
}
200139

201-
if ok {
202-
c.hashes.Remove(hash)
203-
c.daIncluded.Remove(hash)
204-
}
140+
func (c *Cache) daIncludedLen() int {
141+
c.mu.Lock()
142+
defer c.mu.Unlock()
143+
return len(c.daIncluded)
205144
}
206145

207146
// persistSnapshot writes all current in-flight [blockHeight, daHeight] pairs to the store under a single key.
208147
// Only called explicitly via SaveToStore. NEVER CALL IT ON HOT-PATH TO AVOID BAGER WRITE AMPLIFICATION.
209-
func (c *Cache[T]) persistSnapshot(ctx context.Context) error {
148+
func (c *Cache) persistSnapshot(ctx context.Context) error {
210149
if c.store == nil || c.storeKeyPrefix == "" {
211150
return nil
212151
}
213152

214-
heights := c.hashByHeight.Keys()
215-
entries := make([]snapshotEntry, 0, len(heights))
216-
for _, h := range heights {
217-
hash, ok := c.hashByHeight.Peek(h)
218-
if !ok {
219-
continue
220-
}
221-
daH, ok := c.daIncluded.Peek(hash)
153+
c.mu.Lock()
154+
entries := make([]snapshotEntry, 0, len(c.hashByHeight))
155+
for h, hash := range c.hashByHeight {
156+
daH, ok := c.daIncluded[hash]
222157
if !ok {
223158
continue
224159
}
225160
entries = append(entries, snapshotEntry{blockHeight: h, daHeight: daH})
226161
}
162+
c.mu.Unlock()
227163

228164
return c.store.SetMetadata(ctx, c.snapshotKey(), encodeSnapshot(entries))
229165
}
@@ -257,8 +193,7 @@ func decodeSnapshot(buf []byte) []snapshotEntry {
257193
// RestoreFromStore loads the in-flight snapshot with a single store read.
258194
// Each entry is installed as a height placeholder; real hashes replace them
259195
// once the DA retriever re-fires SetHeaderDAIncluded after startup.
260-
// Missing snapshot key is treated as a no-op (fresh node or pre-snapshot version).
261-
func (c *Cache[T]) RestoreFromStore(ctx context.Context) error {
196+
func (c *Cache) RestoreFromStore(ctx context.Context) error {
262197
if c.store == nil || c.storeKeyPrefix == "" {
263198
return nil
264199
}
@@ -271,10 +206,13 @@ func (c *Cache[T]) RestoreFromStore(ctx context.Context) error {
271206
return fmt.Errorf("reading cache snapshot from store: %w", err)
272207
}
273208

209+
c.mu.Lock()
210+
defer c.mu.Unlock()
211+
274212
for _, e := range decodeSnapshot(buf) {
275213
placeholder := HeightPlaceholderKey(c.storeKeyPrefix, e.blockHeight)
276-
c.daIncluded.Add(placeholder, e.daHeight)
277-
c.hashByHeight.Add(e.blockHeight, placeholder)
214+
c.daIncluded[placeholder] = e.daHeight
215+
c.hashByHeight[e.blockHeight] = placeholder
278216
c.setMaxDAHeight(e.daHeight)
279217
}
280218

@@ -297,7 +235,7 @@ func HeightPlaceholderKey(prefix string, height uint64) string {
297235
}
298236

299237
// SaveToStore flushes the current snapshot to the store.
300-
func (c *Cache[T]) SaveToStore(ctx context.Context) error {
238+
func (c *Cache) SaveToStore(ctx context.Context) error {
301239
if c.store == nil {
302240
return nil
303241
}
@@ -308,7 +246,7 @@ func (c *Cache[T]) SaveToStore(ctx context.Context) error {
308246
}
309247

310248
// ClearFromStore deletes the snapshot key from the store.
311-
func (c *Cache[T]) ClearFromStore(ctx context.Context) error {
249+
func (c *Cache) ClearFromStore(ctx context.Context) error {
312250
if c.store == nil {
313251
return nil
314252
}

0 commit comments

Comments
 (0)