-
-
Notifications
You must be signed in to change notification settings - Fork 3k
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 ARC caching and bloom filter for blockstorage #2885
Changes from 1 commit
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,175 @@ | ||
package blockstore | ||
|
||
import ( | ||
"github.com/ipfs/go-ipfs/blocks" | ||
key "github.com/ipfs/go-ipfs/blocks/key" | ||
lru "gx/ipfs/QmVYxfoJQiZijTgPNHCHgHELvQpbsJNTg6Crmc3dQkj3yy/golang-lru" | ||
bloom "gx/ipfs/QmWQ2SJisXwcCLsUXLwYCKSfyExXjFRW2WbBH5sqCUnwX5/bbloom" | ||
context "gx/ipfs/QmZy2y8t9zQH2a1b8q2ZSLKp17ATuJoCNxxyMFG5qFExpt/go-net/context" | ||
ds "gx/ipfs/QmfQzVugPq1w5shWRcLWSeiHF4a2meBX7yVD8Vw7GWJM9o/go-datastore" | ||
) | ||
|
||
// BloomCached returns Blockstore that caches Has requests using Bloom filter | ||
// Size is size of bloom filter in bytes | ||
func BloomCached(bs Blockstore, bloomSize, lruSize int) (*bloomcache, error) { | ||
bl, err := bloom.New(float64(bloomSize), float64(7)) | ||
if err != nil { | ||
return nil, err | ||
} | ||
arc, err := lru.NewARC(lruSize) | ||
if err != nil { | ||
return nil, err | ||
} | ||
bc := &bloomcache{blockstore: bs, bloom: bl, arc: arc} | ||
bc.Invalidate() | ||
go bc.Rebuild() | ||
|
||
return bc, nil | ||
} | ||
|
||
type bloomcache struct { | ||
bloom *bloom.Bloom | ||
active bool | ||
|
||
arc *lru.ARCCache | ||
// This chan is only used for testing to wait for bloom to enable | ||
rebuildChan chan struct{} | ||
blockstore Blockstore | ||
|
||
// Statistics | ||
hits uint64 | ||
misses uint64 | ||
} | ||
|
||
func (b *bloomcache) Invalidate() { | ||
b.rebuildChan = make(chan struct{}) | ||
b.active = false | ||
} | ||
|
||
func (b *bloomcache) BloomActive() bool { | ||
return b.active | ||
} | ||
|
||
func (b *bloomcache) Rebuild() { | ||
ctx := context.TODO() | ||
evt := log.EventBegin(ctx, "bloomcache.Rebuild") | ||
defer evt.Done() | ||
|
||
ch, err := b.blockstore.AllKeysChan(ctx) | ||
if err != nil { | ||
log.Errorf("AllKeysChan failed in bloomcache rebuild with: %v", err) | ||
return | ||
} | ||
for key := range ch { | ||
b.bloom.AddTS([]byte(key)) // Use binary key, the more compact the better | ||
} | ||
close(b.rebuildChan) | ||
b.active = true | ||
} | ||
|
||
func (b *bloomcache) DeleteBlock(k key.Key) error { | ||
if has, ok := b.hasCached(k); ok && !has { | ||
return ErrNotFound | ||
} | ||
|
||
b.arc.Remove(k) // Invalidate cache before deleting. | ||
err := b.blockstore.DeleteBlock(k) | ||
if err == nil { | ||
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. switch err {
case nil:
//
case ds.ErrNotFound, ErrNotFound:
//
default:
return err
} |
||
b.arc.Add(k, false) | ||
} else if err == ds.ErrNotFound || err == ErrNotFound { | ||
b.arc.Add(k, false) | ||
return ErrNotFound | ||
} | ||
return err | ||
} | ||
|
||
// if ok == false has is inconclusive | ||
// if ok == true then has respons to question: is it contained | ||
func (b *bloomcache) hasCached(k key.Key) (has bool, ok bool) { | ||
if k == "" { | ||
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. what? we always have the empty key? 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. It was there in the write_cache, I just mimicked it. 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. oh weird... i wonder what the reasoning there was |
||
return true, true | ||
} | ||
if b.active { | ||
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. this field needs to be locked around 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. Does it? I would prefer not to introduce locking here as it is quite slow when compared with speed of bloom. It would only introduce race if bloom was being deactivated (it isn't in yet, but that doesn't matter) and the pointer to I will leave a comment about that if it seems good to you. 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. rebuild never gets called outside of the constructor currently (which i'm not super keen on), but even the one call in the constructor will cause a race. What if methods are being called right as its being set to true? setting a boolean isnt guaranteed to be atomic 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. Yes, but when it is set from false to true then race is that |
||
blr := b.bloom.HasTS([]byte(k)) | ||
if blr == false { // not contained in bloom is only conclusive answer bloom gives | ||
return blr, true | ||
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. lets hardcode |
||
} | ||
} | ||
h, ok := b.arc.Get(k) | ||
if ok { | ||
return h.(bool), ok | ||
} else { | ||
return false, ok | ||
} | ||
} | ||
|
||
func (b *bloomcache) Has(k key.Key) (bool, error) { | ||
if has, ok := b.hasCached(k); ok { | ||
return has, nil | ||
} | ||
|
||
res, err := b.blockstore.Has(k) | ||
if err == nil { | ||
b.arc.Add(k, res) | ||
} | ||
return res, err | ||
} | ||
|
||
func (b *bloomcache) Get(k key.Key) (blocks.Block, error) { | ||
if has, ok := b.hasCached(k); ok && !has { | ||
return nil, ErrNotFound | ||
} | ||
|
||
bl, err := b.blockstore.Get(k) | ||
if bl == nil && err == ErrNotFound { | ||
b.arc.Add(k, false) | ||
} else if bl != nil { | ||
b.arc.Add(k, true) | ||
} | ||
return bl, err | ||
} | ||
|
||
func (b *bloomcache) Put(bl blocks.Block) error { | ||
if has, ok := b.hasCached(bl.Key()); ok && has { | ||
return nil | ||
} | ||
|
||
err := b.blockstore.Put(bl) | ||
if err == nil { | ||
b.bloom.AddTS([]byte(bl.Key())) | ||
b.arc.Add(bl.Key(), true) | ||
} | ||
return err | ||
} | ||
|
||
func (b *bloomcache) PutMany(bs []blocks.Block) error { | ||
var good []blocks.Block | ||
for _, block := range bs { | ||
if has, ok := b.hasCached(block.Key()); !ok || (ok && !has) { | ||
good = append(good, block) | ||
} | ||
} | ||
err := b.blockstore.PutMany(bs) | ||
if err == nil { | ||
for _, block := range bs { | ||
b.bloom.AddTS([]byte(block.Key())) | ||
} | ||
} | ||
return err | ||
} | ||
|
||
func (b *bloomcache) AllKeysChan(ctx context.Context) (<-chan key.Key, error) { | ||
return b.blockstore.AllKeysChan(ctx) | ||
} | ||
|
||
func (b *bloomcache) GCLock() Unlocker { | ||
return b.blockstore.(GCBlockstore).GCLock() | ||
} | ||
|
||
func (b *bloomcache) PinLock() Unlocker { | ||
return b.blockstore.(GCBlockstore).PinLock() | ||
} | ||
|
||
func (b *bloomcache) GCRequested() bool { | ||
return b.blockstore.(GCBlockstore).GCRequested() | ||
} |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -131,7 +131,7 @@ func setupNode(ctx context.Context, n *IpfsNode, cfg *BuildCfg) error { | |
|
||
var err error | ||
bs := bstore.NewBlockstore(n.Repo.Datastore()) | ||
n.Blockstore, err = bstore.WriteCached(bs, kSizeBlockstoreWriteCache) | ||
n.Blockstore, err = bstore.BloomCached(bs, 256*1024, kSizeBlockstoreWriteCache) | ||
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. lets make this a constant 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 am introducing 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. sure |
||
if err != nil { | ||
return err | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -177,6 +177,12 @@ | |
"hash": "Qmb1DA2A9LS2wR4FFweB4uEDomFsdmnw1VLawLE1yQzudj", | ||
"name": "base32", | ||
"version": "0.0.0" | ||
}, | ||
{ | ||
"author": "kubuxu", | ||
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. why couldnt we use the code in blocks/bloom ? 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 spent few hours tweaking this one around. Also original author was very smart about how he hashes and uses higher unused hashbits to expand lower bits for the multiple hash functions bloom filter requires. |
||
"hash": "QmWQ2SJisXwcCLsUXLwYCKSfyExXjFRW2WbBH5sqCUnwX5", | ||
"name": "bbloom", | ||
"version": "0.0.2" | ||
} | ||
], | ||
"gxVersion": "0.4.0", | ||
|
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.
we should try and get a context passed in, maybe from the constructor?
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.
Seems like a good idea.