-
-
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
implement mark and sweep garbage collection #1420
Conversation
not yet ready for merge, i forgot to take into account that after rebasing on dev0.4.0, pins are stored in the blockstore, so i have to make sure to mark those on GC. |
RFCR? |
@jbenet Yeah, I could use some CR. not quite done with things, but the basics are there. |
return nil | ||
} | ||
|
||
func GC(ctx context.Context, bs bstore.Blockstore, pn pin.Pinner) (<-chan key.Key, error) { |
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.
describe the algorithm in a comment here. it's important that for this sort of thing we have a record of what it should be conceptually. as often that may not match what it is.
|
||
// GCSet currently implemented in memory, in the future, may be bloom filter or | ||
// disk backed to conserve memory. | ||
gcs := NewGCSet() |
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.
now that we have prometheus, might be interesting to maybe mark some of our buffer datastructures to see if any of them are responsible for bad memory spikes.
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.
what would this look like in code?
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.
make a special buffer with a prometheus gauge or somethng?
my biggest comment here is that we must address what happens when things are pinned during GC. Currently, GC will defer "what keys to keep or delete" to both the pin state and the blockstore, neither of which mutex-es with GC. A pin or an add (which pins) during GC could be incorrect. ("lose user data" incorrect) Suggested approaches below. (easy) add a gc lockAdd a lock (similar to an RWLock) which mutexes between "GC" and "Adds+Pins". Meaning, it allows concurrent access of any add and pin operation, but blocms GC during them. And likewise blocks them during GC. This will preseve fast operation during most uses, but will peg the node a bit during GC. Similar to how allocation + GC happens in many memory GCs. I don't expect GC to be so common that it must be run so often that this lock becomes a problem. However, it may have weird effects when there is a node with automatic gc upon hitting a "gc threshold" which is very near to the total amount of data pinned. (hard) add add/pin logSimilar to the lock, but instead of blocking add+pins, it will instead generate a (persistent) log of operations that can be replayed to ensure the adds+pins can be properly applied after gc ends. This is much harder to write + test, but would make add/pins not block. |
if err != nil { | ||
return err | ||
} | ||
} |
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.
my impression is that without concurrency, this will sequentially do IO + mutate memory, losing lots of time in the context switches with the kernel. Perhaps one good hack might be to have 2+ concurrent goroutines calling AddDag? would need some mutex on the map, so that might actually be enough to kill the improvement... but it is IO + lots of syscalls that we might schedule better...
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.
I think we should worry about complicating things when we notice a perf hit because of this.
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.
sgtm! 👍
Yeah, my thoughts were to add a RWMutex to the blockstore where the only taker of the write portion of the lock is the GC routine. (the easy route you describe above). We can transition to the log later, it wont require a migration or anything so i'm fine taking the quick and easy route for now. |
} | ||
|
||
nd, err := lnk.GetNode(ctx, serv) | ||
err = <-FetchGraph(ctx, nd, serv) |
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.
this way of doing it spawns a goroutine per node and keeps them all alive until they all get in. maybe a concurrent approach with channels may fare better.
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.
yeah, i can make that better
2dfda14
to
0149e9e
Compare
@jbenet I improved fetchgraph to still use one goroutine per node fetched, but the goroutine exits as soon as that node is received, as opposed to waiting till all its children are fetched. This would be easier if you could select on an array of channels, i considered using reflection but decided pretty code > slightly more efficient code for now |
separating the locking code on the blockstore into a separate interface complicates things... the write_cache wrapper needs to now cast the blockstore its wrapping as the locking blockstore in order to provide the same interface |
I'm gonna YOLO-cast it:
|
@@ -8,7 +8,7 @@ import ( | |||
) | |||
|
|||
// WriteCached returns a blockstore that caches up to |size| unique writes (bs.Put). | |||
func WriteCached(bs Blockstore, size int) (Blockstore, error) { | |||
func WriteCached(bs Blockstore, size int) (*writecache, error) { |
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.
is this ok to return since *writecache
isn't exported?
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.
Yes, this is the way we should be doing interfaces in go. that way, in the calling code, i can do either:
var bs Blockstore
bs = WriteCached()
OR
var gcbs GCBlockstore
gcbs = WriteCached()
without any ugly casts or failure conditions.
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.
what i mean is getting into an annoying case where i need the real type of the thing but i don't have it -- maybe we should also export WriteCache
? shrug it's fine i guess
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.
i'm fine with exporting WriteCache
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.
👍
some of the tests in That one is expecting all the child objects of the welcome docs dir to show up in the recursive refs listing. |
we should also take another look at https://github.com/ipfs/go-ipfs/blob/master/test/sharness/t0080-repo.sh#L93 because we arent storing things in leveldb anymore... |
Yeah, that looks incorrect to me too.
agreed, does it pass now? |
|
@jbenet the commits from the pinning stuff keep leaking in since this is based on dev0.4.0 and i keep rebasing that branch, so these commits dont match up and it looks weird. I'll fix. |
7580503
to
9202d50
Compare
Alright, i separated our the blocks and merkledag changes to pr #1453, merge that first. Then this PR will be a single commit |
@@ -250,21 +250,11 @@ Defaults to "direct". | |||
return nil, u.ErrCast() | |||
} | |||
out := new(bytes.Buffer) | |||
if typeStr == "indirect" && count { |
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.
i believe this needs to be brought back. (with likely a mark and sweep implementation)
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, its okay that this is gone. you can still specify that you want to see indirect keys, but we arent going to bother with counting them anymore. Thats too much hassle and gains us nothing.
License: MIT Signed-off-by: Jeromy <[email protected]> dont GC blocks used by pinner License: MIT Signed-off-by: Jeromy <[email protected]> comment GC algo License: MIT Signed-off-by: Jeromy <[email protected]> add lock to blockstore to prevent GC from eating wanted blocks License: MIT Signed-off-by: Jeromy <[email protected]> improve FetchGraph License: MIT Signed-off-by: Jeromy <[email protected]> separate interfaces for blockstore and GCBlockstore License: MIT Signed-off-by: Jeromy <[email protected]> reintroduce indirect pinning, add enumerateChildren dag method License: MIT Signed-off-by: Jeromy <[email protected]>
9202d50
to
268239d
Compare
License: MIT Signed-off-by: Jeromy <[email protected]>
@@ -15,54 +16,40 @@ type KeyRemoved struct { | |||
} | |||
|
|||
func GarbageCollect(n *core.IpfsNode, ctx context.Context) error { |
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 flip the params here:GarbageCollect(ctx, n)
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've always had the core.*
functions take the node as their first argument. You want to change this everywhere?
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. even when there is a ctx involved? i guess that makes sense
License: MIT Signed-off-by: Jeromy <[email protected]>
License: MIT Signed-off-by: Jeromy <[email protected]>
1bb4530
to
b521f54
Compare
License: MIT Signed-off-by: Jeromy <[email protected]>
The tests look good. Just so many of those random failures... |
implement mark and sweep garbage collection
This PR implements mark and sweep garbage collection and completely removes the concept of indirect pinning.