-
Notifications
You must be signed in to change notification settings - Fork 268
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
perf: Parallelize saveNewNodes' DB writes with figuring out "what to write" #889
base: master
Are you sure you want to change the base?
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 |
---|---|---|
|
@@ -1028,53 +1028,57 @@ func (tree *MutableTree) balance(node *Node) (newSelf *Node, err error) { | |
// saveNewNodes save new created nodes by the changes of the working tree. | ||
// NOTE: This function clears leftNode/rigthNode recursively and | ||
// calls _hash() on the given node. | ||
// TODO: Come back and figure out how to better parallelize this code. | ||
func (tree *MutableTree) saveNewNodes(version int64) error { | ||
nonce := uint32(0) | ||
newNodes := make([]*Node, 0) | ||
var recursiveAssignKey func(*Node) ([]byte, error) | ||
recursiveAssignKey = func(node *Node) ([]byte, error) { | ||
|
||
nodeChan := make(chan *Node, 64) // Buffered channel to avoid blocking. | ||
doneChan := make(chan error, 1) // Channel to signal completion and return any errors. | ||
|
||
// Start a goroutine to save nodes. | ||
go func() { | ||
var err error | ||
for node := range nodeChan { | ||
if saveErr := tree.ndb.SaveNode(node); saveErr != nil { | ||
err = saveErr | ||
break | ||
} | ||
node.leftNode, node.rightNode = nil, nil // Detach children after saving. | ||
} | ||
doneChan <- err // Send any error encountered or nil if none. | ||
}() | ||
|
||
var recursiveAssignKey func(*Node) []byte | ||
recursiveAssignKey = func(node *Node) []byte { | ||
if node.nodeKey != nil { | ||
if node.nodeKey.nonce != 0 { | ||
return node.nodeKey.GetKey(), nil | ||
return node.nodeKey.GetKey() | ||
} | ||
return node.hash, nil | ||
return node.hash | ||
} | ||
nonce++ | ||
node.nodeKey = &NodeKey{ | ||
version: version, | ||
nonce: nonce, | ||
nonce: nonce, // Example nonce calculation; adjust as needed. | ||
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. just realized the original codebase is wrong, not a bug but just a performance issue. Just worried we can't assume this sorted manner after refactoring ... |
||
} | ||
|
||
var err error | ||
// the inner nodes should have two children. | ||
// Assign keys recursively to child nodes. (Two children are guaranteed) | ||
if node.subtreeHeight > 0 { | ||
node.leftNodeKey, err = recursiveAssignKey(node.leftNode) | ||
if err != nil { | ||
return nil, err | ||
} | ||
node.rightNodeKey, err = recursiveAssignKey(node.rightNode) | ||
if err != nil { | ||
return nil, err | ||
} | ||
node.leftNodeKey = recursiveAssignKey(node.leftNode) | ||
node.rightNodeKey = recursiveAssignKey(node.rightNode) | ||
} | ||
|
||
node._hash(version) | ||
newNodes = append(newNodes, node) | ||
node._hash(version) // Assuming this hashes the node. | ||
nodeChan <- node // Send node to be saved. | ||
|
||
return node.nodeKey.GetKey(), nil | ||
return node.nodeKey.GetKey() | ||
} | ||
|
||
if _, err := recursiveAssignKey(tree.root); err != nil { | ||
recursiveAssignKey(tree.root) | ||
close(nodeChan) // Close the channel on completion. | ||
if err := <-doneChan; err != nil { // Wait for the saving goroutine to finish. | ||
Comment on lines
+1031
to
+1078
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. The parallelization of the
Overall, while the parallelization effort is commendable for its potential to improve performance, careful consideration must be given to error handling, concurrency issues, and the correctness of the implementation. Consider reviewing the error handling strategy, ensuring the safety of detaching children, verifying the nonce calculation logic, assessing the performance and correctness of recursive key assignment, and ensuring robust channel and goroutine management to prevent leaks or panics. |
||
return err | ||
} | ||
|
||
for _, node := range newNodes { | ||
if err := tree.ndb.SaveNode(node); err != nil { | ||
return err | ||
} | ||
node.leftNode, node.rightNode = nil, nil | ||
} | ||
|
||
return nil | ||
} | ||
|
||
|
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.
There is some overhead with creating channels and goroutine, although likely not so big, it might be better to couple channel and goroutine creation with the lifecycle of
MutableTree
instead ofsaveNewNodes
.Since I don't know off hand how much overhead that actually is maybe it's fine as-is too.