This repository has been archived by the owner on Nov 14, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 15
[PDS-158510] Improved Detection of Orphaned Sentinels #5231
Merged
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
2678585
part 1: handling and basic tests
jeremyk-91 558ff6f
stress test
jeremyk-91 8c10178
Add generated changelog entries
jeremyk-91 5634234
keyedstream
jeremyk-91 4f75924
spotlkess
jeremyk-91 6057503
Merge branch 'jkong/sentinels-eveywhere' of github.com:palantir/atlas…
jeremyk-91 df9f5a9
fixes
jeremyk-91 58812ed
apply
jeremyk-91 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,6 +16,7 @@ | |
package com.palantir.atlasdb.transaction.impl; | ||
|
||
import static org.assertj.core.api.Assertions.assertThat; | ||
import static org.assertj.core.api.Assertions.assertThatCode; | ||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; | ||
import static org.assertj.core.api.Assertions.assertThatThrownBy; | ||
import static org.assertj.core.api.Assertions.fail; | ||
|
@@ -134,6 +135,8 @@ | |
import java.util.concurrent.TimeUnit; | ||
import java.util.function.Supplier; | ||
import java.util.stream.Collectors; | ||
import java.util.stream.IntStream; | ||
import java.util.stream.StreamSupport; | ||
import org.apache.commons.lang3.mutable.MutableInt; | ||
import org.apache.commons.lang3.mutable.MutableLong; | ||
import org.apache.commons.lang3.tuple.Pair; | ||
|
@@ -150,6 +153,7 @@ | |
import org.junit.Test; | ||
import org.junit.runner.RunWith; | ||
import org.junit.runners.Parameterized; | ||
import org.mockito.ArgumentCaptor; | ||
|
||
@SuppressWarnings("checkstyle:all") | ||
@RunWith(Parameterized.class) | ||
|
@@ -290,6 +294,7 @@ public void cleanup() {} | |
TableReference.createFromFullyQualifiedName("default.table5"); | ||
|
||
private static final Cell TEST_CELL = Cell.create(PtBytes.toBytes("row1"), PtBytes.toBytes("column1")); | ||
private static final Cell TEST_CELL_2 = Cell.create(PtBytes.toBytes("row2"), PtBytes.toBytes("column2")); | ||
|
||
@Override | ||
@Before | ||
|
@@ -1438,6 +1443,192 @@ public void checkImmutableTsLockAfterReadsForConservativeIfFlagIsSet() { | |
timelockService.unlock(ImmutableSet.of(res.getLock())); | ||
} | ||
|
||
@Test | ||
public void getOrphanedSweepSentinelDoesNotThrow() { | ||
Transaction t1 = txManager.createNewTransaction(); | ||
writeSentinelToTestTable(TEST_CELL); | ||
assertThatCode(() -> t1.get(TABLE_SWEPT_CONSERVATIVE, ImmutableSet.of(TEST_CELL))) | ||
.doesNotThrowAnyException(); | ||
} | ||
|
||
@Test | ||
public void getSweepSentinelUnderCommittedWriteThrowsAndCanBeRetried() { | ||
Transaction t1 = txManager.createNewTransaction(); | ||
writeSentinelToTestTable(TEST_CELL); | ||
|
||
Transaction t2 = txManager.createNewTransaction(); | ||
t2.put(TABLE_SWEPT_CONSERVATIVE, ImmutableMap.of(TEST_CELL, PtBytes.toBytes("blablabla"))); | ||
t2.commit(); | ||
|
||
assertThatThrownBy(() -> t1.get(TABLE_SWEPT_CONSERVATIVE, ImmutableSet.of(TEST_CELL))) | ||
.isInstanceOf(TransactionFailedRetriableException.class) | ||
.hasMessageContaining("Tried to read a value that has been deleted."); | ||
|
||
Transaction retryTxn = txManager.createNewTransaction(); | ||
assertThatCode(() -> retryTxn.get(TABLE_SWEPT_CONSERVATIVE, ImmutableSet.of(TEST_CELL))) | ||
.doesNotThrowAnyException(); | ||
} | ||
|
||
@Test | ||
public void getSweepSentinelUnderLateUncommittedWriteDoesNotThrow() { | ||
Transaction t1 = txManager.createNewTransaction(); | ||
writeSentinelToTestTable(TEST_CELL); | ||
putUncommittedAtFreshTimestamp(TEST_CELL); | ||
|
||
assertThatCode(() -> t1.get(TABLE_SWEPT_CONSERVATIVE, ImmutableSet.of(TEST_CELL))) | ||
.doesNotThrowAnyException(); | ||
} | ||
|
||
@Test | ||
public void getSweepSentinelUnderEarlyUncommittedWriteDoesNotThrow() { | ||
writeSentinelToTestTable(TEST_CELL); | ||
putUncommittedAtFreshTimestamp(TEST_CELL); | ||
Transaction t1 = txManager.createNewTransaction(); | ||
|
||
assertThatCode(() -> t1.get(TABLE_SWEPT_CONSERVATIVE, ImmutableSet.of(TEST_CELL))) | ||
.doesNotThrowAnyException(); | ||
} | ||
|
||
@Test | ||
public void getSweepSentinelUnderMultipleUncommittedWritesDoesNotThrow() { | ||
Transaction t1 = txManager.createNewTransaction(); | ||
writeSentinelToTestTable(TEST_CELL); | ||
|
||
for (int transaction = 1; transaction <= 10; transaction++) { | ||
putUncommittedAtFreshTimestamp(TEST_CELL); | ||
} | ||
|
||
assertThatCode(() -> t1.get(TABLE_SWEPT_CONSERVATIVE, ImmutableSet.of(TEST_CELL))) | ||
.doesNotThrowAnyException(); | ||
} | ||
|
||
@Test | ||
public void getSweepSentinelUnderHiddenCommittedWriteThrows() { | ||
Transaction t1 = txManager.createNewTransaction(); | ||
writeSentinelToTestTable(TEST_CELL); | ||
|
||
Transaction t2 = txManager.createNewTransaction(); | ||
t2.put(TABLE_SWEPT_CONSERVATIVE, ImmutableMap.of(TEST_CELL, PtBytes.toBytes("blablabla"))); | ||
t2.commit(); | ||
|
||
for (int transaction = 1; transaction <= 10; transaction++) { | ||
putUncommittedAtFreshTimestamp(TEST_CELL); | ||
} | ||
|
||
assertThatThrownBy(() -> t1.get(TABLE_SWEPT_CONSERVATIVE, ImmutableSet.of(TEST_CELL))) | ||
.isInstanceOf(TransactionFailedRetriableException.class) | ||
.hasMessageContaining("Tried to read a value that has been deleted."); | ||
} | ||
|
||
@Test | ||
public void getOrphanedSentinelAndSentinelUnderUncommittedWriteDoesNotThrow() { | ||
Transaction t1 = txManager.createNewTransaction(); | ||
writeSentinelToTestTable(TEST_CELL); | ||
writeSentinelToTestTable(TEST_CELL_2); | ||
putUncommittedAtFreshTimestamp(TEST_CELL_2); | ||
|
||
assertThatCode(() -> t1.get(TABLE_SWEPT_CONSERVATIVE, ImmutableSet.of(TEST_CELL, TEST_CELL_2))) | ||
.doesNotThrowAnyException(); | ||
} | ||
|
||
@Test | ||
public void getOrphanedSentinelAndSentinelUnderCommittedWriteThrows() { | ||
Transaction t1 = txManager.createNewTransaction(); | ||
writeSentinelToTestTable(TEST_CELL); | ||
writeSentinelToTestTable(TEST_CELL_2); | ||
Transaction t2 = txManager.createNewTransaction(); | ||
t2.put(TABLE_SWEPT_CONSERVATIVE, ImmutableMap.of(TEST_CELL_2, PtBytes.toBytes("covering"))); | ||
t2.commit(); | ||
|
||
assertThatThrownBy(() -> t1.get(TABLE_SWEPT_CONSERVATIVE, ImmutableSet.of(TEST_CELL, TEST_CELL_2))) | ||
.isInstanceOf(TransactionFailedRetriableException.class) | ||
.hasMessageContaining("Tried to read a value that has been deleted."); | ||
} | ||
|
||
@Test | ||
public void getSentinelUnderCommittedAndSentinelUnderUncommittedWriteThrows() { | ||
Transaction t1 = txManager.createNewTransaction(); | ||
writeSentinelToTestTable(TEST_CELL); | ||
writeSentinelToTestTable(TEST_CELL_2); | ||
putUncommittedAtFreshTimestamp(TEST_CELL); | ||
Transaction t2 = txManager.createNewTransaction(); | ||
t2.put(TABLE_SWEPT_CONSERVATIVE, ImmutableMap.of(TEST_CELL_2, PtBytes.toBytes("covering"))); | ||
t2.commit(); | ||
|
||
assertThatThrownBy(() -> t1.get(TABLE_SWEPT_CONSERVATIVE, ImmutableSet.of(TEST_CELL, TEST_CELL_2))) | ||
.isInstanceOf(TransactionFailedRetriableException.class) | ||
.hasMessageContaining("Tried to read a value that has been deleted."); | ||
} | ||
|
||
@SuppressWarnings("unchecked") // ArgumentCaptor | ||
@Test | ||
public void getSentinelValuesStressTest() { | ||
Transaction t1 = txManager.createNewTransaction(); | ||
|
||
List<Cell> cellsUnderUncommittedWrites = IntStream.rangeClosed(1, 100) | ||
.boxed() | ||
.map(num -> Cell.create(PtBytes.toBytes("row1"), PtBytes.toBytes(num))) | ||
.collect(Collectors.toList()); | ||
List<Cell> cellsUnderHiddenCommittedWrites = IntStream.rangeClosed(1, 100) | ||
.boxed() | ||
.map(num -> Cell.create(PtBytes.toBytes("row2"), PtBytes.toBytes(num))) | ||
.collect(Collectors.toList()); | ||
|
||
for (int index = 0; index < cellsUnderUncommittedWrites.size(); index++) { | ||
Cell cell = cellsUnderUncommittedWrites.get(index); | ||
writeSentinelToTestTable(cell); | ||
for (int uncommittedValue = 0; uncommittedValue <= index; uncommittedValue++) { | ||
putUncommittedAtFreshTimestamp(cell); | ||
} | ||
} | ||
|
||
for (int index = 0; index < cellsUnderHiddenCommittedWrites.size(); index++) { | ||
Cell cell = cellsUnderHiddenCommittedWrites.get(index); | ||
writeSentinelToTestTable(cell); | ||
Transaction txn = txManager.createNewTransaction(); | ||
txn.put(TABLE_SWEPT_CONSERVATIVE, ImmutableMap.of(cell, PtBytes.toBytes("i'm in a transaction"))); | ||
txn.commit(); | ||
for (int uncommittedValue = 0; uncommittedValue < index; uncommittedValue++) { | ||
putUncommittedAtFreshTimestamp(cell); | ||
} | ||
} | ||
|
||
ImmutableSet<Cell> cells = ImmutableSet.<Cell>builder() | ||
.addAll(cellsUnderUncommittedWrites) | ||
.addAll(cellsUnderHiddenCommittedWrites) | ||
.build(); | ||
assertThatThrownBy(() -> t1.get(TABLE_SWEPT_CONSERVATIVE, cells)) | ||
.isInstanceOf(TransactionFailedRetriableException.class) | ||
.hasMessageContaining("Tried to read a value that has been deleted."); | ||
|
||
ArgumentCaptor<Iterable<Long>> captor = ArgumentCaptor.forClass(Iterable.class); | ||
verify(transactionService, times(100)).get(captor.capture()); | ||
|
||
List<Iterable<Long>> stressTestRequests = captor.getAllValues(); | ||
assertThat(stressTestRequests).hasSize(100); | ||
for (int index = 1; index < stressTestRequests.size(); index++) { | ||
List<Long> previousTimestamps = StreamSupport.stream( | ||
stressTestRequests.get(index - 1).spliterator(), false) | ||
.collect(Collectors.toList()); | ||
assertThat(stressTestRequests.get(index)).hasSizeLessThan(previousTimestamps.size()); | ||
} | ||
|
||
Transaction retryTxn = txManager.createNewTransaction(); | ||
assertThatCode(() -> retryTxn.get(TABLE_SWEPT_CONSERVATIVE, cells)).doesNotThrowAnyException(); | ||
} | ||
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. assert that retry succeeds? 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. Yep, makes sense - added. |
||
|
||
private void putUncommittedAtFreshTimestamp(Cell cell) { | ||
keyValueService.put( | ||
TABLE_SWEPT_CONSERVATIVE, | ||
ImmutableMap.of(cell, PtBytes.toBytes("i am uncommitted")), | ||
txManager.createNewTransaction().getTimestamp()); | ||
} | ||
|
||
private void writeSentinelToTestTable(Cell cell) { | ||
keyValueService.put( | ||
TABLE_SWEPT_CONSERVATIVE, ImmutableMap.of(cell, new byte[0]), Value.INVALID_VALUE_TIMESTAMP); | ||
} | ||
|
||
private void setTransactionConfig(TransactionConfig config) { | ||
transactionConfig = config; | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
type: fix | ||
fix: | ||
description: Orphaned sentinels are now correctly skipped over. Previously, an orphaned sentinel covered by a value written by a (possibly uncommitted) transaction would not be considered orphaned when read until Sweep or another read transaction cleaned up the data written by said uncommitted transaction. | ||
links: | ||
- https://github.com/palantir/atlasdb/pull/5231 |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
nice 👍