-
Notifications
You must be signed in to change notification settings - Fork 46
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add block and actor caches #766
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
package modules | ||
|
||
import ( | ||
"context" | ||
"io" | ||
|
||
"github.com/filecoin-project/lotus/node/modules/dtypes" | ||
"github.com/filecoin-project/lotus/node/modules/helpers" | ||
"github.com/filecoin-project/lotus/node/repo" | ||
"go.uber.org/fx" | ||
"golang.org/x/xerrors" | ||
|
||
"github.com/filecoin-project/lily/lens/util" | ||
) | ||
|
||
func CacheConfig(blockstoreCacheSize uint, statestoreCacheSize uint) func(lc fx.Lifecycle, mctx helpers.MetricsCtx) (*util.CacheConfig, error) { | ||
return func(lc fx.Lifecycle, mctx helpers.MetricsCtx) (*util.CacheConfig, error) { | ||
return &util.CacheConfig{ | ||
BlockstoreCacheSize: blockstoreCacheSize, | ||
StatestoreCacheSize: statestoreCacheSize, | ||
}, nil | ||
} | ||
} | ||
|
||
func NewCachingUniversalBlockstore(lc fx.Lifecycle, mctx helpers.MetricsCtx, cc *util.CacheConfig, r repo.LockedRepo) (dtypes.UniversalBlockstore, error) { | ||
bs, err := r.Blockstore(helpers.LifecycleCtx(mctx, lc), repo.UniversalBlockstore) | ||
if err != nil { | ||
return nil, err | ||
} | ||
if c, ok := bs.(io.Closer); ok { | ||
lc.Append(fx.Hook{ | ||
OnStop: func(_ context.Context) error { | ||
return c.Close() | ||
}, | ||
}) | ||
} | ||
|
||
if cc.BlockstoreCacheSize == 0 { | ||
return bs, nil | ||
} | ||
|
||
log.Infof("creating caching blockstore with size=%d", cc.BlockstoreCacheSize) | ||
cbs, err := util.NewCachingBlockstore(bs, int(cc.BlockstoreCacheSize)) | ||
if err != nil { | ||
return nil, xerrors.Errorf("failed to create caching blockstore: %v", err) | ||
} | ||
|
||
return cbs, err | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,26 @@ | ||
package util | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"reflect" | ||
"sync/atomic" | ||
|
||
"github.com/filecoin-project/lotus/blockstore" | ||
"github.com/filecoin-project/specs-actors/actors/util/adt" | ||
lru "github.com/hashicorp/golang-lru" | ||
blocks "github.com/ipfs/go-block-format" | ||
"github.com/ipfs/go-cid" | ||
cbor "github.com/ipfs/go-ipld-cbor" | ||
cbg "github.com/whyrusleeping/cbor-gen" | ||
"golang.org/x/xerrors" | ||
) | ||
|
||
type CacheConfig struct { | ||
BlockstoreCacheSize uint | ||
StatestoreCacheSize uint | ||
} | ||
|
||
func NewCachingStore(backing blockstore.Blockstore) *ProxyingBlockstore { | ||
bs := blockstore.NewMemorySync() | ||
|
||
|
@@ -115,3 +127,186 @@ func (pb *ProxyingBlockstore) GetCount() int64 { | |
func (pb *ProxyingBlockstore) ResetMetrics() { | ||
atomic.StoreInt64(&pb.gets, 0) | ||
} | ||
|
||
var _ blockstore.Blockstore = (*CachingBlockstore)(nil) | ||
|
||
type CachingBlockstore struct { | ||
cache *lru.ARCCache | ||
blocks blockstore.Blockstore | ||
reads int64 // updated atomically | ||
hits int64 // updated atomically | ||
bytes int64 // updated atomically | ||
} | ||
|
||
func NewCachingBlockstore(blocks blockstore.Blockstore, cacheSize int) (*CachingBlockstore, error) { | ||
cache, err := lru.NewARC(cacheSize) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ditto comment wrt the error check here. |
||
if err != nil { | ||
return nil, xerrors.Errorf("new arc: %w", err) | ||
} | ||
|
||
return &CachingBlockstore{ | ||
cache: cache, | ||
blocks: blocks, | ||
}, nil | ||
} | ||
|
||
func (cs *CachingBlockstore) DeleteBlock(c cid.Cid) error { | ||
return cs.blocks.DeleteBlock(c) | ||
} | ||
|
||
func (cs *CachingBlockstore) GetSize(c cid.Cid) (int, error) { | ||
return cs.blocks.GetSize(c) | ||
} | ||
|
||
func (cs *CachingBlockstore) Put(blk blocks.Block) error { | ||
return cs.blocks.Put(blk) | ||
} | ||
|
||
func (cs *CachingBlockstore) PutMany(blks []blocks.Block) error { | ||
return cs.blocks.PutMany(blks) | ||
} | ||
|
||
func (cs *CachingBlockstore) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { | ||
return cs.blocks.AllKeysChan(ctx) | ||
} | ||
|
||
func (cs *CachingBlockstore) HashOnRead(enabled bool) { | ||
cs.blocks.HashOnRead(enabled) | ||
} | ||
|
||
func (cs *CachingBlockstore) DeleteMany(cids []cid.Cid) error { | ||
return cs.blocks.DeleteMany(cids) | ||
} | ||
|
||
func (cs *CachingBlockstore) Get(c cid.Cid) (blocks.Block, error) { | ||
reads := atomic.AddInt64(&cs.reads, 1) | ||
if reads%1000000 == 0 { | ||
hits := atomic.LoadInt64(&cs.hits) | ||
by := atomic.LoadInt64(&cs.bytes) | ||
log.Debugw("CachingBlockstore stats", "reads", reads, "cache_len", cs.cache.Len(), "hit_rate", float64(hits)/float64(reads), "bytes_read", by) | ||
} | ||
|
||
v, hit := cs.cache.Get(c) | ||
if hit { | ||
atomic.AddInt64(&cs.hits, 1) | ||
return v.(blocks.Block), nil | ||
} | ||
|
||
blk, err := cs.blocks.Get(c) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
atomic.AddInt64(&cs.bytes, int64(len(blk.RawData()))) | ||
cs.cache.Add(c, blk) | ||
return blk, err | ||
} | ||
|
||
func (cs *CachingBlockstore) View(c cid.Cid, callback func([]byte) error) error { | ||
reads := atomic.AddInt64(&cs.reads, 1) | ||
if reads%1000000 == 0 { | ||
hits := atomic.LoadInt64(&cs.hits) | ||
by := atomic.LoadInt64(&cs.bytes) | ||
log.Debugw("CachingBlockstore stats", "reads", reads, "cache_len", cs.cache.Len(), "hit_rate", float64(hits)/float64(reads), "bytes_read", by) | ||
} | ||
v, hit := cs.cache.Get(c) | ||
if hit { | ||
atomic.AddInt64(&cs.hits, 1) | ||
return callback(v.(blocks.Block).RawData()) | ||
} | ||
|
||
blk, err := cs.blocks.Get(c) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
atomic.AddInt64(&cs.bytes, int64(len(blk.RawData()))) | ||
cs.cache.Add(c, blk) | ||
return callback(blk.RawData()) | ||
} | ||
|
||
func (cs *CachingBlockstore) Has(c cid.Cid) (bool, error) { | ||
atomic.AddInt64(&cs.reads, 1) | ||
// Safe to query cache since blockstore never deletes | ||
if cs.cache.Contains(c) { | ||
return true, nil | ||
} | ||
|
||
return cs.blocks.Has(c) | ||
} | ||
|
||
var _ adt.Store = (*CachingStateStore)(nil) | ||
|
||
type CachingStateStore struct { | ||
cache *lru.ARCCache | ||
blocks blockstore.Blockstore | ||
store adt.Store | ||
reads int64 // updated atomically | ||
hits int64 // updated atomically | ||
} | ||
|
||
func NewCachingStateStore(blocks blockstore.Blockstore, cacheSize int) (*CachingStateStore, error) { | ||
cache, err := lru.NewARC(cacheSize) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. iirc this only errors when a negative value is passed, could panic here instead and simplify some of the calling code. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think it's good UX to panic on user input There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. True, and enforcement of positive integers could be done with |
||
if err != nil { | ||
return nil, xerrors.Errorf("new arc: %w", err) | ||
} | ||
|
||
store := adt.WrapStore(context.Background(), cbor.NewCborStore(blocks)) | ||
|
||
return &CachingStateStore{ | ||
cache: cache, | ||
store: store, | ||
blocks: blocks, | ||
}, nil | ||
} | ||
|
||
func (cas *CachingStateStore) Context() context.Context { | ||
return context.Background() | ||
} | ||
|
||
func (cas *CachingStateStore) Get(ctx context.Context, c cid.Cid, out interface{}) error { | ||
reads := atomic.AddInt64(&cas.reads, 1) | ||
if reads%1000000 == 0 { | ||
hits := atomic.LoadInt64(&cas.hits) | ||
log.Debugw("CachingStateStore stats", "reads", reads, "cache_len", cas.cache.Len(), "hit_rate", float64(hits)/float64(reads)) | ||
} | ||
|
||
cu, ok := out.(cbg.CBORUnmarshaler) | ||
if !ok { | ||
return xerrors.Errorf("out parameter does not implement CBORUnmarshaler") | ||
} | ||
|
||
v, hit := cas.cache.Get(c) | ||
if hit { | ||
atomic.AddInt64(&cas.hits, 1) | ||
|
||
o := reflect.ValueOf(out).Elem() | ||
if !o.CanSet() { | ||
return xerrors.Errorf("out parameter cannot be set") | ||
} | ||
|
||
if !v.(reflect.Value).Type().AssignableTo(o.Type()) { | ||
return xerrors.Errorf("out parameter cannot be assigned cached value") | ||
} | ||
|
||
o.Set(v.(reflect.Value)) | ||
return nil | ||
} | ||
|
||
blk, err := cas.blocks.Get(c) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if err := cu.UnmarshalCBOR(bytes.NewReader(blk.RawData())); err != nil { | ||
return cbor.NewSerializationError(err) | ||
} | ||
|
||
o := reflect.ValueOf(out).Elem() | ||
cas.cache.Add(c, o) | ||
return nil | ||
} | ||
|
||
func (cas *CachingStateStore) Put(ctx context.Context, v interface{}) (cid.Cid, error) { | ||
return cas.store.Put(ctx, v) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this be within the above else?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No. it creates a non-caching actor store as a fallback from 2 scenarios: there was an error returned from
util.NewCachingStateStore
orm.CacheConfig.StatestoreCacheSize<=0
(if no error returned from
util.NewCachingStateStore
then we have already returned from the function)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah whoops, I missed the return in the inner-most else.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Refactored to make clearer what the flow is here