5
5
"errors"
6
6
"fmt"
7
7
"sync/atomic"
8
+ "time"
8
9
9
10
lru "github.com/hashicorp/golang-lru"
10
11
"github.com/ipfs/go-datastore"
@@ -31,6 +32,8 @@ type Store[H header.Header[H]] struct {
31
32
ds datastore.Batching
32
33
// adaptive replacement cache of headers
33
34
cache * lru.ARCCache
35
+ // metrics collection instance
36
+ metrics * metrics
34
37
35
38
// header heights management
36
39
//
@@ -102,15 +105,24 @@ func newStore[H header.Header[H]](ds datastore.Batching, opts ...Option) (*Store
102
105
return nil , fmt .Errorf ("failed to create height indexer: %w" , err )
103
106
}
104
107
108
+ var metrics * metrics
109
+ if params .metrics {
110
+ metrics , err = newMetrics ()
111
+ if err != nil {
112
+ return nil , err
113
+ }
114
+ }
115
+
105
116
return & Store [H ]{
106
- Params : params ,
107
117
ds : wrappedStore ,
118
+ cache : cache ,
119
+ metrics : metrics ,
120
+ heightIndex : index ,
108
121
heightSub : newHeightSub [H ](),
109
122
writes : make (chan []H , 16 ),
110
123
writesDn : make (chan struct {}),
111
- cache : cache ,
112
- heightIndex : index ,
113
124
pending : newBatch [H ](params .WriteBatchSize ),
125
+ Params : params ,
114
126
}, nil
115
127
}
116
128
@@ -141,17 +153,22 @@ func (s *Store[H]) Stop(ctx context.Context) error {
141
153
default :
142
154
}
143
155
// signal to prevent further writes to Store
144
- s .writes <- nil
145
156
select {
146
- case <- s .writesDn : // wait till it is done writing
157
+ case s .writes <- nil :
158
+ case <- ctx .Done ():
159
+ return ctx .Err ()
160
+ }
161
+ // wait till it is done writing
162
+ select {
163
+ case <- s .writesDn :
147
164
case <- ctx .Done ():
148
165
return ctx .Err ()
149
166
}
150
167
151
168
// cleanup caches
152
169
s .cache .Purge ()
153
170
s .heightIndex .cache .Purge ()
154
- return nil
171
+ return s . metrics . Close ()
155
172
}
156
173
157
174
func (s * Store [H ]) Height () uint64 {
@@ -172,7 +189,7 @@ func (s *Store[H]) Head(ctx context.Context, _ ...header.HeadOption[H]) (H, erro
172
189
case errors .Is (err , datastore .ErrNotFound ), errors .Is (err , header .ErrNotFound ):
173
190
return zero , header .ErrNoHead
174
191
case err == nil :
175
- s .heightSub .SetHeight (uint64 ( head .Height () ))
192
+ s .heightSub .SetHeight (head .Height ())
176
193
log .Infow ("loaded head" , "height" , head .Height (), "hash" , head .Hash ())
177
194
return head , nil
178
195
}
@@ -188,12 +205,8 @@ func (s *Store[H]) Get(ctx context.Context, hash header.Hash) (H, error) {
188
205
return h , nil
189
206
}
190
207
191
- b , err := s .ds . Get (ctx , datastore . NewKey ( hash . String ()) )
208
+ b , err := s .get (ctx , hash )
192
209
if err != nil {
193
- if errors .Is (err , datastore .ErrNotFound ) {
194
- return zero , header .ErrNotFound
195
- }
196
-
197
210
return zero , err
198
211
}
199
212
@@ -356,15 +369,27 @@ func (s *Store[H]) Append(ctx context.Context, headers ...H) error {
356
369
verified , head = append (verified , h ), h
357
370
}
358
371
372
+ onWrite := func () {
373
+ newHead := verified [len (verified )- 1 ]
374
+ s .writeHead .Store (& newHead )
375
+ log .Infow ("new head" , "height" , newHead .Height (), "hash" , newHead .Hash ())
376
+ s .metrics .newHead (newHead .Height ())
377
+ }
378
+
359
379
// queue headers to be written on disk
360
380
select {
361
381
case s .writes <- verified :
362
- ln := len (verified )
363
- s .writeHead .Store (& verified [ln - 1 ])
364
- wh := * s .writeHead .Load ()
365
- log .Infow ("new head" , "height" , wh .Height (), "hash" , wh .Hash ())
366
382
// we return an error here after writing,
367
383
// as there might be an invalid header in between of a given range
384
+ onWrite ()
385
+ return err
386
+ default :
387
+ s .metrics .writesQueueBlocked (ctx )
388
+ }
389
+ // if the writes queue is full, we block until it is not
390
+ select {
391
+ case s .writes <- verified :
392
+ onWrite ()
368
393
return err
369
394
case <- s .writesDn :
370
395
return errStoppedStore
@@ -393,13 +418,17 @@ func (s *Store[H]) flushLoop() {
393
418
continue
394
419
}
395
420
396
- err := s .flush (ctx , s .pending .GetAll ()... )
421
+ startTime := time .Now ()
422
+ toFlush := s .pending .GetAll ()
423
+ err := s .flush (ctx , toFlush ... )
397
424
if err != nil {
425
+ from , to := toFlush [0 ].Height (), toFlush [len (toFlush )- 1 ].Height ()
398
426
// TODO(@Wondertan): Should this be a fatal error case with os.Exit?
399
- from , to := uint64 (headers [0 ].Height ()), uint64 (headers [len (headers )- 1 ].Height ())
400
427
log .Errorw ("writing header batch" , "from" , from , "to" , to )
428
+ s .metrics .flush (ctx , time .Since (startTime ), s .pending .Len (), true )
401
429
continue
402
430
}
431
+ s .metrics .flush (ctx , time .Since (startTime ), s .pending .Len (), false )
403
432
// reset pending
404
433
s .pending .Reset ()
405
434
@@ -472,3 +501,18 @@ func (s *Store[H]) readHead(ctx context.Context) (H, error) {
472
501
473
502
return s .Get (ctx , head )
474
503
}
504
+
505
+ func (s * Store [H ]) get (ctx context.Context , hash header.Hash ) ([]byte , error ) {
506
+ startTime := time .Now ()
507
+ data , err := s .ds .Get (ctx , datastore .NewKey (hash .String ()))
508
+ if err != nil {
509
+ s .metrics .readSingle (ctx , time .Since (startTime ), true )
510
+ if errors .Is (err , datastore .ErrNotFound ) {
511
+ return nil , header .ErrNotFound
512
+ }
513
+ return nil , err
514
+ }
515
+
516
+ s .metrics .readSingle (ctx , time .Since (startTime ), false )
517
+ return data , nil
518
+ }
0 commit comments