@@ -3,6 +3,7 @@ package store
33import (
44 "context"
55 "sync"
6+ "time"
67
78 lru "github.com/hashicorp/golang-lru/v2"
89 "github.com/rs/zerolog"
@@ -18,6 +19,11 @@ const (
1819 DefaultBlockDataCacheSize = 200_000
1920
2021 asyncWriteBufferSize = 8192
22+
23+ // batchWindow is the time the write goroutine waits after receiving the first
24+ // op before flushing. This allows bursts of metadata writes (e.g. 3-4 per
25+ // height in the submitter) to be coalesced into a single Badger WriteBatch.
26+ batchWindow = 100 * time .Microsecond
2127)
2228
2329type asyncWriteOp struct {
@@ -113,13 +119,19 @@ func (cs *CachedStore) startWriteLoop() {
113119 defer close (cs .done )
114120 for op := range cs .writeCh {
115121 ops := []asyncWriteOp {op }
116- drain:
122+
123+ timer := time .NewTimer (batchWindow )
124+ collect:
117125 for {
118126 select {
119- case op := <- cs .writeCh :
127+ case op , ok := <- cs .writeCh :
128+ if ! ok {
129+ timer .Stop ()
130+ break collect
131+ }
120132 ops = append (ops , op )
121- default :
122- break drain
133+ case <- timer . C :
134+ break collect
123135 }
124136 }
125137
@@ -234,40 +246,33 @@ func (cs *CachedStore) PruneBlocks(ctx context.Context, height uint64) error {
234246}
235247
236248// SetMetadata queues an asynchronous metadata write. The write is persisted
237- // by the background goroutine. If the buffer is full or the store has been
238- // stopped, the write falls back to synchronous execution on the underlying store.
249+ // by the background goroutine via BatchMetadata . If the store has been stopped,
250+ // the write falls back to synchronous execution on the underlying store.
239251func (cs * CachedStore ) SetMetadata (ctx context.Context , key string , value []byte ) error {
240252 cs .stopMu .RLock ()
241253 if cs .stopped {
242254 cs .stopMu .RUnlock ()
243255 return cs .Store .SetMetadata (ctx , key , value )
244256 }
245- select {
246- case cs .writeCh <- asyncWriteOp {key : key , value : value }:
247- cs .stopMu .RUnlock ()
248- return nil
249- default :
250- cs .stopMu .RUnlock ()
251- return cs .Store .SetMetadata (ctx , key , value )
252- }
257+ cs .stopMu .RUnlock ()
258+
259+ valueCopy := append ([]byte (nil ), value ... )
260+ cs .writeCh <- asyncWriteOp {key : key , value : valueCopy }
261+ return nil
253262}
254263
255- // DeleteMetadata queues an asynchronous metadata delete. If the buffer is full
256- // or the store has been stopped, the delete falls back to synchronous execution.
264+ // DeleteMetadata queues an asynchronous metadata delete. If the store has been
265+ // stopped, the delete falls back to synchronous execution.
257266func (cs * CachedStore ) DeleteMetadata (ctx context.Context , key string ) error {
258267 cs .stopMu .RLock ()
259268 if cs .stopped {
260269 cs .stopMu .RUnlock ()
261270 return cs .Store .DeleteMetadata (ctx , key )
262271 }
263- select {
264- case cs .writeCh <- asyncWriteOp {key : key , isDelete : true }:
265- cs .stopMu .RUnlock ()
266- return nil
267- default :
268- cs .stopMu .RUnlock ()
269- return cs .Store .DeleteMetadata (ctx , key )
270- }
272+ cs .stopMu .RUnlock ()
273+
274+ cs .writeCh <- asyncWriteOp {key : key , isDelete : true }
275+ return nil
271276}
272277
273278// Close drains pending async writes, then closes the underlying store.
0 commit comments