-
Notifications
You must be signed in to change notification settings - Fork 40
Add further optimizations for Consensus Commit #2764
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
Conversation
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.
Pull Request Overview
This PR introduces additional optimizations for Consensus Commit by updating transaction APIs and tests to use a more flexible begin method with extra flags (readOnly and singleOperation). Key changes include:
- Refactoring internal transaction creation to use a new begin(txId, isolation, readOnly, singleOperation) signature.
- Updating numerous test classes to verify the new multi-argument begin calls.
- Enhancing snapshot read logic and CRUD handler behavior with single-operation mode optimizations.
Reviewed Changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| integration-test/src/main/java/com/scalar/db/transaction/singlecrudoperation/SingleCrudOperationTransactionIntegrationTestBase.java | Added a disabled test for multiple mutations in single CRUD operations. |
| integration-test/src/main/java/com/scalar/db/transaction/consensuscommit/ConsensusCommitSpecificIntegrationTestBase.java | Introduced various new tests using the updated begin method with additional parameters. |
| core/src/test/java/... | Updated tests to verify the new begin(anyString(), eq(Isolation.SNAPSHOT), …) signatures. |
| core/src/main/java/com/scalar/db/transaction/consensuscommit/TwoPhaseConsensusCommitManager.java | Refactored begin methods with extra flags and updated visibility of internal methods. |
| core/src/main/java/com/scalar/db/transaction/consensuscommit/Snapshot.java | Changed mergeResult method access from private to public. |
| core/src/main/java/com/scalar/db/transaction/consensuscommit/CrudHandler.java | Added a singleOperation flag and helper methods for conditional snapshot reading. |
| core/src/main/java/com/scalar/db/transaction/consensuscommit/ConsensusCommitManager.java | Updated transaction begin methods and introduced a beginSingleOperation helper. |
Comments suppressed due to low confidence (4)
core/src/main/java/com/scalar/db/transaction/consensuscommit/CrudHandler.java:57
- [nitpick] A 'singleOperation' flag has been introduced to control snapshot read behavior; consider adding or updating documentation to clearly explain its purpose and effects on CRUD operations.
private final boolean singleOperation;
core/src/main/java/com/scalar/db/transaction/consensuscommit/Snapshot.java:238
- [nitpick] Changing the visibility of mergeResult from private to public alters encapsulation; ensure that this exposure is intentional and is well documented for API consumers.
public Optional<TransactionResult> mergeResult(Key key, Optional<TransactionResult> result)
core/src/main/java/com/scalar/db/transaction/consensuscommit/ConsensusCommitManager.java:254
- The new beginSingleOperation method is used to optimize single-operation reads; verify that the corresponding tests fully cover scenarios where snapshot reads are bypassed as intended.
private DistributedTransaction beginSingleOperation(boolean readOnly) {
core/src/main/java/com/scalar/db/transaction/consensuscommit/TwoPhaseConsensusCommitManager.java:151
- Changing the visibility of this begin method to private may affect testability—ensure that tests relying on internal behavior are updated accordingly.
private TwoPhaseCommitTransaction begin(String txId, Isolation isolation) {
| // Whether the transaction is in single-operation mode or not. Single-operation mode refers to | ||
| // executing a CRUD operation directly through `DistributedTransactionManager` without explicitly | ||
| // beginning a transaction. | ||
| private final boolean singleOperation; |
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.
Introduced single-operation mode, a mode for transactions executed without explicitly beginning a transaction, such as DistributedTransactionManager.get(), DistributedTransactionManager.scan(), and DistributedTransactionManager.insert().
| private boolean isSnapshotReadRequired() { | ||
| // In single-operation mode, we don't need snapshot read | ||
| return !singleOperation; | ||
| } | ||
|
|
||
| private boolean isValidationOrSnapshotReadRequired() { | ||
| return snapshot.isValidationRequired() || isSnapshotReadRequired(); | ||
| } | ||
|
|
||
| private void putIntoGetSetInSnapshot(Get get, Optional<TransactionResult> result) { | ||
| // If neither validation nor snapshot read is required, we don't need to put the result into | ||
| // the get set | ||
| if (isValidationOrSnapshotReadRequired()) { | ||
| snapshot.putIntoGetSet(get, result); | ||
| } | ||
| } | ||
|
|
||
| private void putIntoScanSetInSnapshot( | ||
| Scan scan, LinkedHashMap<Snapshot.Key, TransactionResult> results) { | ||
| // If neither validation nor snapshot read is required, we don't need to put the results into | ||
| // the scan set | ||
| if (isValidationOrSnapshotReadRequired()) { | ||
| snapshot.putIntoScanSet(scan, results); | ||
| } | ||
| } | ||
|
|
||
| private void putIntoScannerSetInSnapshot( | ||
| Scan scan, LinkedHashMap<Snapshot.Key, TransactionResult> results) { | ||
| // if validation is not required, we don't need to put the results into the scanner set | ||
| if (snapshot.isValidationRequired()) { | ||
| snapshot.putIntoScannerSet(scan, results); | ||
| } | ||
| } | ||
|
|
||
| private void verifyNoOverlap(Scan scan, Map<Snapshot.Key, TransactionResult> results) { | ||
| // In read-only mode, we don't need to verify the overlap | ||
| if (!readOnly) { | ||
| // In either read-only mode or single-operation mode, we don't need to verify the overlap | ||
| if (!readOnly && !singleOperation) { | ||
| snapshot.verifyNoOverlap(scan, results); | ||
| } | ||
| } |
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.
Here is the main part of the optimizations.
The optimizations can be summarized as follows:
- If snapshot reads and serializable validation are not required, we can avoid adding records to the
getSetandscanSet, thereby reducing memory usage. This is because they are only used for snapshot reads and serializable validation. - If serializable validation is not required, we can skip adding ScannerInfo to the
scannerSet, since it is used solely for that purpose. - In addition to read-only mode, we can also skip verifying overlaps between the scan and the records in the
writeSetanddeleteSetin single-operation mode.
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.
If a single-crud operation is a self-join reading the same record more than twice, is it OK not to use snapshot read?
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.
The single-operation mode here means the mode for transactions that are executed without explicitly beginning a transaction, such as the following methods:
DistributedTransactionManger transactionManager = ...;
Optional<Result> result = transactionManager.get(Get.newBuilder()...);
List<Result> results = transactionManager.scan(Scan.newBuilder()...);
Scanner scanner = transactionManager.getScanner(Scan.newBuilder()...);
transactionManager.insert(Insert.newBuilder()...);
transactionManager.upsert(Upsert.newBuilder()...);
transactionManager.update(Update.newBuilder()...);
transactionManager.delete(Delete.newBuilder()...);
transactionManager.mutate(Arrays.asList(Delete.newBuilder()..., Update.newBuilder()...));In these cases, the transaction is committed immediately after the operation is done. So we don't need snapshot reads in such cases. Does this answer your question?
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.
@brfrn169 Probably, I am confused with single-crud operation transaction and single-operation mode.
If they are different things, it's pretty confusing so I think we should change the name.
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, okay. This isn’t related to single CRUD operation transactions — it’s specific to Consensus Commit.
Do you have any suggestions for what we should call transactions that are executed without explicitly beginning a transaction?
komamitsu
left a comment
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.
@brfrn169 We don't need to confirm the number of operations in a transaction is only 1 at the commit phase, if the single operation feature is enabled on the transaction?
| private final Scanner scanner; | ||
|
|
||
| private final LinkedHashMap<Snapshot.Key, TransactionResult> results = new LinkedHashMap<>(); | ||
| private final LinkedHashMap<Snapshot.Key, TransactionResult> results; |
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 should have @Nullable?
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.
Fixed in 61b2860. Thanks!
feeblefakie
left a comment
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.
Left some comments and questions. PTAL!
| .map( | ||
| r -> | ||
| new FilteredResult(r, originalProjections, metadata, isIncludeMetadataEnabled)); | ||
| } 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.
How about handling null/empty conjunction in the mergeResult method to unify the operations in the if/else clauses?
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.
Fixed in cefe83b. Thanks!
| private boolean isSnapshotReadRequired() { | ||
| // In single-operation mode, we don't need snapshot read | ||
| return !singleOperation; | ||
| } | ||
|
|
||
| private boolean isValidationOrSnapshotReadRequired() { | ||
| return snapshot.isValidationRequired() || isSnapshotReadRequired(); | ||
| } | ||
|
|
||
| private void putIntoGetSetInSnapshot(Get get, Optional<TransactionResult> result) { | ||
| // If neither validation nor snapshot read is required, we don't need to put the result into | ||
| // the get set | ||
| if (isValidationOrSnapshotReadRequired()) { | ||
| snapshot.putIntoGetSet(get, result); | ||
| } | ||
| } | ||
|
|
||
| private void putIntoScanSetInSnapshot( | ||
| Scan scan, LinkedHashMap<Snapshot.Key, TransactionResult> results) { | ||
| // If neither validation nor snapshot read is required, we don't need to put the results into | ||
| // the scan set | ||
| if (isValidationOrSnapshotReadRequired()) { | ||
| snapshot.putIntoScanSet(scan, results); | ||
| } | ||
| } | ||
|
|
||
| private void putIntoScannerSetInSnapshot( | ||
| Scan scan, LinkedHashMap<Snapshot.Key, TransactionResult> results) { | ||
| // if validation is not required, we don't need to put the results into the scanner set | ||
| if (snapshot.isValidationRequired()) { | ||
| snapshot.putIntoScannerSet(scan, results); | ||
| } | ||
| } | ||
|
|
||
| private void verifyNoOverlap(Scan scan, Map<Snapshot.Key, TransactionResult> results) { | ||
| // In read-only mode, we don't need to verify the overlap | ||
| if (!readOnly) { | ||
| // In either read-only mode or single-operation mode, we don't need to verify the overlap | ||
| if (!readOnly && !singleOperation) { | ||
| snapshot.verifyNoOverlap(scan, results); | ||
| } | ||
| } |
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.
If a single-crud operation is a self-join reading the same record more than twice, is it OK not to use snapshot read?
@komamitsu The single-operation flag is set to DistributedTransactionManger transactionManager = ...;
Optional<Result> result = transactionManager.get(Get.newBuilder()...);
List<Result> results = transactionManager.scan(Scan.newBuilder()...);
Scanner scanner = transactionManager.getScanner(Scan.newBuilder()...);
transactionManager.insert(Insert.newBuilder()...);
transactionManager.upsert(Upsert.newBuilder()...);
transactionManager.update(Update.newBuilder()...);
transactionManager.delete(Delete.newBuilder()...);
transactionManager.mutate(Arrays.asList(Delete.newBuilder()..., Update.newBuilder()...));In these cases, the number of operations in a transaction is always 1 at the commit phase (except for |
komamitsu
left a comment
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.
LGTM! 👍
| parallelExecutor, | ||
| readOnly); | ||
| readOnly, | ||
| singleOperation); |
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.
| singleOperation); | |
| oneOperation); |
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.
Fixed in 042d1e1. Thanks!
Torch3333
left a comment
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.
LGTM, thank you!
feeblefakie
left a comment
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.
LGTM! Thank you!
Description
This PR adds further optimizations for Consensus Commit.
It introduces a
single-operationmode, a mode for transactions executed without explicitly beginning a transaction, such asDistributedTransactionManager.get(),DistributedTransactionManager.scan(), andDistributedTransactionManager.insert().Details of the optimizations are provided in the inline comments.
Related issues and/or PRs
N/A
Changes made
Added some inline comments. Please take a look for the details.
Checklist
Additional notes (optional)
N/A
Release notes
N/A