-
Couldn't load subscription status.
- Fork 1.9k
[Kernel] SnapshotStatistics and new Snapshot write-crc-file APIs #5340
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
[Kernel] SnapshotStatistics and new Snapshot write-crc-file APIs #5340
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.
Generally looks great, thanks!
Possible other API:
writeChecksum(Engine engine, ChecksumWriteMode mode)Now the connector if it doesn't really care can just do:
writeChecksum(engine, statistics.getChecksumWriteMode())
kernel/kernel-api/src/main/java/io/delta/kernel/internal/SnapshotImpl.java
Outdated
Show resolved
Hide resolved
kernel/kernel-api/src/main/java/io/delta/kernel/internal/SnapshotImpl.java
Outdated
Show resolved
Hide resolved
|
@nicklan -- SG -- will implement |
| crcInfo.getFileSizeHistogram)) | ||
| } | ||
|
|
||
| def executeCrcSimple(result: TransactionCommitResult, engine: Engine): TransactionCommitResult = { |
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 semantic this method wanted was: write the CRC file via simple mode. Instead, the semantic it was implementing was: write the CRC file if the SimpleCrcHook was present. That was incorrect.
4642bdd to
a8afd0b
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.
Thanks, lgtm!
| logger.info("Skipping writing checksum file: input mode was NONE"); | ||
| return; | ||
| case SIMPLE: | ||
| final CRCInfo crcInfo = |
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 actual == SIMPLE is this guaranteed to be true? i.e. can we replace this check with that one?
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 if actual == SIMPLE then logReplay.getCrcInfoAtSnapshotVersion() is defined.
Would you like something like the below?
if (actual != SIMPLE) throw new IllegalStateException(...)
final CRCInfo crcInfo = logReplay.getCrcInfoAtSnapshotVersion().get()Using .getOrElse is just a bit more java idiomatic and a bit safer, but I'm okay if you want the 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.
Actually -- I think I liked my checks/logic from a few commits ago:
We should just check if the file already exists. If so, that's a no-op and we should logger.warn and exit early.
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 this is best:
public void writeChecksum(Engine engine, Snapshot.ChecksumWriteMode mode) throws IOException {
final Snapshot.ChecksumWriteMode actual = getStatistics().getChecksumWriteMode();
switch (mode) {
case NONE:
logger.info("Skipping writing checksum file: input mode was NONE");
case SIMPLE:
if (actual == ChecksumWriteMode.NONE) {
logger.warn("Not writing checksum in SIMPLE mode: checksum file already exists");
return;
}
if (actual == ChecksumWriteMode.FULL) {
throw new IllegalStateException(
"Cannot write checksum in SIMPLE mode: FULL mode required");
}
final CRCInfo crcInfo = logReplay.getCrcInfoAtSnapshotVersion().get();
logger.info("Executing checksum write in SIMPLE mode");
new ChecksumWriter(logPath).writeCheckSum(engine, crcInfo);
case FULL:
if (actual == ChecksumWriteMode.NONE) {
logger.warn("Not writing checksum as FULL: checksum file already exists");
return;
}
if (actual == ChecksumWriteMode.SIMPLE) {
logger.warn("Checksum SIMPLE mode was available, but FULL mode was requested");
}
logger.info("Executing checksum write in FULL mode");
ChecksumUtils.computeStateAndWriteChecksum(engine, getLogSegment());
default:
throw new IllegalStateException("Unknown checksum write mode: " + mode);
}
}|
@nicklan -- One consequence of Another idea: The updated (local) code for me is below: public void writeChecksum(Engine engine, Snapshot.ChecksumWriteMode mode) throws IOException {
final Snapshot.ChecksumWriteMode actual = getStatistics().getChecksumWriteMode();
switch (mode) {
case NONE:
logger.info("Skipping writing checksum file: input mode was NONE");
if (actual != ChecksumWriteMode.NONE) {
logger.warn("Note that the checksum file does NOT actually exist");
}
return;
case SIMPLE:
if (actual == ChecksumWriteMode.NONE) {
logger.warn("Not writing checksum in SIMPLE mode: checksum file already exists");
return;
}
if (actual == ChecksumWriteMode.FULL) {
throw new IllegalStateException(
"Cannot write checksum in SIMPLE mode: FULL mode required");
}
final CRCInfo crcInfo = logReplay.getCrcInfoAtSnapshotVersion().get();
logger.info("Executing checksum write in SIMPLE mode");
new ChecksumWriter(logPath).writeCheckSum(engine, crcInfo);
return;
case FULL:
if (actual == ChecksumWriteMode.NONE) {
logger.warn("Not writing checksum as FULL mode: checksum file already exists");
return;
}
if (actual == ChecksumWriteMode.SIMPLE) {
logger.warn("Requested checksum write in FULL mode, but SIMPLE mode is available");
}
logger.info("Executing checksum write in FULL mode");
ChecksumUtils.computeStateAndWriteChecksum(engine, getLogSegment());
return;
default:
throw new IllegalStateException("Unknown checksum write mode: " + 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.
Mostly just clarifying questions. Tests look great! Feeling a little confused today for some reason 😐
| * <li><b>FULL:</b> Computes the necessary state if needed by replaying the delta log since the | ||
| * latest checksum (if present). This always succeeds but may be expensive for large tables | ||
| * when CRC information is not available. |
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 - maybe rephrase this? "This always succeeds" sounds like it cannot fail (I assume it can!). I think this is instead trying to convey that regardless of what the Snapshot has in memory, this will try to execute the crc write.
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.
Hmm, actually, why does this have a mode input at all? Can this not just look internally at what the mode is?
Maybe, is this just to make it explicit opt-in for the more expensive option?
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.
Hmm, actually, why does this have a mode input at all? Can this not just look internally at what the mode is?
Agreed -- if you look at some of the PR comments, I'm thinking along these lines, too.
kernel/kernel-api/src/main/java/io/delta/kernel/internal/SnapshotImpl.java
Show resolved
Hide resolved
| logReplay | ||
| .getCrcInfoAtSnapshotVersion() |
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.
Just curious, do you know why we store this here? Had to do some digging to understand how/when we populate this
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 has to do with our LogReplay / ProtocolMetadataReplay / DomainMetadata Replay logic.
Fundamentally, the Snapshot should be injected with CRC info (optional) and then use that as needed to defer to static log replay utilities (e.g. for domain metadata).
Instead today, we have some tech debt where Snapshot is injected with a LogReplay, and that LogReplay has the crc info
kernel/kernel-api/src/main/java/io/delta/kernel/internal/replay/LogReplay.java
Show resolved
Hide resolved
| * SIMPLE and uses the post-commit snapshot's writeChecksumSimple method. Note, this requires the | ||
| * test suite uses [[commitTransaction]] and [[verifyWrittenContent]]. | ||
| */ | ||
| trait WriteUtilsWithPostCommitSnapshotCrcSimpleWrite extends AnyFunSuite with WriteUtils { |
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.
Ideally we should factor this out like we did with TransactionBuilderSupport :( Not a blocker though
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.
Can you elaborate, sorry?
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 we talked about this in depth before for other test scenarios. But ideally we have abstract definitions for these fxs and then implement them in child traits, instead of just overriding them.
So like TransactionCommitSupport defines these abstractly, and then we can have like BasicTransactionCommitSupport and PostCommitSnapshotCRCCommitSupport etc for each different variation
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.
Makes sense -- that would let us use mixins to inject specific funcitonality -- e.g. write crc, write checkpoint, publish, etc.
...faults/src/test/scala/io/delta/kernel/defaults/SnapshotChecksumStatisticsAndWriteSuite.scala
Show resolved
Hide resolved
| // Create version 2 without writing its CRC | ||
| val snapshot1 = TableManager.loadSnapshot(tablePath).build(engine) | ||
| val txn2 = snapshot1.buildUpdateTableTransaction("xx", Operation.WRITE).build(engine) | ||
| txn2.commit(engine, emptyIterable()) |
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.
sorry, why do we have to do this? won't this test check the same thing if we remove this part?
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 just tests: writes a few versions (0, 1, 2) and then time travel to version 1 and then we assert that writing the CRC at version 1 is FULL.
So -- if you're asking why do we write v2 -- that just felt like a reasonable thing to test. The alternative is we write versions 0 and 1, and then we time travel to the historical version 0. Could absolutely do that, too. I thought that having a few more versions and time travelling to a middle one was a bit more common.
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.
sorry, why is "time-travel" the important part? If you just load write versions (0, 1) and then load version (1) (not via post-commit snapshot) isn't that the same scenario? or I think maybe that was already tested?
So maybe the answer is = under the hood, this tests the same thing that's already tested, but the time-travel scenario semantically makes sense as a separate test
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 alternative is we write versions 0 and 1, and then we time travel to the historical version 0.
Not suggesting this! This scenario makes sense if we are trying to test time-travel, just didn't understand why it was really a distinct different for the underlying implementation
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 want to test a historical version (not the latest). All of the other tests are checking that we can write the CRC at the latest version (either loading it explicitly or using the post commit snapshot).
|
@nicklan I think another option is: Thus, |
Yep, this makes sense to me, allows communicating that there should not be a checksum written, but does not allow "requesting that no checksum be written", which doesn't really make sense. |
|
I'm still wondering whether But couldn't you just presume, if you call |
|
@allisonport-db Yup, fair questions. Both of these APIs are acceptable and reasonable. It comes down to a matter of slight preference, that's all.
|
| logger.warn("Requested checksum write in FULL mode, but SIMPLE mode is available"); | ||
| } | ||
| logger.info("Executing checksum write in FULL mode"); | ||
| ChecksumUtils.computeStateAndWriteChecksum(engine, getLogSegment()); |
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.
Can't we just do new ChecksumWriter(logPath).writeCheckSum(engine, crcInfo) here when actual == ChecksumWriteMode.SIMPLE?
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.
Not sure if computeStateAndWriteChecksum shortcuts that when possible?
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.
Maybe we should switch on the actual mode instead?
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.
We can, of course, but I don't think we should. The user has asked for a FULL replay -- that's what we should do. And we log the warning that they are doing this despite the SIMPLE being available
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 is also what @nicklan asked for too -- #5340 (comment). I have a slight preference for that way (this current way) but totally understand your perspective, too (that's what I had initially coded up to begin with).
So -- I'm inclined to just go with this and merge this PR sooner than later -- and if you feel strongly to change this feel free to start a convo and we can easily change this later?
| // ===== THEN ===== | ||
| val snapshot0 = result0.getPostCommitSnapshot.get() | ||
| assert(snapshot0.getStatistics.getChecksumWriteMode == ChecksumWriteMode.SIMPLE) // expected | ||
| assert(snapshot0.getStatistics.getChecksumWriteMode.get == ChecksumWriteMode.SIMPLE) |
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 - use .contains?
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 is a Java Optional -- it doesn't have .contains :/
🥞 Stacked PR
Use this link to review incremental changes.
Which Delta project/connector is this regarding?
Description
This PR adds:
Snapshot::getStatisticsandSnapshotStatisticsAPIsChecksumWriteModeAPISnapshot::writeChecksum(Engine, ChecksumWriteMode)APIThis PR enables the e2e flow of:
Snapshot::writeChecksumwith the mode they are willing to payHow was this patch tested?
New UTs. Updated existing write-with-crc-suites to run using this new logic, too.
Does this PR introduce any user-facing changes?
New Snapshot APIs.