-
Notifications
You must be signed in to change notification settings - Fork 18
History Builder API 2.0 #24
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
| // We derive from entropy source here to avoid letting the step change state for other states | ||
| // For example, if you start drawing more entropy bits from one of the steps, but won't change | ||
| // other steps, their states won't change either. |
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 like this description + API, makes the PCG "parallel streams" concept easier to understand for people that are less familiar
| public interface EntropySource | ||
| { | ||
| long next(); | ||
| void seed(long seed); |
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 re-seeding supported after next is called?
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, you can re-seed any time, at least as of now...
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 what situations would it make sense to re-seed? Seems like this could lead to runs that can't reproduce (unless we record every seed + when it was applied).
| if (pattern.matcher(statement).find()) | ||
| return primary.execute(statement, cl, bindings); |
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.
Eventually could see an Operation<SUT> API in the future, to make this easier to express
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.
great idea! if you like, you can open a jira and we hopefully will get to it.
|
|
||
| public CompletableFuture<Object[][]> executeAsync(String statement, ConsistencyLevel cl, Object... bindings) | ||
| { | ||
| return null; |
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.
Would prefer an UnsupportedOperationException here
| if (schema.trackLts) | ||
| { | ||
| visited_lts_list = (List<Long>) result[result.length - 1]; | ||
| visited_lts_list.sort(Long::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.
Don't we want these in the original order returned by the query?
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.
Good point. But since we mostly use these to compare with actually visited LTS (which are in monotonically increasing order), we are sorting them here. I could not find a use-case for knowing the order things were inserted in. Maybe we can have a more sophisticated checker that can check query bounds / visibility or something.
| } | ||
|
|
||
| @Override | ||
| public harry.util.BitSet columns() |
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.
Unnecessary qualified class name
| public CompiledStatement insert(long lts, long pd, long cd, long opId) | ||
| public CompiledStatement insert(VisitExecutor.WriteOp op) |
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.
A very welcome improvement!
| CompiledStatement statement = operationInternal(lts, pd, cd, opId, opType); | ||
| statements.add(statement.cql()); | ||
| Collections.addAll(bindings, statement.bindings()); | ||
| hadVisibleVisit = operation.opKind() != OpSelectors.OperationKind.DELETE_PARTITION; |
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.
Aren't delete range / slice also not visible?
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.
They are visible since if there is a partition liveness marker, partition's static column is going to survive. Adding a comment!
| // TODO: we can implement a pluggable time source via a custom clock, too; the only limitation is that order | ||
| // of timestamps has to be consistent with LTS order |
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.
Don't all Harry clocks have this requirement? OffsetClock and ApproximateMonotonicClock both provide monotonicity of RTS given monotonic LTSs.
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, sure. The idea here is that you can come with your own timestamps, not have Harry generate them. Maybe for some weird edge condition testing or something.
| * | ||
| * |
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.
It seems like this is intended to be a common entrypoint for people to create new Harry tests. Those people are likely to look at this file first. Can you expand on this doc?
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.
Added quite a bit of doc now
| import harry.visitors.MutatingRowVisitor; | ||
| import harry.visitors.ReplayingVisitor; | ||
|
|
||
| public class HistoryBuilderIntegrationTest extends ModelTestBase |
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.
nit: Did we mean to delete this file?
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 for the old, bad history builder!
|
|
||
| @Override | ||
| public void beginModification(long lts) | ||
| { |
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.
Unrelated to the patch I guess, but should readingFrom and writingTo be final above?
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, final not static! 👍
88982f1 to
37adaaf
Compare
| Arrays.copyOf(vds, vds.length), Arrays.copyOf(lts, lts.length)); | ||
| } | ||
|
|
||
| public boolean equals(Object o) |
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.
nit: @Override ?
I don't know if you'll need a hashCode() here or ever have this in a collection, but I'm compelled by habit to mention it :p
| List<Long> visited_lts) | ||
| { | ||
| assert slts.length == sds.length; | ||
| assert lts.length == vds.length; |
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.
nit: Brief message be helpful for when these trigger?
| return slts.length > 0; | ||
| } | ||
|
|
||
| public ResultSetRow clone() |
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.
nit: @Override
| { | ||
| return new ResultSetRow(pd, cd, | ||
| Arrays.copyOf(sds, sds.length), Arrays.copyOf(slts, slts.length), | ||
| Arrays.copyOf(vds, vds.length), Arrays.copyOf(lts, lts.length)); |
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.
Does visited_lts need to be part of the clone?
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! thank you for spotting!
| Objects.equals(visited_lts, that.visited_lts); | ||
| } | ||
|
|
||
| public String toString() |
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.
nit: Does visited_lts need to be part of the toString(), or is it something that might grow to be potentially huge and not visually friendly? :D
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, here i would probably leave it out since we display it separately
| return cqlName; | ||
| } | ||
|
|
||
| public boolean equals(Object o) |
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.
nit: @Override on this and hashCode()
| { | ||
| StringBuilder sb = new StringBuilder(); | ||
| sb.append("UPDATE ").append(schema.keyspace).append(".").append(schema.table) | ||
| .append(" SET visited_lts = visited_lts + [").append(lts).append("] ") |
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.
So we're guaranteed the schema has visited_lts here?
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.
added assert in constructor.
| } | ||
|
|
||
| private Reconciler.RowState updateRowState(Reconciler.RowState currentState, List<ColumnSpec<?>> columns, long cd, long[] vds, long lts, boolean writePrimaryKeyLiveness) | ||
| private Reconciler.RowState updateRowState(Reconciler.RowState currentState, List<ColumnSpec<?>> columns, long cd, long[] sds, long lts, boolean writePrimaryKeyLiveness) |
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.
Stumbling over naming here a bit. We have sds, and methods calling this w/ the argument staticVds or vds. updateRowState() isn't always called w/ static values (or value descriptors) right? (Aside: Would vds for normal column values and svds make sense for static values?)
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.
Renamed staticVds to sds, and renamed sds here to just vds (i.e. value descriptors).
|
|
||
| switch (opType) | ||
| long lts = operation.lts(); | ||
| assert pdSelector.pd(operation.lts(), schema) == operation.pd(); |
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.
nit: Quick message for the assert indicating what went wrong?
| assert pdSelector.pd(operation.lts(), schema) == operation.pd(); | ||
|
|
||
| if (operation.opKind() != OpSelectors.OperationKind.DELETE_PARTITION) | ||
| partitionState.visitedLts.add(operation.lts()); |
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.
Deleting the whole partition doesn't count as a visit?
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.
extracted into a method and documented:
/**
* All operations apart from partiton delition, including partition hist are visible since if there is a
* partition liveness marker, partition's static column is going to survive. We use this method to match
* computed LTS with tracked ones.
*/
public boolean hasVisibleVisit()
{
return this != OpSelectors.OperationKind.DELETE_PARTITION;
}
| writeOp.vds(), | ||
| lts, | ||
| op.opType == OpSelectors.OperationKind.INSERT || op.opType == OpSelectors.OperationKind.INSERT_WITH_STATICS); | ||
| op.opKind() == OpSelectors.OperationKind.INSERT || op.opKind() == OpSelectors.OperationKind.INSERT_WITH_STATICS); |
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.
nit: Might work to rename this opKind() -> kind(), so it reads op.kind()
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.
renamed
ce59efc to
32fe4fa
Compare
| * * Ability to generate multiple "next" random seeds | ||
| * * Ability to generate multiple "dependent" seeds, from which we can retrace the base seed with subtraction | ||
| */ | ||
| public interface RandomGenerator |
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.
nit: meant to remove the file as well?
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.
ok i know the reason: patch -p1 does not remove files! Fixed now.
| public boolean nextBoolean() | ||
| { | ||
| return RngUtils.asBoolean(next()); | ||
| } |
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.
nit: All these nextX() methods and derive() could use an @Override
| primary.shutdown(); | ||
| } | ||
|
|
||
| private static final Pattern pattern = Pattern.compile("select", Pattern.CASE_INSENSITIVE); |
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.
nit: Should we use ^select just out of paranoia? (I guess you can also just guarantee there won't be random column names like fooSelect :D
| * be taken to map a real-time timestamp from the value retrieved from the database in order | ||
| * to map it back to the logical timestamp of the operation that wrote this value. | ||
| */ | ||
| // TOD: rename to just "Clock", it is not monotonic anymore |
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.
nit: Something we want to do here or in another patch?
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.
May as well do it now! Done
| private final int STEPS_PER_ITERATION = 1_000; | ||
| private final int MAX_PARTITIONS = 100; | ||
|
|
||
| protected Configuration.ModelConfiguration modelConfiguration() |
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.
nit: @Override
|
|
||
| public interface WriteOp extends Operation | ||
| { | ||
| long pd(); |
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.
nit: Already inherits pd() from Operation?
| ColumnSpec.regularColumn("v2", ColumnSpec.int64Type)), | ||
| Arrays.asList()) | ||
| .trackLts(); | ||
| ; |
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.
nit: remove ;?
| import harry.visitors.ReplayingVisitor; | ||
| import harry.visitors.VisitExecutor; | ||
|
|
||
| public class HistoryBuilderTest |
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.
nit: Remove file as well?
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.
Oh weird, not sure why it has not gotten removed
| } | ||
| } | ||
|
|
||
| public static interface Step<STATE> |
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.
nit: None of the interfaces need the static keyword
|
|
||
| return true; | ||
| }) | ||
| .run(STEPS_PER_ITERATION, seed); |
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.
So I'll summarize what I think is going on here, and then my review should be complete...
We create a ModelChecker with single-operation steps to...
1.) Record a series of operations against the model using a HistoryBuilder as the state container.
2.) Replay them all against the SUT.
3.) Validate all the partitions we visited.
There are some details I'm not clear on, though. It looks like ModelChecker#run() executes individual steps, and each step execution adds data to the HistoryBuilder log. Then when we check the exit condition, we actually replay the operations to the SUT from the beginning up to the last visited LTS and validate the model.
Am I one the right track?
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.
Right, we create a model checker that triggers series of random steps, then replays them against the SUT, and validates visited partitions.
Then when we check the exit condition, we actually replay the operations to the SUT from the beginning up to the last visited LTS and validate the model.
So there are two different history builders: one replaying (with auto-replay on) and one that simply creates series of events, in case you would like to preturb / execute them in some sophisticated way. There was no specific way for us to collect and then replay things, but I have first created a regular history builder, and then replaying one, built on top of the first one.
Patch for CASSANDRA-19116 by Alex Petrov, reviewed by Abe Ratnofsky
32fe4fa to
f8d3983
Compare
No description provided.