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
wallet: batch erase procedures and improve 'EraseRecords' performance #29403
wallet: batch erase procedures and improve 'EraseRecords' performance #29403
Conversation
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers. Code CoverageFor detailed information about the code coverage, see the test coverage report. ReviewsSee the guideline for information on the review process.
If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update. ConflictsNo conflicts as of last run. |
da103ec
to
b7b5e5b
Compare
Updated per feedback. Thanks Marko. |
b7b5e5b
to
69c5bb5
Compare
🚧 At least one of the CI tasks failed. Make sure to run all tests locally, according to the Possibly this is due to a silent merge conflict (the changes in this pull request being Leave a comment here, if you need help tracking down a confusing failure. |
Green CI. Ready. |
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.
ACK 69c5bb5
69c5bb5
to
0fd7b0d
Compare
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.
Updated per feedback. Thanks achow!
0fd7b0d
to
784034f
Compare
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.
Updated per feedback. Thanks achow.
Small diff, only changed assert
for Assume
.
d6bf468
to
757d651
Compare
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.
Rebased after #26836 merge. Ready to go.
BOOST_CHECK(batch->TxnBegin()); | ||
BOOST_CHECK(batch->ErasePrefix(DataStream() << key)); | ||
BOOST_CHECK(batch->TxnCommit()); |
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.
In 5235257 "wallet: bdb batch 'ErasePrefix', do not create txn internally"
nit: Could use RunWithinTxn()
.
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.
In 5235257 "wallet: bdb batch 'ErasePrefix', do not create txn internally"
nit: Could use
RunWithinTxn()
.
I did not used it because it needs walletdb.h
dependency (RunWithinTxn
provides access to WalletBatch
). And this tests (db_tests.cpp
) run on a lower level, using DatabaseBatch
.
// Because this function erases records one by one, ensure that it is executed within a txn context. | ||
// Otherwise, consistency is at risk; it's possible that certain records are removed while others | ||
// remain due to an internal failure during the procedure. |
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.
In 5235257 "wallet: bdb batch 'ErasePrefix', do not create txn internally"
I think it would be relevant to mention that BDB returns errors on del()
if it is not in a transaction.
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 it would be relevant to mention that BDB returns errors on del() if it is not in a transaction.
Sure, we could mention that the read-write operation is currently disallowed by our current bdb configurations.
Still, JFYI, I fixed it locally by subdividing the read-write operations. However, since we currently ensure that all callers start a transaction, and this division adds an extra loop over the matching entries (see furszy@4e7b5c0), I didn't see it as worth pushing it.
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.
re: #29403 (comment)
In commit "wallet: bdb batch 'ErasePrefix', do not create txn internally" (96eb527)
The new comment "Additionally, read-write operations performed outside a db txn context are disallowed by current BDB configurations" seems vague to me and I don't think would be comprehensible without reading this review thread. Would suggest just using achow's comment or combining both comments with something like "The Dbc::del() cursor delete call below would fail without an active transaction.".
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.
re: #29403 (comment)
In commit "wallet: bdb batch 'ErasePrefix', do not create txn internally" (96eb527)
The new comment "Additionally, read-write operations performed outside a db txn context are disallowed by current BDB configurations" seems vague to me and I don't think would be comprehensible without reading this review thread. Would suggest just using achow's comment or combining both comments with something like "The Dbc::del() cursor delete call below would fail without an active transaction.".
I wrote it in this way to (try to) inform readers about current bdb config limitations in case anyone changes this code and doesn't understand why it fails. But, I agree that it is not immediately clear. Will change it for you line.
Hopefully no one else will change this again.
src/wallet/walletdb.cpp
Outdated
@@ -1337,6 +1336,12 @@ bool RunWithinTxn(WalletDatabase& database, const std::string& process_desc, con | |||
return true; | |||
} | |||
|
|||
bool RunWithinTxn(WalletDatabase& database, const std::string& process_desc, const std::function<bool(WalletBatch&)>& func) |
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.
In 757d651 "wallet: simplify EraseRecords by using 'ErasePrefix'"
This overload feels unnecessary given that there are only 2 places this function is really used.
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.
In 757d651 "wallet: simplify EraseRecords by using 'ErasePrefix'"
This overload feels unnecessary given that there are only 2 places this function is really used.
The idea is to use this function as a top-level wrapper for wallet functions and scope the other function for internal walletdb.cpp
usage mostly/only. This approach avoids creating WalletBatch
objects across the sources and makes use of the error-handling code in the WalletBatch
destructor.
Functions such as DescriptorScriptPubKeyMan::TopUp
, the two CWallet::SetupDescriptorScriptPubKeyMans
, and the final state of the batched migration process can utilize this overload in a follow-up.
src/wallet/walletdb.cpp
Outdated
@@ -1314,9 +1314,8 @@ DBErrors WalletBatch::ZapSelectTx(std::vector<uint256>& vTxHashIn, std::vector<u | |||
return DBErrors::LOAD_OK; | |||
} | |||
|
|||
bool RunWithinTxn(WalletDatabase& database, const std::string& process_desc, const std::function<bool(WalletBatch&)>& func) | |||
static bool RunWithinTxn(WalletBatch& batch, const std::string& process_desc, const std::function<bool(WalletBatch&)>& func) |
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.
In 757d651 "wallet: simplify EraseRecords by using 'ErasePrefix'"
This could be done in the commit introducing RunWithinTxn()
rather than changing it in a later commit.
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 could be done in the commit introducing
RunWithinTxn()
rather than changing it in a later commit.
This is merely an organizational discussion, but.. I'm not a big fan of introducing a function overload in a commit that does not utilizes it. Yet, if you have a strong opinion about it, I could move this to the first commit. np.
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.
Light code review ACK 757d651, nice simplification of EraseRecords
757d651
to
8330145
Compare
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.
Updated per feedback. Thanks both!
// Because this function erases records one by one, ensure that it is executed within a txn context. | ||
// Otherwise, consistency is at risk; it's possible that certain records are removed while others | ||
// remain due to an internal failure during the procedure. |
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 it would be relevant to mention that BDB returns errors on del() if it is not in a transaction.
Sure, we could mention that the read-write operation is currently disallowed by our current bdb configurations.
Still, JFYI, I fixed it locally by subdividing the read-write operations. However, since we currently ensure that all callers start a transaction, and this division adds an extra loop over the matching entries (see furszy@4e7b5c0), I didn't see it as worth pushing it.
BOOST_CHECK(batch->TxnBegin()); | ||
BOOST_CHECK(batch->ErasePrefix(DataStream() << key)); | ||
BOOST_CHECK(batch->TxnCommit()); |
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.
In 5235257 "wallet: bdb batch 'ErasePrefix', do not create txn internally"
nit: Could use
RunWithinTxn()
.
I did not used it because it needs walletdb.h
dependency (RunWithinTxn
provides access to WalletBatch
). And this tests (db_tests.cpp
) run on a lower level, using DatabaseBatch
.
src/wallet/walletdb.cpp
Outdated
@@ -1337,6 +1336,12 @@ bool RunWithinTxn(WalletDatabase& database, const std::string& process_desc, con | |||
return true; | |||
} | |||
|
|||
bool RunWithinTxn(WalletDatabase& database, const std::string& process_desc, const std::function<bool(WalletBatch&)>& func) |
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.
In 757d651 "wallet: simplify EraseRecords by using 'ErasePrefix'"
This overload feels unnecessary given that there are only 2 places this function is really used.
The idea is to use this function as a top-level wrapper for wallet functions and scope the other function for internal walletdb.cpp
usage mostly/only. This approach avoids creating WalletBatch
objects across the sources and makes use of the error-handling code in the WalletBatch
destructor.
Functions such as DescriptorScriptPubKeyMan::TopUp
, the two CWallet::SetupDescriptorScriptPubKeyMans
, and the final state of the batched migration process can utilize this overload in a follow-up.
src/wallet/walletdb.cpp
Outdated
@@ -1314,9 +1314,8 @@ DBErrors WalletBatch::ZapSelectTx(std::vector<uint256>& vTxHashIn, std::vector<u | |||
return DBErrors::LOAD_OK; | |||
} | |||
|
|||
bool RunWithinTxn(WalletDatabase& database, const std::string& process_desc, const std::function<bool(WalletBatch&)>& func) | |||
static bool RunWithinTxn(WalletBatch& batch, const std::string& process_desc, const std::function<bool(WalletBatch&)>& func) |
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 could be done in the commit introducing
RunWithinTxn()
rather than changing it in a later commit.
This is merely an organizational discussion, but.. I'm not a big fan of introducing a function overload in a commit that does not utilizes it. Yet, if you have a strong opinion about it, I could move this to the first commit. np.
ACK 8330145 |
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.
Code review ACK 8330145. Looks pretty good, but would suggest cleaning up some things
|
||
// Run procedure, errors are logged internally | ||
if (!func(batch)) { | ||
batch.TxnAbort(); |
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.
In commit "wallet: db, introduce 'RunWithinTxn()' helper function" (b6dcfca)
I think it's bad for debugging that this function inconsistently logs errors when an operation fails. For example it seems like there is no error if the new eraserecords function fails? It would be better to add LogPrint(BCLog::WALLETDB, "Error: %s failed\n", process_desc);
here.
I understand that in the future there may be cases where we may want to abort without logging errors but I think a better way to handle those cases would be to accept kernel::Interrupted
return values here and use the kernel::IsInterrupted()
helper. (We can move these from kernel
to util
without breaking compatibility.)
The comment above on line 1325 "errors are logged internally" is also not accurate and impossible to enforce with an anonymous callback, so would suggest dropping that and logging unconditionally here. (Without logging, the most the comment could say would be "errors need to be logged by func callback" or "errors should be logged by func callback")
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, using util::Interrupted
to skip the failure log in the future sounds great. Error logging added.
// Because this function erases records one by one, ensure that it is executed within a txn context. | ||
// Otherwise, consistency is at risk; it's possible that certain records are removed while others | ||
// remain due to an internal failure during the procedure. |
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.
re: #29403 (comment)
In commit "wallet: bdb batch 'ErasePrefix', do not create txn internally" (96eb527)
The new comment "Additionally, read-write operations performed outside a db txn context are disallowed by current BDB configurations" seems vague to me and I don't think would be comprehensible without reading this review thread. Would suggest just using achow's comment or combining both comments with something like "The Dbc::del() cursor delete call below would fail without an active transaction.".
8330145
to
2095ccd
Compare
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.
Updated per feedback. Thanks ryanofsky!. Small diff.
Per request, now RunWithinTxn
logs an error when the provided function requires to abort the db txn. As well as two comments have been improved for clarity.
'RunWithinTxn()' provides a way to execute db operations within a transactional context. It avoids writing repetitive boilerplate code for starting and committing the database transaction.
Transactions are intended to be started on upper layers rather than internally by the bdb batch object. This enables us to consolidate different write operations within a procedure in the same db txn, improving consistency due to the atomic property of the transaction, as well as its performance due to the reduction of disk write operations. Important Note: This approach also ensures that the BerkeleyBatch::ErasePrefix function behaves exactly as the SQLiteBatch::ErasePrefix function, which does not create a db txn internally. Furthermore, since the `BerkeleyBatch::ErasePrefix' implementation erases records one by one (by traversing the db), this change ensures that the function is always called within an active txn context. Without this measure, there's a potential risk to consistency; certain records may be removed while others could persist due to an internal failure during the procedure.
2095ccd
to
77331aa
Compare
reACK 77331aa |
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.
code review ACK 77331aa
If you end up retouching, I think it's better to keep all of the RunWithinTxn
changes in the first commit. Makes it easier to review and avoids introducing the function with one signature and then changing the signature in a later commit.
static bool RunWithinTxn(WalletBatch& batch, std::string_view process_desc, const std::function<bool(WalletBatch&)>& func) | ||
{ | ||
WalletBatch batch(database); |
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.
in "wallet: simplify EraseRecords by using 'ErasePrefix'" (77331aa):
nit: these changes should be in the first commit
Seeks to optimize and simplify
WalletBatch::EraseRecords
. Currently, this process opens a cursor to iterate over the entire database, searching for records that match the type prefixes, to then call theWalletBatch::Erase
function for each of the matching records.This PR rewrites this 40-line manual process into a single line; instead of performing all of those actions manually, we can simply utilize the
ErasePrefix()
functionality. The result is 06216b3.Moreover, it expands the test coverage for the
ErasePrefix
functionality and documents the db txn requirement forBerkeleyBatch::ErasePrefix
.